commit 3b7f30d14d86ea440e3a4f2675536ed8ea9df13f Author: Damien Coles Date: Mon Jan 26 11:30:40 2026 -0500 public-ready-init diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e4a2a21 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +node_modules +.git +.gitignore +.houdini +.svelte-kit +build +dist +*.log +*.md +.env* +!.env.example +.vscode +.idea diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..38c6737 --- /dev/null +++ b/.env.example @@ -0,0 +1,23 @@ +# GraphQL API +PUBLIC_GRAPHQL_URL=http://localhost:8000/graphql/ + +# Ory Kratos (Authentication) +PUBLIC_KRATOS_URL=http://localhost:4433 + +# Calendar Service +PUBLIC_CALENDAR_API_URL=http://localhost:8001 +PUBLIC_CALENDAR_API_KEY=your-calendar-api-key + +# Email Service +PUBLIC_EMAIL_API_URL=http://localhost:8002 +PUBLIC_EMAIL_API_KEY=your-email-api-key + +# Wave API (Invoice Integration) +WAVE_ACCESS_TOKEN=your-wave-access-token +WAVE_BUSINESS_ID=your-wave-business-id + +# Houdini Schema Introspection (development only) +USER_ID=your-dev-user-id +USER_PROFILE_TYPE=TeamProfileType +OATHKEEPER_SECRET=your-oathkeeper-secret +DJANGO_PROFILE_ID=your-dev-profile-id diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e381bd4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +.houdini +/context/ +.idea \ No newline at end of file diff --git a/.graphqlrc.yaml b/.graphqlrc.yaml new file mode 100644 index 0000000..e8254e0 --- /dev/null +++ b/.graphqlrc.yaml @@ -0,0 +1,16 @@ +projects: + default: + schema: + - ./schema.graphql + - ./.houdini/graphql/schema.graphql + documents: + - '**/*.gql' + - '**/*.graphql' + - '**/*.svelte' + - ./.houdini/graphql/documents.gql + exclude: + - '**/node_modules/**' + - 'src/lib/graphql/wave/**' + wave: + schema: ./src/lib/graphql/wave/schema.graphql + documents: src/lib/graphql/wave/**/*.gql diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..d747fa2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,14 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Generated files +.houdini/ +src/lib/graphql/wave/generated.ts +src/lib/graphql/wave/schema.graphql + +# Miscellaneous +/static/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..819fa57 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,16 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ], + "tailwindStylesheet": "./src/routes/layout.css" +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6b121b5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,177 @@ +# Claude Code Guidelines + +## Project Overview + +This is a SvelteKit 2.x application using Svelte 5 with Tailwind CSS v4. + +## Code Style Rules + +### SVG Attributes + +When using SVG elements, use `style` attribute instead of `fill` attribute to avoid IDE obsolete attribute warnings: + +```svelte + + + + + + + +``` + +### Svelte 5 Runes + +- Use `$state()` for reactive state +- Use `$derived()` for computed values +- Use `$props()` for component props +- Use `$effect()` for side effects + +### Page Store + +Use `$app/state` instead of `$app/stores` for the page object: + +```svelte + +import {page} from '$app/state'; const path = page.url.pathname; + + +import {page} from '$app/stores'; const path = $page.url.pathname; +``` + +### Theme Classes + +Use the custom theme utility classes defined in `layout.css`: + +- `text-theme`, `text-theme-secondary`, `text-theme-muted` +- `bg-theme`, `bg-theme-card` +- `border-theme` +- `shadow-theme`, `shadow-theme-lg` + +### Hover/Active States + +Use consistent hover/active patterns: + +``` +hover:bg-black/5 dark:hover:bg-white/10 +active:bg-black/10 dark:active:bg-white/15 +``` + +### Mobile Grid Layouts + +For card grids that should display single-column on mobile and multi-column on larger screens, always: + +1. Explicitly set `grid-cols-1` (don't rely on CSS grid's implicit single column) +2. Add `min-w-0` on grid items with flex content to allow proper shrinking + +```svelte + +
+
+
...
+
+
+ + +
+
...
+
+``` + +### Button Element Content + +Button elements can only contain phrasing content (inline elements). Use `` instead of `
`, `

`, or `

`-`

` inside buttons: + +```svelte + + + + + +``` + +For multi-line button content, use `` to create line breaks: + +```svelte + +``` + +### SvelteSet Reactivity + +`SvelteSet` from `svelte/reactivity` is already reactive and does NOT need to be wrapped in `$state()`: + +```svelte + +import { SvelteSet } from 'svelte/reactivity'; +let expandedItems = new SvelteSet(); + + +let expandedItems = $state(new SvelteSet()); +``` + +### Each Block Keys + +Always provide a key for `{#each}` blocks to avoid ESLint warnings: + +```svelte + +{#each items as item (item.id)} +{#each days as day (day)} + + +{#each items as item} +``` + +### Date Formatting + +Use the date utilities from `$lib/utils/date` which use `date-fns` to properly handle ISO date strings without timezone offset issues: + +```svelte +import {formatDate} from '$lib/utils/date'; + + +{formatDate(startDate) || '—'} +``` + +### File Organization + +- Place `.svelte.ts` files (stores, shared state) in `src/lib/stores/`, not in component folders +- GraphQL mutations go in `src/lib/graphql/mutations/{entity}/` +- GraphQL queries go in `src/lib/graphql/queries/{entity}/` + +### Backend Integration Notes + +- The backend uses a unique constraint allowing only ONE active scope per address +- The `isConditional` field on tasks is legacy and should always be set to `false` +- GraphQL inputs use camelCase (e.g., `isActive`, `accountId`) which maps to snake_case in Django + +### Task Frequency Values + +The backend `TaskFrequencyChoices` enum uses **lowercase** values. Handle frequency differently depending on the operation: + +**Individual mutations** (create/update task or task template) - use lowercase: + +```typescript +frequency: 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'triannual' | 'annual' | 'as_needed'; +``` + +**JSON import** (`createScopeTemplateFromJson`) - uppercase is acceptable: + +```json +{ "frequency": "DAILY" } +``` + +The backend's `build_scope_template` service normalizes uppercase to lowercase automatically. + +Valid frequency values: `daily`, `weekly`, `monthly`, `quarterly`, `triannual`, `annual`, `as_needed` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..482d5ad --- /dev/null +++ b/Dockerfile @@ -0,0 +1,64 @@ +# Use the official Node.js runtime as base image +FROM node:22-alpine AS builder + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci + +# Make PUBLIC_ vars available at build time +ARG PUBLIC_CALENDAR_API_URL +ARG PUBLIC_CALENDAR_API_KEY +ARG PUBLIC_EMAIL_API_URL +ARG PUBLIC_EMAIL_API_KEY +ARG PUBLIC_WAVE_BUSINESS_ID +ARG WAVE_ACCESS_TOKEN +ENV PUBLIC_CALENDAR_API_URL=$PUBLIC_CALENDAR_API_URL +ENV PUBLIC_CALENDAR_API_KEY=$PUBLIC_CALENDAR_API_KEY +ENV PUBLIC_EMAIL_API_URL=$PUBLIC_EMAIL_API_URL +ENV PUBLIC_EMAIL_API_KEY=$PUBLIC_EMAIL_API_KEY +ENV PUBLIC_WAVE_BUSINESS_ID=$PUBLIC_WAVE_BUSINESS_ID +ENV WAVE_ACCESS_TOKEN=$WAVE_ACCESS_TOKEN + +# Copy source code +COPY . . + +# Build the application +RUN npm run build + +# Production stage +FROM node:22-alpine AS runner + +WORKDIR /app + +# Ensure production mode in runtime +ENV NODE_ENV=production +ENV HOST=0.0.0.0 +ENV PORT=3000 + +# Copy package files +COPY package*.json ./ + +# Install only production dependencies (modern flag) +RUN npm ci --omit=dev && npm cache clean --force + +# Copy built application from builder stage +COPY --from=builder /app/build build/ + +# Create a non-root user +RUN addgroup -g 1001 -S nodejs +RUN adduser -S svelte -u 1001 + +# Change ownership of the app directory +RUN chown -R svelte:nodejs /app +USER svelte + +# Expose the port your app runs on +EXPOSE 3000 + +# Start the application +CMD ["node", "build"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e795a73 --- /dev/null +++ b/README.md @@ -0,0 +1,230 @@ +# Nexus 5 Frontend 3 - Full Portal + +Full-featured web portal for the Nexus 5 platform, combining public marketing pages, customer portal, team interface, and admin dashboard in a single unified application. + +## Overview + +This is the third and most complete iteration of the Nexus 5 frontend, designed as a comprehensive portal that serves all user types: public visitors, customers, team members, and administrators. It includes public-facing marketing pages alongside authenticated portals for different user roles. + +## Tech Stack + +- **SvelteKit 5** - Latest SvelteKit with Svelte 5 runes +- **Houdini** - Type-safe GraphQL client +- **Tailwind CSS v4** - Next-generation Tailwind with CSS variables +- **TypeScript** - Strict mode enabled +- **Node adapter** - Production deployment +- **Wave Integration** - Invoice management via Wave API + +## Evolution + +| Feature | Frontend 1 | Frontend 2 | Frontend 3 | +|---------|------------|------------|------------| +| **Purpose** | Admin only | Team only | All users | +| **Public Pages** | No | No | Yes | +| **Customer Portal** | No | No | Yes | +| **Team Portal** | No | Yes | Yes | +| **Admin Portal** | Yes | No | Yes | +| **Theming** | Basic dark mode | Theme toggle | Full CSS variables | +| **AI Chat** | No | No | Yes | +| **Invoice Integration** | No | No | Wave API | + +## Features + +### Public Pages +- **Home** - Landing page with service overview +- **Services** - Service offerings and descriptions +- **Pricing** - Pricing information +- **About** - Company information +- **Contact** - Contact form with email integration +- **Privacy/Terms** - Legal pages + +### Customer Portal +- **Dashboard** - Overview of accounts and upcoming work +- **Accounts** - View service accounts and locations +- **Schedule** - Upcoming services and projects +- **History** - Completed work history +- **Invoices** - View and pay invoices +- **Profile** - Customer profile management + +### Team Portal +- **Dashboard** - Today's assignments +- **Services** - Assigned services with session management +- **Projects** - Assigned projects +- **Reports** - Submit and view reports +- **Profile** - Team member profile + +### Admin Portal +- **Dashboard** - Business overview +- **Customers** - Customer management +- **Accounts** - Account management +- **Services** - Service scheduling and assignment +- **Projects** - Project management +- **Scopes** - Scope template management +- **Reports** - Report management +- **Invoices** - Invoice creation and Wave integration +- **Calendar** - Event management +- **Profiles** - User profile management +- **Notifications** - Notification rule configuration +- **Event Log** - System event audit log + +### Cross-cutting Features +- **AI Chat** - Claude-powered assistant for all users +- **Messages** - Inter-user messaging +- **Notifications** - Real-time notifications +- **Theme Toggle** - Light/dark mode support + +## Getting Started + +### Prerequisites + +- Node.js 18+ +- Access to Nexus 5 GraphQL API +- (Optional) Wave API credentials for invoicing + +### Development + +```bash +# Install dependencies +npm install + +# Start development server +npm run dev + +# Build for production +npm run build + +# Type checking +npm run check +``` + +### Docker Deployment + +```bash +# Build and run +docker-compose up --build + +# Production deployment +docker-compose -f docker-compose.prod.yml up -d +``` + +### Environment Variables + +See `.env.example` for required configuration: + +- `PUBLIC_GRAPHQL_URL` - Nexus 5 API endpoint +- `PUBLIC_KRATOS_URL` - Ory Kratos public URL +- `WAVE_ACCESS_TOKEN` - Wave API token (for invoicing) +- `WAVE_BUSINESS_ID` - Wave business ID + +## Project Structure + +``` +src/ +├── lib/ +│ ├── components/ +│ │ ├── admin/ # Admin-specific components +│ │ ├── chat/ # AI chat interface +│ │ ├── customer/ # Customer portal components +│ │ ├── entity/ # Shared entity components +│ │ ├── icons/ # SVG icon components +│ │ ├── layout/ # Layout components +│ │ ├── nav/ # Navigation (TopNav, AdminNav, etc.) +│ │ ├── session/ # Work session components +│ │ ├── shared/ # Shared UI components +│ │ └── team/ # Team portal components +│ ├── data/ # Static data (event types, etc.) +│ ├── graphql/ +│ │ ├── mutations/ # GraphQL mutations +│ │ ├── queries/ # GraphQL queries +│ │ └── wave/ # Wave API integration +│ ├── server/ # Server-side utilities +│ ├── services/ # External service integrations +│ ├── stores/ # Svelte stores +│ └── utils/ # Utility functions +└── routes/ + ├── admin/ # Admin portal routes + ├── customer/ # Customer portal routes + ├── team/ # Team portal routes + ├── messages/ # Messaging + ├── notifications/ # Notifications + ├── about/ # Public pages + ├── contact/ + ├── pricing/ + ├── privacy/ + ├── services/ + ├── terms/ + └── login/logout/ # Auth flows +``` + +## Key Patterns + +### CSS Variables for Theming + +Uses CSS custom properties for comprehensive theming: + +```css +:root { + --color-primary-500: #3b82f6; + --bg-primary: 255 255 255; + --text-primary: 15 23 42; +} + +.dark { + --bg-primary: 15 23 42; + --text-primary: 248 250 252; +} +``` + +### Role-Based Navigation + +Navigation adapts based on user role: + +```svelte +{#if isAdmin} + +{:else if isTeamMember} + +{:else if isCustomer} + +{:else} + +{/if} +``` + +### Server-Side Data Loading + +Uses SvelteKit's load functions for server-side data fetching: + +```typescript +// +page.server.ts +export const load = async ({ locals }) => { + const data = await fetchData(locals.user); + return { data }; +}; +``` + +### Wave API Integration + +Separate GraphQL client for Wave invoice management: + +```typescript +import { waveClient } from '$lib/graphql/wave'; + +const invoices = await waveClient.query({ + query: ListInvoices, + variables: { businessId } +}); +``` + +## Related Repositories + +- **nexus-5** - Django GraphQL API backend +- **nexus-5-auth** - Ory Kratos/Oathkeeper authentication +- **nexus-5-frontend-1** - Admin-only dashboard +- **nexus-5-frontend-2** - Team-only mobile app +- **nexus-5-emailer** - Email microservice +- **nexus-5-scheduler** - Calendar integration microservice + +## License + +MIT License - See LICENSE file for details. diff --git a/codegen.wave.ts b/codegen.wave.ts new file mode 100644 index 0000000..c7bc36b --- /dev/null +++ b/codegen.wave.ts @@ -0,0 +1,28 @@ +import type { CodegenConfig } from '@graphql-codegen/cli'; + +const config: CodegenConfig = { + schema: [ + { + 'https://gql.waveapps.com/graphql/public': { + headers: { + Authorization: 'Bearer ' + process.env.WAVE_ACCESS_TOKEN + } + } + } + ], + documents: 'src/lib/graphql/wave/**/*.gql', + generates: { + 'src/lib/graphql/wave/schema.graphql': { + plugins: ['schema-ast'] + }, + 'src/lib/graphql/wave/generated.ts': { + plugins: ['typescript', 'typescript-operations', 'typescript-urql'], + config: { + withHooks: false, + withComponent: false + } + } + } +}; + +export default config; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2db5c76 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +services: + web: + build: + context: . + dockerfile: Dockerfile + args: + PUBLIC_CALENDAR_API_URL: ${PUBLIC_CALENDAR_API_URL} + PUBLIC_CALENDAR_API_KEY: ${PUBLIC_CALENDAR_API_KEY} + PUBLIC_EMAIL_API_URL: ${PUBLIC_EMAIL_API_URL} + PUBLIC_EMAIL_API_KEY: ${PUBLIC_EMAIL_API_KEY} + PUBLIC_WAVE_BUSINESS_ID: ${PUBLIC_WAVE_BUSINESS_ID} + WAVE_ACCESS_TOKEN: ${WAVE_ACCESS_TOKEN} + image: nexus-5-frontend-3:latest + container_name: nexus-5-frontend-3 + ports: + - '7000:3000' + env_file: + - .env + environment: + - NODE_ENV=production + - PORT=3000 + - HOST=0.0.0.0 + restart: unless-stopped diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..d9e03f1 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,46 @@ +import prettier from 'eslint-config-prettier'; +import { fileURLToPath } from 'node:url'; +import { includeIgnoreFile } from '@eslint/compat'; +import js from '@eslint/js'; +import svelte from 'eslint-plugin-svelte'; +import { defineConfig } from 'eslint/config'; +import globals from 'globals'; +import ts from 'typescript-eslint'; +import svelteConfig from './svelte.config.js'; + +const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); + +export default defineConfig( + includeIgnoreFile(gitignorePath), + { + ignores: ['src/lib/graphql/wave/generated.ts'] + }, + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs.recommended, + prettier, + ...svelte.configs.prettier, + { + languageOptions: { + globals: { ...globals.browser, ...globals.node } + }, + rules: { + // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. + // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors + 'no-undef': 'off', + // Disable the navigation resolve rule - we use standard href links + 'svelte/no-navigation-without-resolve': 'off' + } + }, + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: ['.svelte'], + parser: ts.parser, + svelteConfig + } + } + } +); diff --git a/houdini.config.js b/houdini.config.js new file mode 100644 index 0000000..7cfe68b --- /dev/null +++ b/houdini.config.js @@ -0,0 +1,27 @@ +/// + +/** @type {import('houdini').ConfigFile} */ +const config = { + watchSchema: { + url: 'http://10.10.10.51:5500/graphql/', + headers: { + 'X-USER-ID': (env) => env.USER_ID, + 'X-USER-PROFILE-TYPE': (env) => env.USER_PROFILE_TYPE, + 'X-OATHKEEPER-SECRET': (env) => env.OATHKEEPER_SECRET, + 'X-DJANGO-PROFILE-ID': (env) => env.DJANGO_PROFILE_ID + } + }, + schemaPath: './schema.graphql', + runtimeDir: '.houdini', + defaultCachePolicy: 'NetworkOnly', + // Exclude Wave GraphQL files - they use urql with a separate schema + exclude: ['src/lib/graphql/wave/**'], + plugins: { + 'houdini-svelte': { + client: './src/lib/graphql/client.ts', + forceRunesMode: true + } + } +}; + +export default config; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..50c1629 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,11225 @@ +{ + "name": "nexus-5-frontend-3", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nexus-5-frontend-3", + "version": "0.0.1", + "dependencies": { + "@urql/svelte": "^5.0.0", + "graphql": "^16.12.0", + "heic2any": "^0.0.4", + "urql": "^5.0.1" + }, + "devDependencies": { + "@eslint/compat": "^1.4.0", + "@eslint/js": "^9.39.1", + "@graphql-codegen/cli": "^6.1.0", + "@graphql-codegen/schema-ast": "^5.0.0", + "@graphql-codegen/typescript": "^5.0.6", + "@graphql-codegen/typescript-operations": "^5.0.6", + "@graphql-codegen/typescript-urql": "^4.0.1", + "@sveltejs/adapter-node": "^5.4.0", + "@sveltejs/kit": "^2.48.5", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tailwindcss/forms": "^0.5.10", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.1.17", + "@types/node": "^24", + "date-fns": "^4.1.0", + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.13.0", + "globals": "^16.5.0", + "houdini": "^2.0.0-next.11", + "houdini-svelte": "^3.0.0-next.13", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.7.1", + "svelte": "^5.43.8", + "svelte-check": "^4.3.4", + "tailwindcss": "^4.1.17", + "typescript": "^5.9.3", + "typescript-eslint": "^8.47.0", + "vite": "^7.2.2", + "vite-plugin-devtools-json": "^1.0.0", + "vite-plugin-mkcert": "^1.17.9" + } + }, + "node_modules/@0no-co/graphql.web": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.2.0.tgz", + "integrity": "sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==", + "license": "MIT", + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + }, + "peerDependenciesMeta": { + "graphql": { + "optional": true + } + } + }, + "node_modules/@ardatan/relay-compiler": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/@ardatan/relay-compiler/-/relay-compiler-12.0.3.tgz", + "integrity": "sha512-mBDFOGvAoVlWaWqs3hm1AciGHSQE1rqFc/liZTyYz/Oek9yZdT5H26pH2zAFuEiTiBVPPyMuqf5VjOFPI2DGsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/runtime": "^7.26.10", + "chalk": "^4.0.0", + "fb-watchman": "^2.0.0", + "immutable": "~3.7.6", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "relay-runtime": "12.0.0", + "signedsource": "^1.0.0" + }, + "bin": { + "relay-compiler": "bin/relay-compiler" + }, + "peerDependencies": { + "graphql": "*" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", + "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", + "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", + "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", + "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-flow": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@clack/core": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.5.tgz", + "integrity": "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.6.3.tgz", + "integrity": "sha512-AM+kFmAHawpUQv2q9+mcB6jLKxXGjgu/r2EQjEwujgpCdzrST6BJqYw00GRn56/L/Izw5U7ImoLmy00X/r80Pw==", + "bundleDependencies": [ + "is-unicode-supported" + ], + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "^0.3.2", + "is-unicode-supported": "*", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts/node_modules/is-unicode-supported": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@envelop/core": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@envelop/core/-/core-4.0.3.tgz", + "integrity": "sha512-O0Vz8E0TObT6ijAob8jYFVJavcGywKThM3UAsxUIBBVPYZTMiqI9lo2gmAnbMUnrDcAYkUTZEW9FDYPRdF5l6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/types": "4.0.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@envelop/instrumentation": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@envelop/instrumentation/-/instrumentation-1.0.0.tgz", + "integrity": "sha512-cxgkB66RQB95H3X27jlnxCRNTmPuSTgmBAq6/4n2Dtv4hsk4yz8FadA1ggmd0uZzvKqWD6CR+WFgTjhDqg7eyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@envelop/types": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@envelop/types/-/types-4.0.1.tgz", + "integrity": "sha512-ULo27/doEsP7uUhm2iTnElx13qTO6I5FKvmLoX41cpfuw8x6e0NUFknoqhEsLzAbgz8xVS5mjwcxGCXh4lDYzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/compat": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.4.1.tgz", + "integrity": "sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.40 || 9" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.0.tgz", + "integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@graphql-codegen/add": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-6.0.0.tgz", + "integrity": "sha512-biFdaURX0KTwEJPQ1wkT6BRgNasqgQ5KbCI1a3zwtLtO7XTo7/vKITPylmiU27K5DSOWYnY/1jfSqUAEBuhZrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^6.0.0", + "tslib": "~2.6.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/add/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@graphql-codegen/cli": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-6.1.0.tgz", + "integrity": "sha512-7w3Zq5IFONVOBcyOiP01Nv9WRxGS/TEaBCAb/ALYA3xHq95dqKCpoGnxt/Ut9R18jiS+aMgT0gc8Tr8sHy44jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.18.13", + "@babel/template": "^7.18.10", + "@babel/types": "^7.18.13", + "@graphql-codegen/client-preset": "^5.2.0", + "@graphql-codegen/core": "^5.0.0", + "@graphql-codegen/plugin-helpers": "^6.1.0", + "@graphql-tools/apollo-engine-loader": "^8.0.0", + "@graphql-tools/code-file-loader": "^8.0.0", + "@graphql-tools/git-loader": "^8.0.0", + "@graphql-tools/github-loader": "^9.0.0", + "@graphql-tools/graphql-file-loader": "^8.0.0", + "@graphql-tools/json-file-loader": "^8.0.0", + "@graphql-tools/load": "^8.1.0", + "@graphql-tools/url-loader": "^9.0.0", + "@graphql-tools/utils": "^10.0.0", + "@inquirer/prompts": "^7.8.2", + "@whatwg-node/fetch": "^0.10.0", + "chalk": "^4.1.0", + "cosmiconfig": "^9.0.0", + "debounce": "^2.0.0", + "detect-indent": "^6.0.0", + "graphql-config": "^5.1.1", + "is-glob": "^4.0.1", + "jiti": "^2.3.0", + "json-to-pretty-yaml": "^1.2.2", + "listr2": "^9.0.0", + "log-symbols": "^4.0.0", + "micromatch": "^4.0.5", + "shell-quote": "^1.7.3", + "string-env-interpolation": "^1.0.1", + "ts-log": "^2.2.3", + "tslib": "^2.4.0", + "yaml": "^2.3.1", + "yargs": "^17.0.0" + }, + "bin": { + "gql-gen": "cjs/bin.js", + "graphql-code-generator": "cjs/bin.js", + "graphql-codegen": "cjs/bin.js", + "graphql-codegen-esm": "esm/bin.js" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@parcel/watcher": "^2.1.0", + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + }, + "peerDependenciesMeta": { + "@parcel/watcher": { + "optional": true + } + } + }, + "node_modules/@graphql-codegen/client-preset": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-5.2.1.tgz", + "integrity": "sha512-6qFjHQQUWrEH+MVvWs5sPUgme8X+Ivg3WfzaCESooRBQZ4/EnSFlXkPWUTbOKYLRUoMv4g6iTRcZQf6u1wtHZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/template": "^7.20.7", + "@graphql-codegen/add": "^6.0.0", + "@graphql-codegen/gql-tag-operations": "5.1.1", + "@graphql-codegen/plugin-helpers": "^6.1.0", + "@graphql-codegen/typed-document-node": "^6.1.4", + "@graphql-codegen/typescript": "^5.0.6", + "@graphql-codegen/typescript-operations": "^5.0.6", + "@graphql-codegen/visitor-plugin-common": "^6.2.1", + "@graphql-tools/documents": "^1.0.0", + "@graphql-tools/utils": "^10.0.0", + "@graphql-typed-document-node/core": "3.2.0", + "tslib": "~2.6.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", + "graphql-sock": "^1.0.0" + }, + "peerDependenciesMeta": { + "graphql-sock": { + "optional": true + } + } + }, + "node_modules/@graphql-codegen/client-preset/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@graphql-codegen/core": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/core/-/core-5.0.0.tgz", + "integrity": "sha512-vLTEW0m8LbE4xgRwbFwCdYxVkJ1dBlVJbQyLb9Q7bHnVFgHAP982Xo8Uv7FuPBmON+2IbTjkCqhFLHVZbqpvjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^6.0.0", + "@graphql-tools/schema": "^10.0.0", + "@graphql-tools/utils": "^10.0.0", + "tslib": "~2.6.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/core/node_modules/@graphql-tools/schema": { + "version": "10.0.30", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.30.tgz", + "integrity": "sha512-yPXU17uM/LR90t92yYQqn9mAJNOVZJc0nQtYeZyZeQZeQjwIGlTubvvoDL0fFVk+wZzs4YQOgds2NwSA4npodA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.6", + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/core/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@graphql-codegen/gql-tag-operations": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-5.1.1.tgz", + "integrity": "sha512-XewD0XxN2sgKieEIFeGWV5yT5X2aNy+eg+K8bHlUD7QfyrN2bi67rv/O5Edu7LVDOJR69uqVBp++18d742mn3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^6.1.0", + "@graphql-codegen/visitor-plugin-common": "6.2.1", + "@graphql-tools/utils": "^10.0.0", + "auto-bind": "~4.0.0", + "tslib": "~2.6.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/gql-tag-operations/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@graphql-codegen/plugin-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-6.1.0.tgz", + "integrity": "sha512-JJypehWTcty9kxKiqH7TQOetkGdOYjY78RHlI+23qB59cV2wxjFFVf8l7kmuXS4cpGVUNfIjFhVr7A1W7JMtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.0.0", + "change-case-all": "1.0.15", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "lodash": "~4.17.0", + "tslib": "~2.6.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/plugin-helpers/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@graphql-codegen/schema-ast": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/schema-ast/-/schema-ast-5.0.0.tgz", + "integrity": "sha512-jn7Q3PKQc0FxXjbpo9trxzlz/GSFQWxL042l0iC8iSbM/Ar+M7uyBwMtXPsev/3Razk+osQyreghIz0d2+6F7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^6.0.0", + "@graphql-tools/utils": "^10.0.0", + "tslib": "~2.6.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/schema-ast/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@graphql-codegen/typed-document-node": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-6.1.4.tgz", + "integrity": "sha512-ITWsA+qvT7R64z7KmYHXfgyD5ff069FAGq/hpR0EWVfzXT4RW1Xn/3Biw7/jvwMGsS1BTjo8ZLSIMNM8KjE3GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^6.1.0", + "@graphql-codegen/visitor-plugin-common": "6.2.1", + "auto-bind": "~4.0.0", + "change-case-all": "1.0.15", + "tslib": "~2.6.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/typed-document-node/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@graphql-codegen/typescript": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-5.0.6.tgz", + "integrity": "sha512-rKW3wYInAnmO/DmKjhW3/KLMxUauUCZuMEPQmuoHChnwIuMjn5kVXCdArGyQqv+vVtFj55aS+sJLN4MPNNjSNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^6.1.0", + "@graphql-codegen/schema-ast": "^5.0.0", + "@graphql-codegen/visitor-plugin-common": "6.2.1", + "auto-bind": "~4.0.0", + "tslib": "~2.6.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-operations": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-5.0.6.tgz", + "integrity": "sha512-pkR/82qWO50OHWeV3BiDuVxNFxiJerpmNjFep71VlabADXiU3GIeSaDd6G9a1/SCniVTXZQk2ivCb0ZJiuwo1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^6.1.0", + "@graphql-codegen/typescript": "^5.0.6", + "@graphql-codegen/visitor-plugin-common": "6.2.1", + "auto-bind": "~4.0.0", + "tslib": "~2.6.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", + "graphql-sock": "^1.0.0" + }, + "peerDependenciesMeta": { + "graphql-sock": { + "optional": true + } + } + }, + "node_modules/@graphql-codegen/typescript-operations/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@graphql-codegen/typescript-urql": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-urql/-/typescript-urql-4.0.1.tgz", + "integrity": "sha512-rZWoN9jUQT40lwSBd6B/HGF2kq15bF34WVz/H+EuEBcHSdkTkZFtFb+lXgCZLGssPKDkPyr12fmHgbzkdIvPdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^3.0.0", + "@graphql-codegen/visitor-plugin-common": "2.13.8", + "auto-bind": "~4.0.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", + "graphql-tag": "^2.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/@ardatan/relay-compiler": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@ardatan/relay-compiler/-/relay-compiler-12.0.0.tgz", + "integrity": "sha512-9anThAaj1dQr6IGmzBMcfzOQKTa5artjuPmw8NYK/fiGEMjADbSguBY2FMDykt+QhilR3wc9VA/3yVju7JHg7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.14.0", + "@babel/generator": "^7.14.0", + "@babel/parser": "^7.14.0", + "@babel/runtime": "^7.0.0", + "@babel/traverse": "^7.14.0", + "@babel/types": "^7.0.0", + "babel-preset-fbjs": "^3.4.0", + "chalk": "^4.0.0", + "fb-watchman": "^2.0.0", + "fbjs": "^3.0.0", + "glob": "^7.1.1", + "immutable": "~3.7.6", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "relay-runtime": "12.0.0", + "signedsource": "^1.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "relay-compiler": "bin/relay-compiler" + }, + "peerDependencies": { + "graphql": "*" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/@graphql-codegen/plugin-helpers": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-3.1.2.tgz", + "integrity": "sha512-emOQiHyIliVOIjKVKdsI5MXj312zmRDwmHpyUTZMjfpvxq/UVAHUJIVdVf+lnjjrI+LXBTgMlTWTgHQfmICxjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^9.0.0", + "change-case-all": "1.0.15", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "lodash": "~4.17.0", + "tslib": "~2.4.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/@graphql-codegen/plugin-helpers/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/@graphql-codegen/visitor-plugin-common": { + "version": "2.13.8", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.13.8.tgz", + "integrity": "sha512-IQWu99YV4wt8hGxIbBQPtqRuaWZhkQRG2IZKbMoSvh0vGeWb3dB0n0hSgKaOOxDY+tljtOf9MTcUYvJslQucMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^3.1.2", + "@graphql-tools/optimize": "^1.3.0", + "@graphql-tools/relay-operation-optimizer": "^6.5.0", + "@graphql-tools/utils": "^9.0.0", + "auto-bind": "~4.0.0", + "change-case-all": "1.0.15", + "dependency-graph": "^0.11.0", + "graphql-tag": "^2.11.0", + "parse-filepath": "^1.0.2", + "tslib": "~2.4.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/@graphql-tools/optimize": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/optimize/-/optimize-1.4.0.tgz", + "integrity": "sha512-dJs/2XvZp+wgHH8T5J2TqptT9/6uVzIYvA6uFACha+ufvdMBedkfR4b4GbT8jAKLRARiqRTxy3dctnwkTM2tdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/@graphql-tools/relay-operation-optimizer": { + "version": "6.5.18", + "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.5.18.tgz", + "integrity": "sha512-mc5VPyTeV+LwiM+DNvoDQfPqwQYhPV/cl5jOBjTgSniyaq8/86aODfMkrE2OduhQ5E00hqrkuL2Fdrgk0w1QJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ardatan/relay-compiler": "12.0.0", + "@graphql-tools/utils": "^9.2.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/@graphql-tools/utils": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", + "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-codegen/typescript-urql/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@graphql-codegen/typescript/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@graphql-codegen/visitor-plugin-common": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-6.2.1.tgz", + "integrity": "sha512-5QT1hCV3286mrmoIC7vlFXsTlwELMexhuFIkjh+oVGGL1E8hxkIPAU0kfH/lsPbQHKi8zKmic2pl3tAdyYxNyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^6.1.0", + "@graphql-tools/optimize": "^2.0.0", + "@graphql-tools/relay-operation-optimizer": "^7.0.0", + "@graphql-tools/utils": "^10.0.0", + "auto-bind": "~4.0.0", + "change-case-all": "1.0.15", + "dependency-graph": "^1.0.0", + "graphql-tag": "^2.11.0", + "parse-filepath": "^1.0.2", + "tslib": "~2.6.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/visitor-plugin-common/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@graphql-hive/signal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@graphql-hive/signal/-/signal-2.0.0.tgz", + "integrity": "sha512-Pz8wB3K0iU6ae9S1fWfsmJX24CcGeTo6hE7T44ucmV/ALKRj+bxClmqrYcDT7v3f0d12Rh4FAXBb6gon+WkDpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@graphql-tools/apollo-engine-loader": { + "version": "8.0.27", + "resolved": "https://registry.npmjs.org/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-8.0.27.tgz", + "integrity": "sha512-XT4BvqmRXkVaT8GgNb9/pr8u4M4vTcvGuI2GlvK+albrJNIV8VxTpsdVYma3kw+VtSIYrxEvLixlfDA/KdmDpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.11.0", + "@whatwg-node/fetch": "^0.10.13", + "sync-fetch": "0.6.0-2", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/batch-execute": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-10.0.4.tgz", + "integrity": "sha512-t8E0ILelbaIju0aNujMkKetUmbv3/07nxGSv0kEGLBk9GNtEmQ/Bjj8ZTo2WN35/Fy70zCHz2F/48Nx/Ec48cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.10.3", + "@whatwg-node/promise-helpers": "^1.3.2", + "dataloader": "^2.2.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/code-file-loader": { + "version": "8.1.27", + "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-8.1.27.tgz", + "integrity": "sha512-q3GDbm+7m3DiAnqxa+lYMgYZd49+ez6iGFfXHmzP6qAnf5WlBxRNKNjNVuxOgoV30DCr+vOJfoXeU7VN1qqGWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/graphql-tag-pluck": "8.3.26", + "@graphql-tools/utils": "^10.11.0", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/delegate": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-12.0.2.tgz", + "integrity": "sha512-1X93onxNgOzRvnZ8Xulwi6gNuBeuDxvGYOjUHEZyesPCsaWsyiVj1Wk6Pw/DTPGLy70sOFUKQGcaZbWnDORM2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/batch-execute": "^10.0.4", + "@graphql-tools/executor": "^1.4.13", + "@graphql-tools/schema": "^10.0.29", + "@graphql-tools/utils": "^10.10.3", + "@repeaterjs/repeater": "^3.0.6", + "@whatwg-node/promise-helpers": "^1.3.2", + "dataloader": "^2.2.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/delegate/node_modules/@graphql-tools/schema": { + "version": "10.0.30", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.30.tgz", + "integrity": "sha512-yPXU17uM/LR90t92yYQqn9mAJNOVZJc0nQtYeZyZeQZeQjwIGlTubvvoDL0fFVk+wZzs4YQOgds2NwSA4npodA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.6", + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/documents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/documents/-/documents-1.0.1.tgz", + "integrity": "sha512-aweoMH15wNJ8g7b2r4C4WRuJxZ0ca8HtNO54rkye/3duxTkW4fGBEutCx03jCIr5+a1l+4vFJNP859QnAVBVCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.4.13.tgz", + "integrity": "sha512-2hTSRfH2kb4ua0ANOV/K6xUoCZsHAE6igE1bimtWUK7v0bowPIxGRKRPpF8JLbImpsJuTCC4HGOCMy7otg3FIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.10.3", + "@graphql-typed-document-node/core": "^3.2.0", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor-common": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-common/-/executor-common-1.0.5.tgz", + "integrity": "sha512-gsBRxP4ui8s7/ppKGCJUQ9xxTNoFpNYmEirgM52EHo74hL5hrpS5o4zOmBH33+9t2ZasBziIfupYtLNa0DgK0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/core": "^5.4.0", + "@graphql-tools/utils": "^10.10.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor-common/node_modules/@envelop/core": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@envelop/core/-/core-5.4.0.tgz", + "integrity": "sha512-/1fat63pySE8rw/dZZArEVytLD90JApY85deDJ0/34gm+yhQ3k70CloSUevxoOE4YCGveG3s9SJJfQeeB4NAtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/instrumentation": "^1.0.0", + "@envelop/types": "^5.2.1", + "@whatwg-node/promise-helpers": "^1.2.4", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@graphql-tools/executor-common/node_modules/@envelop/types": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@envelop/types/-/types-5.2.1.tgz", + "integrity": "sha512-CsFmA3u3c2QoLDTfEpGr4t25fjMU31nyvse7IzWTvb0ZycuPjMjb0fjlheh+PbhBYb9YLugnT2uY6Mwcg1o+Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@graphql-tools/executor-graphql-ws": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-3.1.3.tgz", + "integrity": "sha512-q4k8KLoH2U51XdWJRdiW/KIKbBOtJ1mcILv0ALvBkOF99C3vwGj2zr4U0AMGCD3HzML2mPZuajhfYo/xB/pnZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/executor-common": "^1.0.5", + "@graphql-tools/utils": "^10.10.3", + "@whatwg-node/disposablestack": "^0.0.6", + "graphql-ws": "^6.0.6", + "isows": "^1.0.7", + "tslib": "^2.8.1", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor-http": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-http/-/executor-http-3.0.7.tgz", + "integrity": "sha512-sHjtiUZmRtkjhpSzMhxT2ywAGzHjuB1rHsiaSLAq8U5BQg5WoLakKYD7BajgVHwNbfWEc+NnFiJI7ldyhiciiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-hive/signal": "^2.0.0", + "@graphql-tools/executor-common": "^1.0.5", + "@graphql-tools/utils": "^10.10.3", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/fetch": "^0.10.13", + "@whatwg-node/promise-helpers": "^1.3.2", + "meros": "^1.3.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor-legacy-ws": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-1.1.24.tgz", + "integrity": "sha512-wfSpOJCxeBcwVXy3JS4TB4oLwVICuVKPlPQhcAjTRPWYwKerE0HosgUzxCX1fEQ4l1B1OMgKWRglGpoXExKqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.11.0", + "@types/ws": "^8.0.0", + "isomorphic-ws": "^5.0.0", + "tslib": "^2.4.0", + "ws": "^8.17.1" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/git-loader": { + "version": "8.0.31", + "resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-8.0.31.tgz", + "integrity": "sha512-xVHM1JecjpU2P0aOj/IaIUc3w6It8sWOdrJElWFZdY9yfWRqXFYwfemtsn/JOrJDIJXYeGpJ304OeqJD5vFIEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/graphql-tag-pluck": "8.3.26", + "@graphql-tools/utils": "^10.11.0", + "is-glob": "4.0.3", + "micromatch": "^4.0.8", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/github-loader": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-9.0.5.tgz", + "integrity": "sha512-89FRDQGMlzL3607BCQtJhKEiQaZtTmdAnyC5Hmi9giTQXVzEXBbMEZOU0qILxj64cr+smNBx5XqxQ1xn0uZeEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/executor-http": "^3.0.6", + "@graphql-tools/graphql-tag-pluck": "^8.3.26", + "@graphql-tools/utils": "^10.11.0", + "@whatwg-node/fetch": "^0.10.13", + "@whatwg-node/promise-helpers": "^1.0.0", + "sync-fetch": "0.6.0-2", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/graphql-file-loader": { + "version": "8.1.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-8.1.8.tgz", + "integrity": "sha512-dZi9Cw+NWEzJAqzIUON9qjZfjebjcoT4H6jqLkEoAv6kRtTq52m4BLXgFWjMHU7PNLE9OOHB9St7UeZQL+GYrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/import": "7.1.8", + "@graphql-tools/utils": "^10.11.0", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/graphql-tag-pluck": { + "version": "8.3.26", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.26.tgz", + "integrity": "sha512-hLsX++KA3YR/PnNJGBq1weSAY8XUUAQFfOSHanLHA2qs5lcNgU6KWbiLiRsJ/B/ZNi2ZO687dhzeZ4h4Yt0V6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/import": { + "version": "7.1.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-7.1.8.tgz", + "integrity": "sha512-aUKHMbaeHhCkS867mNCk9sJuvd9xE3Ocr+alwdvILkDxHf7Xaumx4mK8tN9FAXeKhQWGGD5QpkIBnUzt2xoX/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.11.0", + "@theguild/federation-composition": "^0.21.0", + "resolve-from": "5.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/import/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-tools/json-file-loader": { + "version": "8.0.25", + "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-8.0.25.tgz", + "integrity": "sha512-Dnr9z818Kdn3rfoZO/+/ZQUqWavjV7AhEp4edV1mGsX+J1HFkNC3WMl6MD3W0hth2HWLQpCFJDdOPnchxnFNfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.11.0", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/load": { + "version": "8.1.7", + "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-8.1.7.tgz", + "integrity": "sha512-RxrHOC4vVI50+Q1mwgpmTVCB/UDDYVEGD/g/hP3tT2BW9F3rJ7Z3Lmt/nGfPQuWPao3w6vgJ9oSAWtism7CU5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/schema": "^10.0.30", + "@graphql-tools/utils": "^10.11.0", + "p-limit": "3.1.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/load/node_modules/@graphql-tools/schema": { + "version": "10.0.30", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.30.tgz", + "integrity": "sha512-yPXU17uM/LR90t92yYQqn9mAJNOVZJc0nQtYeZyZeQZeQjwIGlTubvvoDL0fFVk+wZzs4YQOgds2NwSA4npodA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.6", + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/merge": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.1.6.tgz", + "integrity": "sha512-bTnP+4oom4nDjmkS3Ykbe+ljAp/RIiWP3R35COMmuucS24iQxGLa9Hn8VMkLIoaoPxgz6xk+dbC43jtkNsFoBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/optimize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/optimize/-/optimize-2.0.0.tgz", + "integrity": "sha512-nhdT+CRGDZ+bk68ic+Jw1OZ99YCDIKYA5AlVAnBHJvMawSx9YQqQAIj4refNc1/LRieGiuWvhbG3jvPVYho0Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/relay-operation-optimizer": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.26.tgz", + "integrity": "sha512-cVdS2Hw4hg/WgPVV2wRIzZM975pW5k4vdih3hR4SvEDQVr6MmozmlTQSqzMyi9yg8LKTq540Oz3bYQa286yGmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ardatan/relay-compiler": "^12.0.3", + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema": { + "version": "9.0.19", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz", + "integrity": "sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^8.4.1", + "@graphql-tools/utils": "^9.2.1", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema/node_modules/@graphql-tools/merge": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz", + "integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^9.2.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema/node_modules/@graphql-tools/utils": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", + "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/url-loader": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-9.0.5.tgz", + "integrity": "sha512-EPNhZBBL48TudLdyenOw1wV9dI7vsinWLLxSTtkx4zUQxmU+p/LxMyf7MUwjmp3yFZhR/9XchsTZX6uvOyXWqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/executor-graphql-ws": "^3.1.2", + "@graphql-tools/executor-http": "^3.0.6", + "@graphql-tools/executor-legacy-ws": "^1.1.24", + "@graphql-tools/utils": "^10.11.0", + "@graphql-tools/wrap": "^11.0.0", + "@types/ws": "^8.0.0", + "@whatwg-node/fetch": "^0.10.13", + "@whatwg-node/promise-helpers": "^1.0.0", + "isomorphic-ws": "^5.0.0", + "sync-fetch": "0.6.0-2", + "tslib": "^2.4.0", + "ws": "^8.17.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/utils": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.11.0.tgz", + "integrity": "sha512-iBFR9GXIs0gCD+yc3hoNswViL1O5josI33dUqiNStFI/MHLCEPduasceAcazRH77YONKNiviHBV8f7OgcT4o2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/wrap": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-11.1.2.tgz", + "integrity": "sha512-TcKZzUzJNmuyMBQ1oMdnxhBUUacN/5VEJu0/1KVce2aIzCwTTaN9JTU3MgjO7l5Ixn4QLkc6XbxYNv0cHDQgtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/delegate": "^12.0.2", + "@graphql-tools/schema": "^10.0.29", + "@graphql-tools/utils": "^10.10.3", + "@whatwg-node/promise-helpers": "^1.3.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/wrap/node_modules/@graphql-tools/schema": { + "version": "10.0.30", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.30.tgz", + "integrity": "sha512-yPXU17uM/LR90t92yYQqn9mAJNOVZJc0nQtYeZyZeQZeQjwIGlTubvvoDL0fFVk+wZzs4YQOgds2NwSA4npodA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.6", + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-yoga/logger": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/logger/-/logger-1.0.0.tgz", + "integrity": "sha512-JYoxwnPggH2BfO+dWlWZkDeFhyFZqaTRGLvFhy+Pjp2UxitEW6nDrw+pEDw/K9tJwMjIFMmTT9VfTqrnESmBHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.5.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-yoga/subscription": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/subscription/-/subscription-4.0.0.tgz", + "integrity": "sha512-0qsN/BPPZNMoC2CZ8i+P6PgiJyHh1H35aKDt37qARBDaIOKDQuvEOq7+4txUKElcmXi7DYFo109FkhSQoEajrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-yoga/typed-event-target": "^2.0.0", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/events": "^0.1.0", + "tslib": "^2.5.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-yoga/typed-event-target": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/typed-event-target/-/typed-event-target-2.0.0.tgz", + "integrity": "sha512-oA/VGxGmaSDym1glOHrltw43qZsFwLLjBwvh57B79UKX/vo3+UQcRgOyE44c5RP7DCYjkrC2tuArZmb6jCzysw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@repeaterjs/repeater": "^3.0.4", + "tslib": "^2.5.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "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/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@inquirer/core/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kamilkisiela/fast-url-parser": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@kamilkisiela/fast-url-parser/-/fast-url-parser-1.1.4.tgz", + "integrity": "sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==", + "dev": true, + "license": "MIT" + }, + "node_modules/@kitql/helpers": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@kitql/helpers/-/helpers-0.8.13.tgz", + "integrity": "sha512-8Om4jxF3XLTrpCkrZ8q6kHco4o6u/zKisA7XjLdiUDeSX7VSprlq6fnG2xayrbrc1He+pHQkgj2cHqqUvEHTGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esm-env": "^1.2.2" + }, + "engines": { + "node": "^16.14 || >=18" + }, + "funding": { + "url": "https://github.com/sponsors/jycouet" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@repeaterjs/repeater": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.6.tgz", + "integrity": "sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.9", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.9.tgz", + "integrity": "sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.7.tgz", + "integrity": "sha512-znp1A/Y1Jj4l/Zy7PX5DZKBE0ZNY+5QBngiE21NJkfSTyzzC5iKNWOtwFXKtIrn7MXEFBck4jD95iBNkGjK92Q==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.4.0.tgz", + "integrity": "sha512-NMsrwGVPEn+J73zH83Uhss/hYYZN6zT3u31R3IHAn3MiKC3h8fjmIAhLfTSOeNHr5wPYfjjMg8E+1gyFgyrEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "rollup": "^4.9.5" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.4.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.49.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.49.0.tgz", + "integrity": "sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/cookie": "^0.6.0", + "acorn": "^8.14.1", + "cookie": "^0.6.0", + "devalue": "^5.3.2", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz", + "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "magic-string": "^0.30.17", + "vitefu": "^1.1.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", + "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", + "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", + "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.17" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz", + "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.17", + "@tailwindcss/oxide-darwin-arm64": "4.1.17", + "@tailwindcss/oxide-darwin-x64": "4.1.17", + "@tailwindcss/oxide-freebsd-x64": "4.1.17", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", + "@tailwindcss/oxide-linux-x64-musl": "4.1.17", + "@tailwindcss/oxide-wasm32-wasi": "4.1.17", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz", + "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz", + "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz", + "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz", + "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz", + "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz", + "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz", + "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz", + "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz", + "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz", + "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.6.0", + "@emnapi/runtime": "^1.6.0", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.0.7", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz", + "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz", + "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.17.tgz", + "integrity": "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.17", + "@tailwindcss/oxide": "4.1.17", + "tailwindcss": "4.1.17" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@theguild/federation-composition": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@theguild/federation-composition/-/federation-composition-0.21.0.tgz", + "integrity": "sha512-cdQ9rDEtBpT553DLLtcsSjtSDIadibIxAD3K5r0eUuDOfxx+es7Uk+aOucfqMlNOM3eybsgJN3T2SQmEsINwmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "constant-case": "^3.0.4", + "debug": "4.4.3", + "json5": "^2.2.3", + "lodash.sortby": "^4.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "graphql": "^16.0.0" + } + }, + "node_modules/@types/braces": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.5.tgz", + "integrity": "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/micromatch": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.10.tgz", + "integrity": "sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/braces": "*" + } + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz", + "integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/type-utils": "8.48.0", + "@typescript-eslint/utils": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.48.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz", + "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz", + "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.48.0", + "@typescript-eslint/types": "^8.48.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz", + "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz", + "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz", + "integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/utils": "8.48.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz", + "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz", + "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.48.0", + "@typescript-eslint/tsconfig-utils": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz", + "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz", + "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@urql/core": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@urql/core/-/core-6.0.1.tgz", + "integrity": "sha512-FZDiQk6jxbj5hixf2rEPv0jI+IZz0EqqGW8mJBEug68/zHTtT+f34guZDmyjJZyiWbj0vL165LoMr/TkeDHaug==", + "license": "MIT", + "dependencies": { + "@0no-co/graphql.web": "^1.0.13", + "wonka": "^6.3.2" + } + }, + "node_modules/@urql/svelte": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@urql/svelte/-/svelte-5.0.0.tgz", + "integrity": "sha512-tHYEyFZwWsBW9GfpXbK+GImWhyZO1TJkhHsquosza0D0qOZyL+wGp/qT74WPUBJaF4gkUSXOQtUidDI7uvnuoQ==", + "license": "MIT", + "dependencies": { + "@urql/core": "^6.0.0", + "wonka": "^6.3.2" + }, + "peerDependencies": { + "@urql/core": "^6.0.0", + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/@whatwg-node/disposablestack": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@whatwg-node/disposablestack/-/disposablestack-0.0.6.tgz", + "integrity": "sha512-LOtTn+JgJvX8WfBVJtF08TGrdjuFzGJc4mkP8EdDI8ADbvO7kiexYep1o8dwnt0okb0jYclCDXF13xU7Ge4zSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/events": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.2.tgz", + "integrity": "sha512-ApcWxkrs1WmEMS2CaLLFUEem/49erT3sxIVjpzU5f6zmVcnijtDSrhoK2zVobOIikZJdH63jdAXOrvjf6eOUNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/fetch": { + "version": "0.10.13", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.13.tgz", + "integrity": "sha512-b4PhJ+zYj4357zwk4TTuF2nEe0vVtOrwdsrNo5hL+u1ojXNhh1FgJ6pg1jzDlwlT4oBdzfSwaBwMCtFCsIWg8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/node-fetch": "^0.8.3", + "urlpattern-polyfill": "^10.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/node-fetch": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.8.4.tgz", + "integrity": "sha512-AlKLc57loGoyYlrzDbejB9EeR+pfdJdGzbYnkEuZaGekFboBwzfVYVMsy88PMriqPI1ORpiGYGgSSWpx7a2sDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^3.1.1", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/promise-helpers": "^1.3.2", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/promise-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz", + "integrity": "sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@whatwg-node/server": { + "version": "0.10.17", + "resolved": "https://registry.npmjs.org/@whatwg-node/server/-/server-0.10.17.tgz", + "integrity": "sha512-QxI+HQfJeI/UscFNCTcSri6nrHP25mtyAMbhEri7W2ctdb3EsorPuJz7IovSgNjvKVs73dg9Fmayewx1O2xOxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/instrumentation": "^1.0.0", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/fetch": "^0.10.13", + "@whatwg-node/promise-helpers": "^1.3.2", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/auto-bind": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", + "integrity": "sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-plugin-syntax-trailing-function-commas": { + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", + "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-preset-fbjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", + "integrity": "sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-syntax-class-properties": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-block-scoped-functions": "^7.0.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.0.0", + "@babel/plugin-transform-flow-strip-types": "^7.0.0", + "@babel/plugin-transform-for-of": "^7.0.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-member-expression-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-property-literals": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-template-literals": "^7.0.0", + "babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.3.tgz", + "integrity": "sha512-8QdH6czo+G7uBsNo0GiUfouPN1lRzKdJTGnKXwe12gkFbnnOUaUKGN55dMkfy+mnxmvjwl9zcI4VncczcVXDhA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/builtins": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001759", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", + "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/change-case-all": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.15.tgz", + "integrity": "sha512-3+GIFhk3sNuvFAJKU46o26OdzudQlPNBCu1ZQi3cMeMHhty1bhDxu2WrEilVNYaGvqUtR1VSigFcJOiS13dRhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "change-case": "^4.1.2", + "is-lower-case": "^2.0.2", + "is-upper-case": "^2.0.2", + "lower-case": "^2.0.2", + "lower-case-first": "^2.0.2", + "sponge-case": "^1.0.1", + "swap-case": "^2.0.2", + "title-case": "^3.0.3", + "upper-case": "^2.0.2", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/cross-inspect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.1.tgz", + "integrity": "sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/dataloader": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", + "integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debounce": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", + "integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.5.0.tgz", + "integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==", + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.266", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz", + "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.13.0.tgz", + "integrity": "sha512-2ohCCQJJTNbIpQCSDSTWj+FN0OVfPmSO03lmSNT7ytqMaWF6kpT86LdzDqtm4sh7TVPl/OEWJ/d7R87bXP2Vjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.6.1", + "@jridgewell/sourcemap-codec": "^1.5.0", + "esutils": "^2.0.3", + "globals": "^16.0.0", + "known-css-properties": "^0.37.0", + "postcss": "^8.4.49", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^7.0.0", + "semver": "^7.6.3", + "svelte-eslint-parser": "^1.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^8.57.1 || ^9.0.0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.0.tgz", + "integrity": "sha512-WBmtxe7R9C5mvL4n2le8nMUe4mD5V9oiK2vJpQ9I3y20ENPUomPcphBXE8D1x/Bm84oN1V+lOfgXxtqmxTp3Xg==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fbjs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-fetch": "^3.1.5", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^1.0.35" + } + }, + "node_modules/fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/graphql": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", + "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-config": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-5.1.5.tgz", + "integrity": "sha512-mG2LL1HccpU8qg5ajLROgdsBzx/o2M6kgI3uAmoaXiSH9PCUbtIyLomLqUtCFaAeG2YCFsl0M5cfQ9rKmDoMVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/graphql-file-loader": "^8.0.0", + "@graphql-tools/json-file-loader": "^8.0.0", + "@graphql-tools/load": "^8.1.0", + "@graphql-tools/merge": "^9.0.0", + "@graphql-tools/url-loader": "^8.0.0", + "@graphql-tools/utils": "^10.0.0", + "cosmiconfig": "^8.1.0", + "jiti": "^2.0.0", + "minimatch": "^9.0.5", + "string-env-interpolation": "^1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "cosmiconfig-toml-loader": "^1.0.0", + "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + }, + "peerDependenciesMeta": { + "cosmiconfig-toml-loader": { + "optional": true + } + } + }, + "node_modules/graphql-config/node_modules/@envelop/core": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@envelop/core/-/core-5.4.0.tgz", + "integrity": "sha512-/1fat63pySE8rw/dZZArEVytLD90JApY85deDJ0/34gm+yhQ3k70CloSUevxoOE4YCGveG3s9SJJfQeeB4NAtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/instrumentation": "^1.0.0", + "@envelop/types": "^5.2.1", + "@whatwg-node/promise-helpers": "^1.2.4", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/graphql-config/node_modules/@envelop/types": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@envelop/types/-/types-5.2.1.tgz", + "integrity": "sha512-CsFmA3u3c2QoLDTfEpGr4t25fjMU31nyvse7IzWTvb0ZycuPjMjb0fjlheh+PbhBYb9YLugnT2uY6Mwcg1o+Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/graphql-config/node_modules/@graphql-hive/signal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@graphql-hive/signal/-/signal-1.0.0.tgz", + "integrity": "sha512-RiwLMc89lTjvyLEivZ/qxAC5nBHoS2CtsWFSOsN35sxG9zoo5Z+JsFHM8MlvmO9yt+MJNIyC5MLE1rsbOphlag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/graphql-config/node_modules/@graphql-tools/batch-execute": { + "version": "9.0.19", + "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-9.0.19.tgz", + "integrity": "sha512-VGamgY4PLzSx48IHPoblRw0oTaBa7S26RpZXt0Y4NN90ytoE0LutlpB2484RbkfcTjv9wa64QD474+YP1kEgGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.9.1", + "@whatwg-node/promise-helpers": "^1.3.0", + "dataloader": "^2.2.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-config/node_modules/@graphql-tools/delegate": { + "version": "10.2.23", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-10.2.23.tgz", + "integrity": "sha512-xrPtl7f1LxS+B6o+W7ueuQh67CwRkfl+UKJncaslnqYdkxKmNBB4wnzVcW8ZsRdwbsla/v43PtwAvSlzxCzq2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/batch-execute": "^9.0.19", + "@graphql-tools/executor": "^1.4.9", + "@graphql-tools/schema": "^10.0.25", + "@graphql-tools/utils": "^10.9.1", + "@repeaterjs/repeater": "^3.0.6", + "@whatwg-node/promise-helpers": "^1.3.0", + "dataloader": "^2.2.3", + "dset": "^3.1.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-config/node_modules/@graphql-tools/executor-common": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-common/-/executor-common-0.0.6.tgz", + "integrity": "sha512-JAH/R1zf77CSkpYATIJw+eOJwsbWocdDjY+avY7G+P5HCXxwQjAjWVkJI1QJBQYjPQDVxwf1fmTZlIN3VOadow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/core": "^5.3.0", + "@graphql-tools/utils": "^10.9.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-config/node_modules/@graphql-tools/executor-graphql-ws": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-2.0.7.tgz", + "integrity": "sha512-J27za7sKF6RjhmvSOwOQFeNhNHyP4f4niqPnerJmq73OtLx9Y2PGOhkXOEB0PjhvPJceuttkD2O1yMgEkTGs3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/executor-common": "^0.0.6", + "@graphql-tools/utils": "^10.9.1", + "@whatwg-node/disposablestack": "^0.0.6", + "graphql-ws": "^6.0.6", + "isomorphic-ws": "^5.0.0", + "tslib": "^2.8.1", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-config/node_modules/@graphql-tools/executor-http": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-http/-/executor-http-1.3.3.tgz", + "integrity": "sha512-LIy+l08/Ivl8f8sMiHW2ebyck59JzyzO/yF9SFS4NH6MJZUezA1xThUXCDIKhHiD56h/gPojbkpcFvM2CbNE7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-hive/signal": "^1.0.0", + "@graphql-tools/executor-common": "^0.0.4", + "@graphql-tools/utils": "^10.8.1", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/fetch": "^0.10.4", + "@whatwg-node/promise-helpers": "^1.3.0", + "meros": "^1.2.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-config/node_modules/@graphql-tools/executor-http/node_modules/@graphql-tools/executor-common": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-common/-/executor-common-0.0.4.tgz", + "integrity": "sha512-SEH/OWR+sHbknqZyROCFHcRrbZeUAyjCsgpVWCRjqjqRbiJiXq6TxNIIOmpXgkrXWW/2Ev4Wms6YSGJXjdCs6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/core": "^5.2.3", + "@graphql-tools/utils": "^10.8.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-config/node_modules/@graphql-tools/schema": { + "version": "10.0.30", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.30.tgz", + "integrity": "sha512-yPXU17uM/LR90t92yYQqn9mAJNOVZJc0nQtYeZyZeQZeQjwIGlTubvvoDL0fFVk+wZzs4YQOgds2NwSA4npodA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.6", + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-config/node_modules/@graphql-tools/url-loader": { + "version": "8.0.33", + "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-8.0.33.tgz", + "integrity": "sha512-Fu626qcNHcqAj8uYd7QRarcJn5XZ863kmxsg1sm0fyjyfBJnsvC7ddFt6Hayz5kxVKfsnjxiDfPMXanvsQVBKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/executor-graphql-ws": "^2.0.1", + "@graphql-tools/executor-http": "^1.1.9", + "@graphql-tools/executor-legacy-ws": "^1.1.19", + "@graphql-tools/utils": "^10.9.1", + "@graphql-tools/wrap": "^10.0.16", + "@types/ws": "^8.0.0", + "@whatwg-node/fetch": "^0.10.0", + "@whatwg-node/promise-helpers": "^1.0.0", + "isomorphic-ws": "^5.0.0", + "sync-fetch": "0.6.0-2", + "tslib": "^2.4.0", + "ws": "^8.17.1" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-config/node_modules/@graphql-tools/wrap": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-10.1.4.tgz", + "integrity": "sha512-7pyNKqXProRjlSdqOtrbnFRMQAVamCmEREilOXtZujxY6kYit3tvWWSjUrcIOheltTffoRh7EQSjpy2JDCzasg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/delegate": "^10.2.23", + "@graphql-tools/schema": "^10.0.25", + "@graphql-tools/utils": "^10.9.1", + "@whatwg-node/promise-helpers": "^1.3.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-config/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/graphql-config/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/graphql-config/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graphql-tag": { + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", + "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/graphql-ws": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-6.0.6.tgz", + "integrity": "sha512-zgfER9s+ftkGKUZgc0xbx8T7/HMO4AV5/YuYiFc+AtgcO5T0v8AxYYNQ+ltzuzDZgNkYJaFspm5MMYLjQzrkmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@fastify/websocket": "^10 || ^11", + "crossws": "~0.3", + "graphql": "^15.10.1 || ^16", + "uWebSockets.js": "^20", + "ws": "^8" + }, + "peerDependenciesMeta": { + "@fastify/websocket": { + "optional": true + }, + "crossws": { + "optional": true + }, + "uWebSockets.js": { + "optional": true + }, + "ws": { + "optional": true + } + } + }, + "node_modules/graphql-yoga": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/graphql-yoga/-/graphql-yoga-4.0.5.tgz", + "integrity": "sha512-vIbJU9QX5RP4PoxbMCHcfOlt/3EsC/0uLdAOlKaiUvlwJDTFCaIHo2X10vL4i/27Gw8g90ECIwm2YbmeLDwcqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/core": "^4.0.0", + "@graphql-tools/executor": "^1.0.0", + "@graphql-tools/schema": "^10.0.0", + "@graphql-tools/utils": "^10.0.0", + "@graphql-yoga/logger": "^1.0.0", + "@graphql-yoga/subscription": "^4.0.0", + "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/server": "^0.9.1", + "dset": "^3.1.1", + "lru-cache": "^10.0.0", + "tslib": "^2.5.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^15.2.0 || ^16.0.0" + } + }, + "node_modules/graphql-yoga/node_modules/@graphql-tools/schema": { + "version": "10.0.29", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.29.tgz", + "integrity": "sha512-+Htiupnq6U/AWOEAJerIOGT1pAf4u43Q3n2JmFpqFfYJchz6sKWZ7L9Lpe/NusaaUQty/IOF+eQlNFypEaWxhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.5", + "@graphql-tools/utils": "^10.10.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-yoga/node_modules/@whatwg-node/fetch": { + "version": "0.9.23", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.23.tgz", + "integrity": "sha512-7xlqWel9JsmxahJnYVUj/LLxWcnA93DR4c9xlw3U814jWTiYalryiH1qToik1hOxweKKRLi4haXHM5ycRksPBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/node-fetch": "^0.6.0", + "urlpattern-polyfill": "^10.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/graphql-yoga/node_modules/@whatwg-node/node-fetch": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.6.0.tgz", + "integrity": "sha512-tcZAhrpx6oVlkEsRngeTEEE7I5/QdLjeEz4IlekabGaESP7+Dkm/6a9KcF1KdCBB7mO9PXtBkwCuTCt8+UPg8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@kamilkisiela/fast-url-parser": "^1.1.4", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/graphql-yoga/node_modules/@whatwg-node/server": { + "version": "0.9.71", + "resolved": "https://registry.npmjs.org/@whatwg-node/server/-/server-0.9.71.tgz", + "integrity": "sha512-ueFCcIPaMgtuYDS9u0qlUoEvj6GiSsKrwnOLPp9SshqjtcRaR1IEHRjoReq3sXNydsF5i0ZnmuYgXq9dV53t0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/fetch": "^0.10.5", + "@whatwg-node/promise-helpers": "^1.2.2", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/graphql-yoga/node_modules/@whatwg-node/server/node_modules/@whatwg-node/fetch": { + "version": "0.10.13", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.13.tgz", + "integrity": "sha512-b4PhJ+zYj4357zwk4TTuF2nEe0vVtOrwdsrNo5hL+u1ojXNhh1FgJ6pg1jzDlwlT4oBdzfSwaBwMCtFCsIWg8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/node-fetch": "^0.8.3", + "urlpattern-polyfill": "^10.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/graphql-yoga/node_modules/@whatwg-node/server/node_modules/@whatwg-node/node-fetch": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.8.4.tgz", + "integrity": "sha512-AlKLc57loGoyYlrzDbejB9EeR+pfdJdGzbYnkEuZaGekFboBwzfVYVMsy88PMriqPI1ORpiGYGgSSWpx7a2sDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^3.1.1", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/promise-helpers": "^1.3.2", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "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/houdini": { + "version": "2.0.0-next.11", + "resolved": "https://registry.npmjs.org/houdini/-/houdini-2.0.0-next.11.tgz", + "integrity": "sha512-XrksBpHcUZwJn6cUulwIAXMTEmvTDIXSizEKc6WbDYezzoPQKiH4hk5RB4mNevzMbDbxoKy55MBAq/rYuYUljA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@clack/prompts": "^0.6.3", + "@graphql-tools/merge": "^9.0.6", + "@graphql-tools/schema": "^9.0.4", + "@kitql/helpers": "^0.8.10", + "@types/estree": "^1.0.7", + "@types/fs-extra": "^9.0.13", + "@types/micromatch": "^4.0.2", + "@ungap/structured-clone": "^1.0.2", + "@whatwg-node/server": "^0.10.5", + "ast-types": "^0.16.1", + "commander": "^9.4.0", + "deepmerge": "^4.2.2", + "estree-walker": "3.0.1", + "fs-extra": "^10.1.0", + "glob": "^11.0.1", + "graphql": "^16.10.0", + "graphql-yoga": "^4.0.4", + "memfs": "^3.4.7", + "micromatch": "^4.0.8", + "minimatch": "^5.1.0", + "node-fetch": "^3.2.10", + "npx-import": "^1.1.3", + "recast": "0.23.8" + }, + "bin": { + "houdini": "build/cmd-esm/index.js" + }, + "funding": { + "url": "https://github.com/sponsors/HoudiniGraphql" + }, + "peerDependencies": { + "vite": "^7.0.0" + } + }, + "node_modules/houdini-svelte": { + "version": "3.0.0-next.13", + "resolved": "https://registry.npmjs.org/houdini-svelte/-/houdini-svelte-3.0.0-next.13.tgz", + "integrity": "sha512-jCJjGNdDGuxAld7SxpsX8JJm/Ik4c/+IymORbH9c4bS4WlWgd9Q5IfTn6EUYac1hXj9Uqmi6Coputqr/yGe18w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@kitql/helpers": "^0.8.2", + "ast-types": "^0.16.1", + "estree-walker": "^3.0.1", + "graphql": "^16.10.0", + "houdini": "^2.0.0-next.10", + "recast": "0.23.8", + "rollup": "^4.39.0" + }, + "funding": { + "url": "https://github.com/sponsors/HoudiniGraphql" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.9.0", + "svelte": "^5.0.0", + "vite": "^7.0.0" + } + }, + "node_modules/houdini-svelte/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/houdini/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/houdini/node_modules/estree-walker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.1.tgz", + "integrity": "sha512-woY0RUD87WzMBUiZLx8NsYr23N5BKsOMZHhu2hoNRVh6NXGfoiT1KOL8G3UHlJAnEDGmfa5ubNA/AacfG+Kb0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/houdini/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz", + "integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz", + "integrity": "sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-2.0.2.tgz", + "integrity": "sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-2.0.2.tgz", + "integrity": "sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-to-pretty-yaml": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/json-to-pretty-yaml/-/json-to-pretty-yaml-1.2.2.tgz", + "integrity": "sha512-rvm6hunfCcqegwYaG5T4yKJWxc9FXFgBVrcTZ4XfSVRwa5HA/Xs+vB/Eo9treYYHCeNM0nrSUr82V/M31Urc7A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "remedial": "^1.0.7", + "remove-trailing-spaces": "^1.0.6" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lower-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-2.0.2.tgz", + "integrity": "sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/meros": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/meros/-/meros-1.3.2.tgz", + "integrity": "sha512-Q3mobPbvEx7XbwhnC1J1r60+5H6EZyNccdzSz0eGexJRwouUtTZxPVRGdqKtxlpD84ScK4+tIGldkqDtCKdI0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=13" + }, + "peerDependencies": { + "@types/node": ">=13" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npx-import": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/npx-import/-/npx-import-1.1.4.tgz", + "integrity": "sha512-3ShymTWOgqGyNlh5lMJAejLuIv3W1K3fbI5Ewc6YErZU3Sp0PqsNs8UIU1O8z5+KVl/Du5ag56Gza9vdorGEoA==", + "dev": true, + "license": "ISC", + "dependencies": { + "execa": "^6.1.0", + "parse-package-name": "^1.0.0", + "semver": "^7.3.7", + "validate-npm-package-name": "^4.0.0" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-package-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-package-name/-/parse-package-name-1.0.0.tgz", + "integrity": "sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-scurry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.4.0.tgz", + "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.1.tgz", + "integrity": "sha512-Bzv1LZcuiR1Sk02iJTS1QzlFNp/o5l2p3xkopwOrbPmtMeh3fK9rVW5M3neBQzHq+kGKj/4LGQMTNcTH4NGPtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", + "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recast": { + "version": "0.23.8", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.8.tgz", + "integrity": "sha512-D8izEmRtY34O+B5k2kgNmPx4b/be4MGPGFiNzWUGtezDCWDyj/1w1uQQvzySRzAO/b+6TD05FwGPuYR4X52sVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/relay-runtime": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-12.0.0.tgz", + "integrity": "sha512-QU6JKr1tMsry22DXNy9Whsq5rmvwr3LSZiiWV/9+DFpuTWvp+WFhobWMc8TC4OjKFfNhEZy7mOiqUAn5atQtug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.0.0", + "fbjs": "^3.0.0", + "invariant": "^2.2.4" + } + }, + "node_modules/remedial": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/remedial/-/remedial-1.0.8.tgz", + "integrity": "sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "engines": { + "node": "*" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true, + "license": "ISC" + }, + "node_modules/remove-trailing-spaces": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/remove-trailing-spaces/-/remove-trailing-spaces-1.0.9.tgz", + "integrity": "sha512-xzG7w5IRijvIkHIjDk65URsJJ7k4J95wmcArY5PRcmjldIOl7oTvG8+X2Ag690R7SfwiOcHrWZKVc1Pp5WIOzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true, + "license": "ISC" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "license": "ISC" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/signedsource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/signedsource/-/signedsource-1.0.0.tgz", + "integrity": "sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sponge-case": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sponge-case/-/sponge-case-1.0.1.tgz", + "integrity": "sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string-env-interpolation": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz", + "integrity": "sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "5.45.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.45.2.tgz", + "integrity": "sha512-yyXdW2u3H0H/zxxWoGwJoQlRgaSJLp+Vhktv12iRw2WRDlKqUPT54Fi0K/PkXqrdkcQ98aBazpy0AH4BCBVfoA==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.5.0", + "esm-env": "^1.2.1", + "esrap": "^2.2.0", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.4.tgz", + "integrity": "sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.4.0.tgz", + "integrity": "sha512-fjPzOfipR5S7gQ/JvI9r2H8y9gMGXO3JtmrylHLLyahEMquXI0lrebcjT+9/hNgDej0H7abTyox5HpHmW1PSWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.0", + "postcss": "^8.4.49", + "postcss-scss": "^4.0.9", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0", + "pnpm": "10.18.3" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-eslint-parser/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svelte/node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/swap-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-2.0.2.tgz", + "integrity": "sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/sync-fetch": { + "version": "0.6.0-2", + "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.6.0-2.tgz", + "integrity": "sha512-c7AfkZ9udatCuAy9RSfiGPpeOKKUAUK5e1cXadLOGUjasdxqYqAK0jTNkM/FSEyJ3a5Ra27j/tw/PS0qLmaF/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^3.3.2", + "timeout-signal": "^2.0.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", + "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/timeout-signal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/timeout-signal/-/timeout-signal-2.0.0.tgz", + "integrity": "sha512-YBGpG4bWsHoPvofT6y/5iqulfXIiIErl5B0LdtHT1mGXDFTAhhRrbUpTvBgYbovr+3cKblya2WAOcpoy90XguA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/title-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", + "integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-log": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/ts-log/-/ts-log-2.2.7.tgz", + "integrity": "sha512-320x5Ggei84AxzlXp91QkIGSw5wgaLT6GeAH0KsqDmRZdVWW2OiSeVvElVoatk3f7nicwXlElXsoFkARiGE2yg==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.0.tgz", + "integrity": "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.48.0", + "@typescript-eslint/parser": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/utils": "8.48.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", + "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unixify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz", + "integrity": "sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "normalize-path": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/urql": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/urql/-/urql-5.0.1.tgz", + "integrity": "sha512-r58gYlWvCTC19QvkTaARaCLV9/bp870byH/qbLaw3S7f8i/bC6x2Szub8RVXptiMxWmqq5dyVBjUL9G+xPEuqg==", + "license": "MIT", + "dependencies": { + "@urql/core": "^6.0.1", + "wonka": "^6.3.2" + }, + "peerDependencies": { + "@urql/core": "^6.0.0", + "react": ">= 16.8.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/validate-npm-package-name": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", + "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/value-or-promise": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.12.tgz", + "integrity": "sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/vite": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", + "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-devtools-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-devtools-json/-/vite-plugin-devtools-json-1.0.0.tgz", + "integrity": "sha512-MobvwqX76Vqt/O4AbnNMNWoXWGrKUqZbphCUle/J2KXH82yKQiunOeKnz/nqEPosPsoWWPP9FtNuPBSYpiiwkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "uuid": "^11.1.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/vite-plugin-mkcert": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/vite-plugin-mkcert/-/vite-plugin-mkcert-1.17.9.tgz", + "integrity": "sha512-SwI7yqp2Cq4r2XItarnHRCj2uzHPqevbxFNMLpyN+LDXd5w1vmZeM4l5X/wCZoP4mjPQYN+9+4kmE6e3nPO5fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^1.12.2", + "debug": "^4.4.3", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=v16.7.0" + }, + "peerDependencies": { + "vite": ">=3" + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/wonka": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.5.tgz", + "integrity": "sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==", + "license": "MIT" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3b692ef --- /dev/null +++ b/package.json @@ -0,0 +1,57 @@ +{ + "name": "nexus-5-frontend-3", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "lint": "prettier --check . && eslint .", + "generate:wave": "graphql-codegen --config codegen.wave.ts" + }, + "devDependencies": { + "@eslint/compat": "^1.4.0", + "@eslint/js": "^9.39.1", + "@graphql-codegen/cli": "^6.1.0", + "@graphql-codegen/schema-ast": "^5.0.0", + "@graphql-codegen/typescript": "^5.0.6", + "@graphql-codegen/typescript-operations": "^5.0.6", + "@graphql-codegen/typescript-urql": "^4.0.1", + "@sveltejs/adapter-node": "^5.4.0", + "@sveltejs/kit": "^2.48.5", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tailwindcss/forms": "^0.5.10", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.1.17", + "@types/node": "^24", + "date-fns": "^4.1.0", + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.13.0", + "globals": "^16.5.0", + "houdini": "^2.0.0-next.11", + "houdini-svelte": "^3.0.0-next.13", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.7.1", + "svelte": "^5.43.8", + "svelte-check": "^4.3.4", + "tailwindcss": "^4.1.17", + "typescript": "^5.9.3", + "typescript-eslint": "^8.47.0", + "vite": "^7.2.2", + "vite-plugin-devtools-json": "^1.0.0", + "vite-plugin-mkcert": "^1.17.9" + }, + "dependencies": { + "@urql/svelte": "^5.0.0", + "graphql": "^16.12.0", + "heic2any": "^0.0.4", + "urql": "^5.0.1" + } +} diff --git a/schema.graphql b/schema.graphql new file mode 100644 index 0000000..ddfe9d3 --- /dev/null +++ b/schema.graphql @@ -0,0 +1,4411 @@ +input AccountAddressInput { + accountId: ID! + city: String! + isActive: Boolean! = true + isPrimary: Boolean! = false + name: String! + notes: String! = "" + state: String! + streetAddress: String! + zipCode: String! +} + +"""Physical address information for an account""" +type AccountAddressType implements Node { + accountId: UUID! + city: String! + + """The Globally Unique ID of this object""" + id: ID! + isActive: Boolean! + isPrimary: Boolean! + labors: [LaborType!]! + name: String! + notes: String! + schedules: [ScheduleType!]! + scopes: [ScopeType!]! + services: [ServiceType!]! + state: String! + streetAddress: String! + zipCode: String! +} + +input AccountAddressUpdateInput { + city: String = null + id: ID! + isActive: Boolean = null + isPrimary: Boolean = null + name: String = null + notes: String = null + state: String = null + streetAddress: String = null + zipCode: String = null +} + +"""Contact information for an account""" +input AccountContactFilter { + AND: AccountContactFilter + DISTINCT: Boolean + NOT: AccountContactFilter + OR: AccountContactFilter + accountId: UUID + id: UUID + isActive: Boolean + isPrimary: Boolean +} + +input AccountContactInput { + accountId: ID! + email: String = null + firstName: String! + isActive: Boolean! = true + isPrimary: Boolean! = false + lastName: String! + notes: String! = "" + phone: String = null +} + +"""Contact information for an account""" +type AccountContactType implements Node { + accountId: UUID! + email: String! + firstName: String! + fullName: String! + + """The Globally Unique ID of this object""" + id: ID! + isActive: Boolean! + isPrimary: Boolean! + lastName: String! + notes: String! + phone: String +} + +input AccountContactUpdateInput { + email: String = null + firstName: String = null + id: ID! + isActive: Boolean = null + isPrimary: Boolean = null + lastName: String = null + notes: String = null + phone: String = null +} + +"""Account model belonging to a customer""" +input AccountFilter { + AND: AccountFilter + DISTINCT: Boolean + NOT: AccountFilter + OR: AccountFilter + customerId: UUID + id: UUID + isActive: Boolean + name: String + search: String +} + +input AccountInput { + customerId: ID! + endDate: Date = null + name: String! + startDate: Date! + status: String! +} + +"""Punchlist records for accounts""" +input AccountPunchlistFilter { + AND: AccountPunchlistFilter + DISTINCT: Boolean + NOT: AccountPunchlistFilter + OR: AccountPunchlistFilter + accountId: UUID + id: UUID +} + +input AccountPunchlistInput { + accountId: ID! + date: Date! +} + +"""Punchlist records for accounts""" +type AccountPunchlistType implements Node { + accountId: UUID! + date: Date! + + """The Globally Unique ID of this object""" + id: ID! +} + +input AccountPunchlistUpdateInput { + accountId: ID = null + date: Date = null + id: ID! +} + +"""Account model belonging to a customer""" +type AccountType implements Node { + addresses: [AccountAddressType!]! + contacts: [AccountContactType!]! + customerId: UUID! + endDate: Date + + """The Globally Unique ID of this object""" + id: ID! + + """Check if the account is currently active based on dates and status""" + isActive: Boolean! + name: String! + + """Get the primary address for this account""" + primaryAddress: AccountAddressType + revenues: [RevenueType!]! + startDate: Date! + + """Current status of the account""" + status: StatusChoices! +} + +input AccountUpdateInput { + customerId: ID = null + endDate: Date = null + id: ID! + name: String = null + startDate: Date = null + status: String = null +} + +input AddParticipantInput { + conversationId: ID! + participantId: ID! +} + +"""Address choices for a customer""" +enum AddressChoices { + BILLING + OFFICE + OTHER + SHIPPING +} + +type AdminDashboardData { + invoices: [InvoiceType!]! + projectScopeTemplates: [ProjectScopeTemplateType!]! + projects: [ProjectType!]! + reports: [ReportType!]! + serviceScopeTemplates: [ScopeTemplateType!]! + services: [ServiceType!]! +} + +input ArchiveConversationInput { + conversationId: ID! + isArchived: Boolean! +} + +"""Area within a scope (e.g., Kitchen, Restrooms, Lobby)""" +input AreaFilter { + AND: AreaFilter + DISTINCT: Boolean + NOT: AreaFilter + OR: AreaFilter + id: UUID + scopeId: UUID +} + +input AreaInput { + name: String! + order: Int! = 0 + scopeId: ID! +} + +"""Reusable area definition belonging to a ScopeTemplate""" +input AreaTemplateFilter { + AND: AreaTemplateFilter + DISTINCT: Boolean + NOT: AreaTemplateFilter + OR: AreaTemplateFilter + id: UUID + scopeTemplateId: UUID + + """Case-insensitive search on name""" + search: String = null +} + +input AreaTemplateInput { + name: String! + order: Int = 0 + scopeTemplateId: ID! +} + +"""Reusable area definition belonging to a ScopeTemplate""" +type AreaTemplateType implements Node { + """The Globally Unique ID of this object""" + id: ID! + name: String! + order: Int! + scopeTemplateId: UUID! + taskTemplates: [TaskTemplateType!]! +} + +input AreaTemplateUpdateInput { + id: ID! + name: String = null + order: Int = null +} + +"""Area within a scope (e.g., Kitchen, Restrooms, Lobby)""" +type AreaType implements Node { + """The Globally Unique ID of this object""" + id: ID! + name: String! + order: Int! + scopeId: UUID! + tasks: [TaskType!]! +} + +input AreaUpdateInput { + id: ID! + name: String = null + order: Int = null + scopeId: ID = null +} + +input BoolBaseFilterLookup { + """Exact match. Filter will be skipped on `null` value""" + exact: Boolean + + """ + Exact match of items in a given list. Filter will be skipped on `null` value + """ + inList: [Boolean!] + + """Assignment test. Filter will be skipped on `null` value""" + isNull: Boolean +} + +input CloseServiceSessionInput { + serviceId: ID! + taskIds: [ID!]! +} + +""" +Conversation thread that groups messages together. +Can be linked to specific entities (Project, Service, Account, etc.) for context. +""" +input ConversationFilter { + AND: ConversationFilter + DISTINCT: Boolean + NOT: ConversationFilter + OR: ConversationFilter + + """Type of conversation (DIRECT, GROUP, SUPPORT)""" + conversationType: ConversationTypeChoicesFilterLookup + createdAt: DatetimeDatetimeFilterLookup + id: UUIDFilterLookup + + """Whether this conversation is archived (system-wide)""" + isArchived: BoolBaseFilterLookup + + """Timestamp of most recent message""" + lastMessageAt: DatetimeDatetimeFilterLookup +} + +input ConversationInput { + conversationType: String! + entityId: ID = null + entityType: String = null + metadata: String = null + participantIds: [ID!]! + subject: String! +} + +""" +Links users (TeamProfile or CustomerProfile) to conversations. +Tracks per-user read status and preferences. +""" +type ConversationParticipantType implements Node { + """Conversation this participant belongs to""" + conversationId: UUID! + createdAt: DateTime! + + """The Globally Unique ID of this object""" + id: ID! + + """Whether participant has archived this conversation (user-specific)""" + isArchived: Boolean! + + """Whether participant has muted notifications for this conversation""" + isMuted: Boolean! + + """When participant joined the conversation""" + joinedAt: DateTime! + + """Timestamp when participant last read messages in this conversation""" + lastReadAt: DateTime + participant: ParticipantType! + + """Number of unread messages for this participant""" + unreadCount: Int! + updatedAt: DateTime! +} + +""" +Conversation thread that groups messages together. +Can be linked to specific entities (Project, Service, Account, etc.) for context. +""" +type ConversationType implements Node { + canDelete: Boolean! + + """Type of conversation (DIRECT, GROUP, SUPPORT)""" + conversationType: ConversationTypeChoices! + createdAt: DateTime! + createdBy: ParticipantType + entity: EntityType + + """The Globally Unique ID of this object""" + id: ID! + + """Whether this conversation is archived (system-wide)""" + isArchived: Boolean! + + """Timestamp of most recent message""" + lastMessageAt: DateTime + messageCount: Int! + messages(limit: Int = 50, offset: Int = 0): [MessageType!]! + + """Additional conversation metadata""" + metadata: JSON! + participants: [ConversationParticipantType!]! + + """Conversation subject/title""" + subject: String! + unreadCount: Int! + updatedAt: DateTime! +} + +"""Type of conversation""" +enum ConversationTypeChoices { + DIRECT + GROUP + SUPPORT +} + +input ConversationTypeChoicesFilterLookup { + """ + Case-sensitive containment test. Filter will be skipped on `null` value + """ + contains: ConversationTypeChoices + + """Case-sensitive ends-with. Filter will be skipped on `null` value""" + endsWith: ConversationTypeChoices + + """Exact match. Filter will be skipped on `null` value""" + exact: ConversationTypeChoices + + """ + Case-insensitive containment test. Filter will be skipped on `null` value + """ + iContains: ConversationTypeChoices + + """Case-insensitive ends-with. Filter will be skipped on `null` value""" + iEndsWith: ConversationTypeChoices + + """Case-insensitive exact match. Filter will be skipped on `null` value""" + iExact: ConversationTypeChoices + + """ + Case-insensitive regular expression match. Filter will be skipped on `null` value + """ + iRegex: ConversationTypeChoices + + """Case-insensitive starts-with. Filter will be skipped on `null` value""" + iStartsWith: ConversationTypeChoices + + """ + Exact match of items in a given list. Filter will be skipped on `null` value + """ + inList: [ConversationTypeChoices!] + + """Assignment test. Filter will be skipped on `null` value""" + isNull: Boolean + + """ + Case-sensitive regular expression match. Filter will be skipped on `null` value + """ + regex: ConversationTypeChoices + + """Case-sensitive starts-with. Filter will be skipped on `null` value""" + startsWith: ConversationTypeChoices +} + +"""A connection to a list of items.""" +type ConversationTypeCursorConnection { + """Contains the nodes in this connection""" + edges: [ConversationTypeCursorEdge!]! + + """Pagination data for this connection""" + pageInfo: PageInfo! + + """Total quantity of existing nodes.""" + totalCount: Int! +} + +"""An edge in a connection.""" +type ConversationTypeCursorEdge { + """A cursor for use in pagination""" + cursor: String! + + """The item at the end of the edge""" + node: ConversationType! +} + +input ConversationUpdateInput { + id: ID! + isArchived: Boolean = null + metadata: String = null + subject: String = null +} + +input CreateProjectScopeFromTemplateInput { + accountAddressId: ID = null + accountId: ID = null + description: String = null + isActive: Boolean = true + name: String = null + projectId: ID! + templateId: ID! +} + +input CreateScopeFromTemplateInput { + accountAddressId: ID = null + accountId: ID! + description: String = null + isActive: Boolean = true + name: String = null + templateId: ID! +} + +input CustomerAddressInput { + addressType: String! + city: String! + customerId: ID! + isActive: Boolean! = true + isPrimary: Boolean! = false + state: String! + streetAddress: String! + zipCode: String! +} + +"""Address information for a customer""" +type CustomerAddressType implements Node { + """Type of address""" + addressType: AddressChoices! + city: String! + + """The Globally Unique ID of this object""" + id: ID! + isActive: Boolean! + isPrimary: Boolean! + state: String! + streetAddress: String! + zipCode: String! +} + +input CustomerAddressUpdateInput { + addressType: String = null + city: String = null + id: ID! + isActive: Boolean = null + isPrimary: Boolean = null + state: String = null + streetAddress: String = null + zipCode: String = null +} + +"""Contact information for a customer""" +input CustomerContactFilter { + AND: CustomerContactFilter + DISTINCT: Boolean + NOT: CustomerContactFilter + OR: CustomerContactFilter + customerId: UUID + id: UUID + isActive: Boolean + isPrimary: Boolean +} + +input CustomerContactInput { + customerId: ID! + email: String! + firstName: String! + isActive: Boolean! = true + isPrimary: Boolean! = false + lastName: String! + notes: String! = "" + phone: String! +} + +"""Contact information for a customer""" +type CustomerContactType implements Node { + email: String! + firstName: String! + fullName: String! + + """The Globally Unique ID of this object""" + id: ID! + isActive: Boolean! + isPrimary: Boolean! + lastName: String! + notes: String! + phone: String +} + +input CustomerContactUpdateInput { + email: String = null + firstName: String = null + id: ID! + isActive: Boolean = null + isPrimary: Boolean = null + lastName: String = null + notes: String = null + phone: String = null +} + +type CustomerDashboardData { + invoices: [InvoiceType!]! + projects: [ProjectType!]! + services: [ServiceType!]! +} + +"""Customer model with contact information""" +input CustomerFilter { + AND: CustomerFilter + DISTINCT: Boolean + NOT: CustomerFilter + OR: CustomerFilter + id: UUID + + """Check if the customer is currently active based on dates and status""" + isActive: Boolean = null + search: String = null +} + +input CustomerInput { + billingEmail: String! + billingTerms: String! + endDate: Date = null + name: String! + startDate: Date! + status: String! + waveCustomerId: String = null +} + +"""External/public-facing customer accounts""" +input CustomerProfileFilter { + AND: CustomerProfileFilter + DISTINCT: Boolean + NOT: CustomerProfileFilter + OR: CustomerProfileFilter + + """Customers this profile has access to""" + customers: DjangoModelFilterInput + id: UUID +} + +input CustomerProfileInput { + customerIds: [ID!] = null + email: String = null + firstName: String! + lastName: String! + notes: String = "" + phone: String = null + status: String! = "PENDING" + userId: ID = null +} + +"""External/public-facing customer accounts""" +type CustomerProfileType implements Node { + """Customers this profile has access to""" + customers: [CustomerType!]! + email: String + firstName: String! + fullName: String! + + """The Globally Unique ID of this object""" + id: ID! + lastName: String! + notes: String! + + """Unique identifier from Ory Kratos authentication system""" + oryKratosId: String + phone: String + + """Current status of the profile""" + status: StatusChoices! +} + +union CustomerProfileTypeTeamProfileType = CustomerProfileType | TeamProfileType + +input CustomerProfileUpdateInput { + customerIds: [ID!] = null + email: String = null + firstName: String = null + id: ID! + lastName: String = null + notes: String = null + phone: String = null + status: String = null + userId: ID = null +} + +"""Customer model with contact information""" +type CustomerType implements Node { + accounts: [AccountType!]! + addresses: [CustomerAddressType!]! + billingEmail: String! + billingTerms: String! + contacts: [CustomerContactType!]! + endDate: Date + + """The Globally Unique ID of this object""" + id: ID! + + """Check if the customer is currently active based on dates and status""" + isActive: Boolean! + name: String! + startDate: Date! + + """Current status of the customer""" + status: StatusChoices! + + """Wave customer ID""" + waveCustomerId: String +} + +input CustomerUpdateInput { + billingEmail: String = null + billingTerms: String = null + endDate: Date = null + id: ID! + name: String = null + startDate: Date = null + status: String = null + waveCustomerId: String = null +} + +"""Date (isoformat)""" +scalar Date + +input DateDateFilterLookup { + day: IntComparisonFilterLookup + + """Exact match. Filter will be skipped on `null` value""" + exact: Date + + """Greater than. Filter will be skipped on `null` value""" + gt: Date + + """Greater than or equal to. Filter will be skipped on `null` value""" + gte: Date + + """ + Exact match of items in a given list. Filter will be skipped on `null` value + """ + inList: [Date!] + + """Assignment test. Filter will be skipped on `null` value""" + isNull: Boolean + isoWeekDay: IntComparisonFilterLookup + isoYear: IntComparisonFilterLookup + + """Less than. Filter will be skipped on `null` value""" + lt: Date + + """Less than or equal to. Filter will be skipped on `null` value""" + lte: Date + month: IntComparisonFilterLookup + quarter: IntComparisonFilterLookup + + """Inclusive range test (between)""" + range: DateRangeLookup + week: IntComparisonFilterLookup + weekDay: IntComparisonFilterLookup + year: IntComparisonFilterLookup +} + +enum DateOrdering { + ASC + DESC +} + +input DateRangeLookup { + end: Date = null + start: Date = null +} + +"""Date with time (isoformat)""" +scalar DateTime + +input DatetimeDatetimeFilterLookup { + date: IntComparisonFilterLookup + day: IntComparisonFilterLookup + + """Exact match. Filter will be skipped on `null` value""" + exact: DateTime + + """Greater than. Filter will be skipped on `null` value""" + gt: DateTime + + """Greater than or equal to. Filter will be skipped on `null` value""" + gte: DateTime + hour: IntComparisonFilterLookup + + """ + Exact match of items in a given list. Filter will be skipped on `null` value + """ + inList: [DateTime!] + + """Assignment test. Filter will be skipped on `null` value""" + isNull: Boolean + isoWeekDay: IntComparisonFilterLookup + isoYear: IntComparisonFilterLookup + + """Less than. Filter will be skipped on `null` value""" + lt: DateTime + + """Less than or equal to. Filter will be skipped on `null` value""" + lte: DateTime + minute: IntComparisonFilterLookup + month: IntComparisonFilterLookup + quarter: IntComparisonFilterLookup + + """Inclusive range test (between)""" + range: DatetimeRangeLookup + second: IntComparisonFilterLookup + time: IntComparisonFilterLookup + week: IntComparisonFilterLookup + weekDay: IntComparisonFilterLookup + year: IntComparisonFilterLookup +} + +input DatetimeRangeLookup { + end: DateTime = null + start: DateTime = null +} + +"""Decimal (fixed-point)""" +scalar Decimal + +enum DeliveryStatusChoices { + BOUNCED + DELIVERED + FAILED + PENDING + QUEUED + SENDING + SENT +} + +type DjangoFileType { + name: String! + path: String! + size: Int! + url: String! +} + +type DjangoImageType { + height: Int! + name: String! + path: String! + size: Int! + url: String! + width: Int! +} + +input DjangoModelFilterInput { + pk: ID! +} + +type DjangoModelType { + pk: ID! +} + +type EntityType { + account: AccountType + customer: CustomerType + entityId: ID! + entityType: String! + project: ProjectType + service: ServiceType +} + +""" +Event model to track system events that may trigger notifications. +Provides audit trail and basis for notification system. +""" +type EventType { + createdAt: DateTime! + + """UUID of the entity that triggered this event""" + entityId: ID! + + """Type of entity (e.g., 'Project', 'Report', 'Invoice')""" + entityType: String! + + """Type of event that occurred""" + eventType: EventTypeChoices! + id: ID! + + """ + Additional event metadata (e.g., old_status, new_status, changed_fields) + """ + metadata: JSON! + triggeredById: ID + triggeredByType: String + updatedAt: DateTime! +} + +enum EventTypeChoices { + ACCOUNT_ADDRESS_CREATED + ACCOUNT_ADDRESS_DELETED + ACCOUNT_ADDRESS_UPDATED + ACCOUNT_CONTACT_CREATED + ACCOUNT_CONTACT_DELETED + ACCOUNT_CONTACT_UPDATED + ACCOUNT_CREATED + ACCOUNT_DELETED + ACCOUNT_PUNCHLIST_CREATED + ACCOUNT_PUNCHLIST_DELETED + ACCOUNT_PUNCHLIST_UPDATED + ACCOUNT_STATUS_CHANGED + ACCOUNT_UPDATED + AREA_CREATED + AREA_DELETED + AREA_TEMPLATE_CREATED + AREA_TEMPLATE_DELETED + AREA_TEMPLATE_UPDATED + AREA_UPDATED + CONVERSATION_ARCHIVED + CONVERSATION_CREATED + CONVERSATION_PARTICIPANT_ADDED + CONVERSATION_PARTICIPANT_REMOVED + CUSTOMER_ADDRESS_CREATED + CUSTOMER_ADDRESS_DELETED + CUSTOMER_ADDRESS_UPDATED + CUSTOMER_CONTACT_CREATED + CUSTOMER_CONTACT_DELETED + CUSTOMER_CONTACT_UPDATED + CUSTOMER_CREATED + CUSTOMER_DELETED + CUSTOMER_PROFILE_ACCESS_GRANTED + CUSTOMER_PROFILE_ACCESS_REVOKED + CUSTOMER_PROFILE_CREATED + CUSTOMER_PROFILE_DELETED + CUSTOMER_PROFILE_UPDATED + CUSTOMER_STATUS_CHANGED + CUSTOMER_UPDATED + INVOICE_CANCELLED + INVOICE_GENERATED + INVOICE_OVERDUE + INVOICE_PAID + INVOICE_SENT + LABOR_RATE_CREATED + LABOR_RATE_DELETED + LABOR_RATE_UPDATED + MESSAGE_DELETED + MESSAGE_READ + MESSAGE_RECEIVED + MESSAGE_SENT + MONITORING_COMMAND_EXECUTED + MONITORING_INCOMPLETE_WORK_REMINDER + MONITORING_NIGHTLY_ASSIGNMENTS + PROJECT_CANCELLED + PROJECT_COMPLETED + PROJECT_CREATED + PROJECT_DELETED + PROJECT_DISPATCHED + PROJECT_PUNCHLIST_CREATED + PROJECT_PUNCHLIST_DELETED + PROJECT_PUNCHLIST_UPDATED + PROJECT_SCOPE_CATEGORY_CREATED + PROJECT_SCOPE_CATEGORY_DELETED + PROJECT_SCOPE_CATEGORY_UPDATED + PROJECT_SCOPE_CREATED + PROJECT_SCOPE_DELETED + PROJECT_SCOPE_TASK_CREATED + PROJECT_SCOPE_TASK_DELETED + PROJECT_SCOPE_TASK_UPDATED + PROJECT_SCOPE_TEMPLATE_INSTANTIATED + PROJECT_SCOPE_UPDATED + PROJECT_SESSION_CLOSED + PROJECT_SESSION_OPENED + PROJECT_SESSION_REVERTED + PROJECT_STATUS_CHANGED + PROJECT_TASK_COMPLETED + PROJECT_TASK_UNCOMPLETED + PROJECT_UPDATED + PUNCHLIST_PRIORITY_CHANGED + PUNCHLIST_STATUS_CHANGED + REPORT_APPROVED + REPORT_CREATED + REPORT_DELETED + REPORT_SUBMITTED + REPORT_UPDATED + REVENUE_RATE_CREATED + REVENUE_RATE_DELETED + REVENUE_RATE_UPDATED + SCHEDULE_CREATED + SCHEDULE_DELETED + SCHEDULE_FREQUENCY_CHANGED + SCHEDULE_UPDATED + SCOPE_CREATED + SCOPE_DELETED + SCOPE_TEMPLATE_CREATED + SCOPE_TEMPLATE_DELETED + SCOPE_TEMPLATE_INSTANTIATED + SCOPE_TEMPLATE_UPDATED + SCOPE_UPDATED + SERVICES_BULK_GENERATED + SERVICE_CANCELLED + SERVICE_COMPLETED + SERVICE_CREATED + SERVICE_DELETED + SERVICE_DISPATCHED + SERVICE_SESSION_CLOSED + SERVICE_SESSION_OPENED + SERVICE_SESSION_REVERTED + SERVICE_STATUS_CHANGED + SERVICE_TASK_COMPLETED + SERVICE_TASK_UNCOMPLETED + SERVICE_TEAM_ASSIGNED + SERVICE_TEAM_UNASSIGNED + SERVICE_UPDATED + SESSION_IMAGE_DELETED + SESSION_IMAGE_UPDATED + SESSION_IMAGE_UPLOADED + SESSION_MEDIA_INTERNAL_FLAGGED + SESSION_NOTE_CREATED + SESSION_NOTE_DELETED + SESSION_NOTE_UPDATED + SESSION_VIDEO_DELETED + SESSION_VIDEO_UPDATED + SESSION_VIDEO_UPLOADED + TASK_COMPLETION_RECORDED + TASK_CREATED + TASK_DELETED + TASK_TEMPLATE_CREATED + TASK_TEMPLATE_DELETED + TASK_TEMPLATE_UPDATED + TASK_UPDATED + TEAM_PROFILE_CREATED + TEAM_PROFILE_DELETED + TEAM_PROFILE_ROLE_CHANGED + TEAM_PROFILE_UPDATED +} + +"""Hungry Howies location records""" +input HungryHowiesLocationFilter { + AND: HungryHowiesLocationFilter + DISTINCT: Boolean + NOT: HungryHowiesLocationFilter + OR: HungryHowiesLocationFilter + accountId: UUID + id: UUID + ovenType: HungryHowiesOvenChoices + storeNumber: String +} + +input HungryHowiesLocationInput { + accountId: ID! + ovenType: String = null + storeNumber: String! +} + +""" +HungryHowiesLocationNote(id, created_at, updated_at, content, author, internal, location) +""" +input HungryHowiesLocationNoteFilter { + AND: HungryHowiesLocationNoteFilter + DISTINCT: Boolean + NOT: HungryHowiesLocationNoteFilter + OR: HungryHowiesLocationNoteFilter + authorId: UUID + contentContains: String = null + createdAfter: DateTime = null + createdBefore: DateTime = null + id: UUID + + """Internal notes are only visible to team members, not customers""" + internal: Boolean + locationId: UUID +} + +input HungryHowiesLocationNoteInput { + authorId: ID = null + content: String! + internal: Boolean! = true + locationId: ID! +} + +""" +HungryHowiesLocationNote(id, created_at, updated_at, content, author, internal, location) +""" +type HungryHowiesLocationNoteType implements Node { + authorId: UUID + content: String! + createdAt: DateTime! + + """The Globally Unique ID of this object""" + id: ID! + + """Internal notes are only visible to team members, not customers""" + internal: Boolean! + locationId: UUID! + updatedAt: DateTime! +} + +input HungryHowiesLocationNoteUpdateInput { + authorId: ID = null + content: String = null + id: ID! + internal: Boolean = null +} + +"""Hungry Howies location records""" +type HungryHowiesLocationType implements Node { + account: AccountType! + + """The Globally Unique ID of this object""" + id: ID! + notes: [HungryHowiesLocationNoteType!]! + ovenType: HungryHowiesOvenChoices + storeNumber: String! +} + +input HungryHowiesLocationUpdateInput { + accountId: ID = null + id: ID! + ovenType: String = null + storeNumber: String = null +} + +enum HungryHowiesOvenChoices { + MIDDLEBY_MARSHALL + OTHER + XLT +} + +"""Punchlist records for Hungry Howies projects""" +input HungryHowiesPunchlistFilter { + AND: HungryHowiesPunchlistFilter + DISTINCT: Boolean + NOT: HungryHowiesPunchlistFilter + OR: HungryHowiesPunchlistFilter + id: UUID + projectId: UUID +} + +input HungryHowiesPunchlistInput { + backCeiling: Boolean! = false + backVents: Boolean! = false + date: Date! + endClean: Boolean! = false + endLock: Boolean! = false + endTrash: Boolean! = false + equip: Boolean! = false + equipCut: Boolean! = false + equipDelivery: Boolean! = false + equipDisp: Boolean! = false + equipDough: Boolean! = false + equipHotbox: Boolean! = false + equipMake: Boolean! = false + equipManager: Boolean! = false + equipOther: Boolean! = false + equipSinks: Boolean! = false + equipSub: Boolean! = false + exportedAt: DateTime = null + kitchenCeiling: Boolean! = false + kitchenOven: String! = "" + kitchenOvenAlerts: Boolean! = false + kitchenOvenDisassemble: Boolean! = false + kitchenOvenExterior: Boolean! = false + kitchenOvenReassemble: Boolean! = false + kitchenPos: Boolean! = false + kitchenQt: Boolean! = false + kitchenVents: Boolean! = false + kitchenWalls: Boolean! = false + lobbyCeiling: Boolean! = false + lobbyCounter: Boolean! = false + lobbyPos: Boolean! = false + lobbyVents: Boolean! = false + notes: String! = "" + pdfUrl: String = null + projectId: ID! + secondDate: DateTime = null + secondVisit: Boolean! = false + sheetUrl: String = null +} + +"""Punchlist records for Hungry Howies projects""" +type HungryHowiesPunchlistType implements Node { + backCeiling: Boolean! + backVents: Boolean! + date: Date! + endClean: Boolean! + endLock: Boolean! + endTrash: Boolean! + equip: Boolean! + equipCut: Boolean! + equipDelivery: Boolean! + equipDisp: Boolean! + equipDough: Boolean! + equipHotbox: Boolean! + equipMake: Boolean! + equipManager: Boolean! + equipOther: Boolean! + equipSinks: Boolean! + equipSub: Boolean! + exportedAt: DateTime + + """The Globally Unique ID of this object""" + id: ID! + kitchenCeiling: Boolean! + kitchenOven: String! + kitchenOvenAlerts: Boolean! + kitchenOvenDisassemble: Boolean! + kitchenOvenExterior: Boolean! + kitchenOvenReassemble: Boolean! + kitchenPos: Boolean! + kitchenQt: Boolean! + kitchenVents: Boolean! + kitchenWalls: Boolean! + lobbyCeiling: Boolean! + lobbyCounter: Boolean! + lobbyPos: Boolean! + lobbyVents: Boolean! + notes: String! + pdfUrl: String + projectId: UUID! + secondDate: DateTime + secondVisit: Boolean! + sheetUrl: String +} + +input HungryHowiesPunchlistUpdateInput { + backCeiling: Boolean = null + backVents: Boolean = null + date: Date = null + endClean: Boolean = null + endLock: Boolean = null + endTrash: Boolean = null + equip: Boolean = null + equipCut: Boolean = null + equipDelivery: Boolean = null + equipDisp: Boolean = null + equipDough: Boolean = null + equipHotbox: Boolean = null + equipMake: Boolean = null + equipManager: Boolean = null + equipOther: Boolean = null + equipSinks: Boolean = null + equipSub: Boolean = null + exportedAt: DateTime = null + id: ID! + kitchenCeiling: Boolean = null + kitchenOven: String = null + kitchenOvenAlerts: Boolean = null + kitchenOvenDisassemble: Boolean = null + kitchenOvenExterior: Boolean = null + kitchenOvenReassemble: Boolean = null + kitchenPos: Boolean = null + kitchenQt: Boolean = null + kitchenVents: Boolean = null + kitchenWalls: Boolean = null + lobbyCeiling: Boolean = null + lobbyCounter: Boolean = null + lobbyPos: Boolean = null + lobbyVents: Boolean = null + notes: String = null + pdfUrl: String = null + projectId: ID = null + secondDate: DateTime = null + secondVisit: Boolean = null + sheetUrl: String = null +} + +input IntComparisonFilterLookup { + """Exact match. Filter will be skipped on `null` value""" + exact: Int + + """Greater than. Filter will be skipped on `null` value""" + gt: Int + + """Greater than or equal to. Filter will be skipped on `null` value""" + gte: Int + + """ + Exact match of items in a given list. Filter will be skipped on `null` value + """ + inList: [Int!] + + """Assignment test. Filter will be skipped on `null` value""" + isNull: Boolean + + """Less than. Filter will be skipped on `null` value""" + lt: Int + + """Less than or equal to. Filter will be skipped on `null` value""" + lte: Int + + """Inclusive range test (between)""" + range: IntRangeLookup +} + +input IntRangeLookup { + end: Int = null + start: Int = null +} + +"""Status choices for an invoice""" +enum InvoiceChoices { + CANCELLED + DRAFT + OVERDUE + PAID + SENT +} + +"""Invoice records""" +input InvoiceFilter { + AND: InvoiceFilter + DISTINCT: Boolean + NOT: InvoiceFilter + OR: InvoiceFilter + customerId: UUID + id: UUID + + """Current status of the invoice""" + status: InvoiceChoices +} + +input InvoiceInput { + customerId: ID! + date: Date! + datePaid: Date = null + paymentType: String = null + projectIds: [ID!] = null + revenueIds: [ID!] = null + status: String! + waveInvoiceId: String = null +} + +"""Invoice records""" +type InvoiceType implements Node { + customerId: UUID! + date: Date! + datePaid: Date + + """The Globally Unique ID of this object""" + id: ID! + paymentType: PaymentChoices + projects: [ProjectType!]! + revenues: [RevenueType!]! + + """Current status of the invoice""" + status: InvoiceChoices! + + """Wave invoice ID""" + waveInvoiceId: String +} + +input InvoiceUpdateInput { + customerId: ID = null + date: Date = null + datePaid: Date = null + id: ID! + paymentType: String = null + projectIds: [ID!] = null + revenueIds: [ID!] = null + status: String = null + waveInvoiceId: String = null +} + +""" +The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf). +""" +scalar JSON + +type LaborBreakdown { + grandTotal: Decimal! + projects: [LaborBreakdownProject!]! + projectsTotal: Decimal! + services: [LaborBreakdownService!]! + servicesTotal: Decimal! + teamMemberId: ID! + teamMemberName: String! +} + +type LaborBreakdownProject { + isTeamMemberAssigned: Boolean! + laborShare: Decimal! + projectId: ID! + projectName: String! + teamMemberCount: Int! + totalLaborAmount: Decimal! +} + +type LaborBreakdownService { + isTeamMemberAssigned: Boolean! + laborShare: Decimal! + serviceId: ID! + teamMemberCount: Int! + totalLaborRate: Decimal! +} + +"""Labor records for accounts""" +input LaborFilter { + AND: LaborFilter + DISTINCT: Boolean + NOT: LaborFilter + OR: LaborFilter + accountAddressId: UUID + id: UUID +} + +input LaborInput { + accountAddressId: ID! + amount: Float! + endDate: Date = null + startDate: Date! +} + +"""Labor records for accounts""" +type LaborType implements Node { + accountAddressId: UUID + amount: Decimal! + endDate: Date + + """The Globally Unique ID of this object""" + id: ID! + startDate: Date! +} + +input LaborUpdateInput { + accountAddressId: ID = null + amount: Float = null + endDate: Date = null + id: ID! + startDate: Date = null +} + +input MarkAsReadInput { + conversationId: ID! +} + +"""Individual message within a conversation.""" +input MessageFilter { + AND: MessageFilter + DISTINCT: Boolean + NOT: MessageFilter + OR: MessageFilter + + """Conversation this message belongs to""" + conversationId: UUIDFilterLookup + createdAt: DatetimeDatetimeFilterLookup + id: UUIDFilterLookup + + """Whether this is an automated system message""" + isSystemMessage: BoolBaseFilterLookup +} + +input MessageInput { + attachments: String = null + body: String! + conversationId: ID! + metadata: String = null + replyToId: ID = null +} + +""" +Tracks when individual messages are read by specific participants. +Allows for fine-grained read tracking beyond conversation-level. +""" +type MessageReadReceiptType implements Node { + createdAt: DateTime! + + """The Globally Unique ID of this object""" + id: ID! + + """Message that was read""" + messageId: UUID! + + """When the message was read""" + readAt: DateTime! + reader: ParticipantType! + updatedAt: DateTime! +} + +"""Individual message within a conversation.""" +type MessageType implements Node { + """Array of attachment metadata (file paths, names, sizes, types)""" + attachments: JSON! + + """Message content""" + body: String! + canDelete: Boolean! + + """Conversation this message belongs to""" + conversationId: UUID! + createdAt: DateTime! + + """The Globally Unique ID of this object""" + id: ID! + + """Whether this is an automated system message""" + isSystemMessage: Boolean! + + """Additional message metadata (formatting, mentions, etc.)""" + metadata: JSON! + readBy: [MessageReadReceiptType!]! + replyTo: MessageType + + """Message this is replying to (for threading)""" + replyToId: UUID + sender: ParticipantType! + updatedAt: DateTime! +} + +"""A connection to a list of items.""" +type MessageTypeCursorConnection { + """Contains the nodes in this connection""" + edges: [MessageTypeCursorEdge!]! + + """Pagination data for this connection""" + pageInfo: PageInfo! + + """Total quantity of existing nodes.""" + totalCount: Int! +} + +"""An edge in a connection.""" +type MessageTypeCursorEdge { + """A cursor for use in pagination""" + cursor: String! + + """The item at the end of the edge""" + node: MessageType! +} + +type Mutation { + """Add a participant to a conversation""" + addParticipant(input: AddParticipantInput!): ConversationParticipantType! + + """Add a task completion to an active project session""" + addProjectTaskCompletion(notes: String = null, projectId: ID!, taskId: ID!): ProjectSessionType! + + """Add a task completion to an active service session""" + addTaskCompletion(notes: String = null, serviceId: ID!, taskId: ID!): ServiceSessionType! + + """Archive or unarchive a conversation""" + archiveConversation(input: ArchiveConversationInput!): ConversationType! + + """Close the active ProjectSession""" + closeProjectSession(input: ProjectSessionCloseInput!): ProjectSessionType! + + """Close the active service session and record completed tasks""" + closeServiceSession(input: CloseServiceSessionInput!): ServiceSessionType! + + """Create a new account""" + createAccount(input: AccountInput!): AccountType! + + """Create a new account address""" + createAccountAddress(input: AccountAddressInput!): AccountAddressType! + + """Create a new account contact""" + createAccountContact(input: AccountContactInput!): AccountContactType! + + """Create a new account punchlist""" + createAccountPunchlist(input: AccountPunchlistInput!): AccountPunchlistType! + + """Create a new area""" + createArea(input: AreaInput!): AreaType! + + """Create a new area template""" + createAreaTemplate(input: AreaTemplateInput!): AreaTemplateType! + + """Create a new conversation""" + createConversation(input: ConversationInput!): ConversationType! + + """Create a new customer""" + createCustomer(input: CustomerInput!): CustomerType! + + """Create a new customer address""" + createCustomerAddress(input: CustomerAddressInput!): CustomerAddressType! + + """Create a new customer contact""" + createCustomerContact(input: CustomerContactInput!): CustomerContactType! + + """Create a new customer profile""" + createCustomerProfile(input: CustomerProfileInput!): CustomerProfileType! + + """Create a new hungry howies punchlist""" + createHhPunchlist(input: HungryHowiesPunchlistInput!): HungryHowiesPunchlistType! + + """Create a new Hungry Howies location""" + createHungryHowiesLocation(input: HungryHowiesLocationInput!): HungryHowiesLocationType! + + """Create a new Hungry Howies location note""" + createHungryHowiesLocationNote(input: HungryHowiesLocationNoteInput!): HungryHowiesLocationNoteType! + + """Create a new invoice""" + createInvoice(input: InvoiceInput!): InvoiceType! + + """Create a new labor rate""" + createLabor(input: LaborInput!): LaborType! + + """Create a notification rule (Admin only)""" + createNotificationRule(input: NotificationRuleInput!): NotificationRuleType! + + """Create a new project""" + createProject(input: ProjectInput!): ProjectType! + + """Create a Project Area Template""" + createProjectAreaTemplate(input: ProjectAreaTemplateInput!): ProjectAreaTemplateType! + + """Create a new project punchlist""" + createProjectPunchlist(input: ProjectPunchlistInput!): ProjectPunchlistType! + + """Create a new ProjectScope""" + createProjectScope(input: ProjectScopeInput!): ProjectScopeType! + + """Create a ProjectScopeCategory""" + createProjectScopeCategory(input: ProjectScopeCategoryInput!): ProjectScopeCategoryType! + + """ + Instantiate a ProjectScope (with Categories and Tasks) from a ProjectScopeTemplate + """ + createProjectScopeFromTemplate(input: CreateProjectScopeFromTemplateInput!): ProjectScopeType! + + """Create a ProjectScopeTask""" + createProjectScopeTask(input: ProjectScopeTaskInput!): ProjectScopeTaskType! + + """Create a new Project Scope Template""" + createProjectScopeTemplate(input: ProjectScopeTemplateInput!): ProjectScopeTemplateType! + + """ + Create a ProjectScopeTemplate (and nested Categories/Tasks) from a JSON payload + """ + createProjectScopeTemplateFromJson(payload: JSON!, replace: Boolean! = false): ProjectScopeTemplateType! + + """Create a new project session note""" + createProjectSessionNote(input: ProjectSessionNoteInput!): ProjectSessionNoteType! + + """Create a Project Task Template""" + createProjectTaskTemplate(input: ProjectTaskTemplateInput!): ProjectTaskTemplateType! + + """Create a new report""" + createReport(input: ReportInput!): ReportType! + + """Create a new revenue rate""" + createRevenue(input: RevenueInput!): RevenueType! + + """Create a new service schedule""" + createSchedule(input: ScheduleInput!): ScheduleType! + + """Create a new scope""" + createScope(input: ScopeInput!): ScopeType! + + """Instantiate a Scope (with Areas and Tasks) from a ScopeTemplate""" + createScopeFromTemplate(input: CreateScopeFromTemplateInput!): ScopeType! + + """Create a new scope template""" + createScopeTemplate(input: ScopeTemplateInput!): ScopeTemplateType! + + """Create a ScopeTemplate (and nested Areas/Tasks) from a JSON payload""" + createScopeTemplateFromJson(payload: JSON!, replace: Boolean! = false): ScopeTemplateType! + + """Create a new service visit""" + createService(input: ServiceInput!): ServiceType! + + """Create a new service session note""" + createServiceSessionNote(input: ServiceSessionNoteInput!): ServiceSessionNoteType! + + """Create a new task""" + createTask(input: TaskInput!): TaskType! + + """Create a new task completion""" + createTaskCompletion(input: TaskCompletionInput!): TaskCompletionType! + + """Create a new task template""" + createTaskTemplate(input: TaskTemplateInput!): TaskTemplateType! + + """Create a new team profile""" + createTeamProfile(input: TeamProfileInput!): TeamProfileType! + + """Delete an existing account""" + deleteAccount(id: ID!): ID! + + """Delete an existing account address""" + deleteAccountAddress(id: ID!): ID! + + """Delete an existing account contact""" + deleteAccountContact(id: ID!): ID! + + """Delete an existing account punchlist""" + deleteAccountPunchlist(id: ID!): ID! + + """Delete an existing area""" + deleteArea(id: ID!): ID! + + """Delete an existing area template""" + deleteAreaTemplate(id: ID!): ID! + + """Delete a conversation""" + deleteConversation(id: ID!): ID! + + """Delete an existing customer""" + deleteCustomer(id: ID!): ID! + + """Delete an existing customer address""" + deleteCustomerAddress(id: ID!): ID! + + """Delete an existing customer contact""" + deleteCustomerContact(id: ID!): ID! + + """Delete an existing customer profile""" + deleteCustomerProfile(id: ID!): ID! + + """Delete an existing hungry howies punchlist""" + deleteHhPunchlist(id: ID!): ID! + + """Delete a Hungry Howies location""" + deleteHungryHowiesLocation(id: ID!): ID! + + """Delete a Hungry Howies location note""" + deleteHungryHowiesLocationNote(id: ID!): ID! + + """Delete an existing invoice""" + deleteInvoice(id: ID!): ID! + + """Delete an existing labor rate""" + deleteLabor(id: ID!): ID! + + """Delete a message""" + deleteMessage(id: ID!): ID! + + """Delete a notification""" + deleteNotification(id: ID!): ID! + + """Delete a notification rule (Admin only)""" + deleteNotificationRule(id: ID!): ID! + + """Delete an existing project""" + deleteProject(id: ID!): ID! + + """Delete a Project Area Template""" + deleteProjectAreaTemplate(id: ID!): ID! + + """Delete an existing project punchlist""" + deleteProjectPunchlist(id: ID!): ID! + + """Delete a ProjectScope""" + deleteProjectScope(id: ID!): ID! + + """Delete a ProjectScopeCategory""" + deleteProjectScopeCategory(id: ID!): ID! + + """Delete a ProjectScopeTask""" + deleteProjectScopeTask(id: ID!): ID! + + """Delete a Project Scope Template""" + deleteProjectScopeTemplate(id: ID!): ID! + + """Delete a ProjectSession image""" + deleteProjectSessionImage(id: ID!): ID! + + """Delete a project session note""" + deleteProjectSessionNote(id: ID!): ID! + + """Delete a ProjectSession video""" + deleteProjectSessionVideo(id: ID!): ID! + + """Delete a Project Task Template""" + deleteProjectTaskTemplate(id: ID!): ID! + + """Delete an existing report""" + deleteReport(id: ID!): ID! + + """Delete an existing revenue rate""" + deleteRevenue(id: ID!): ID! + + """Delete an existing service schedule""" + deleteSchedule(id: ID!): ID! + + """Delete an existing scope""" + deleteScope(id: ID!): ID! + + """Delete an existing scope template""" + deleteScopeTemplate(id: ID!): ID! + + """Delete an existing service visit""" + deleteService(id: ID!): ID! + + """Delete a ServiceSession image""" + deleteServiceSessionImage(id: ID!): ID! + + """Delete a service session note""" + deleteServiceSessionNote(id: ID!): ID! + + """Delete a ServiceSession video""" + deleteServiceSessionVideo(id: ID!): ID! + + """Delete an existing task""" + deleteTask(id: ID!): ID! + + """Delete an existing task completion""" + deleteTaskCompletion(id: ID!): ID! + + """Delete an existing task template""" + deleteTaskTemplate(id: ID!): ID! + + """Delete an existing team profile""" + deleteTeamProfile(id: ID!): ID! + + """Generate service visits for a given month (all-or-nothing)""" + generateServicesByMonth(input: ServiceGenerationInput!): [ServiceType!]! + + """Mark all notifications as read for current user""" + markAllNotificationsAsRead: Int! + + """Mark conversation as read""" + markConversationAsRead(input: MarkAsReadInput!): ConversationType! + + """Mark notification as read""" + markNotificationAsRead(id: ID!): NotificationType! + + """Mute or unmute a conversation""" + muteConversation(input: MuteConversationInput!): ConversationType! + + """Start a new ProjectSession for a scheduled project""" + openProjectSession(input: ProjectSessionStartInput!): ProjectSessionType! + + """Open a service session for a scheduled service""" + openServiceSession(input: OpenServiceSessionInput!): ServiceSessionType! + + """Remove a participant from a conversation""" + removeParticipant(input: RemoveParticipantInput!): ID! + + """Remove a task completion from an active project session""" + removeProjectTaskCompletion(projectId: ID!, taskId: ID!): ProjectSessionType! + + """Remove a task completion from an active service session""" + removeTaskCompletion(serviceId: ID!, taskId: ID!): ServiceSessionType! + + """ + Revert the active ProjectSession back to scheduled (deletes the active session) + """ + revertProjectSession(input: ProjectSessionRevertInput!): Boolean! + + """ + Revert an active service session back to scheduled (deletes the active session) + """ + revertServiceSession(input: RevertServiceSessionInput!): Boolean! + + """Send a message in a conversation""" + sendMessage(input: MessageInput!): MessageType! + + """Update an existing account""" + updateAccount(input: AccountUpdateInput!): AccountType! + + """Update an existing account address""" + updateAccountAddress(input: AccountAddressUpdateInput!): AccountAddressType! + + """Update an existing account contact""" + updateAccountContact(input: AccountContactUpdateInput!): AccountContactType! + + """Update an existing account punchlist""" + updateAccountPunchlist(input: AccountPunchlistUpdateInput!): AccountPunchlistType! + + """Update an existing area""" + updateArea(input: AreaUpdateInput!): AreaType! + + """Update an existing area template""" + updateAreaTemplate(input: AreaTemplateUpdateInput!): AreaTemplateType! + + """Update a conversation""" + updateConversation(input: ConversationUpdateInput!): ConversationType! + + """Update an existing customer""" + updateCustomer(input: CustomerUpdateInput!): CustomerType! + + """Update an existing customer address""" + updateCustomerAddress(input: CustomerAddressUpdateInput!): CustomerAddressType! + + """Update an existing customer contact""" + updateCustomerContact(input: CustomerContactUpdateInput!): CustomerContactType! + + """Update an existing customer profile""" + updateCustomerProfile(input: CustomerProfileUpdateInput!): CustomerProfileType! + + """Update an existing hungry howies punchlist""" + updateHhPunchlist(input: HungryHowiesPunchlistUpdateInput!): HungryHowiesPunchlistType! + + """Update an existing Hungry Howies location""" + updateHungryHowiesLocation(input: HungryHowiesLocationUpdateInput!): HungryHowiesLocationType! + + """Update an existing Hungry Howies location note""" + updateHungryHowiesLocationNote(input: HungryHowiesLocationNoteUpdateInput!): HungryHowiesLocationNoteType! + + """Update an existing invoice""" + updateInvoice(input: InvoiceUpdateInput!): InvoiceType! + + """Update an existing labor rate""" + updateLabor(input: LaborUpdateInput!): LaborType! + + """Update a notification rule (Admin only)""" + updateNotificationRule(input: NotificationRuleUpdateInput!): NotificationRuleType! + + """Update an existing project""" + updateProject(input: ProjectUpdateInput!): ProjectType! + + """Update a Project Area Template""" + updateProjectAreaTemplate(input: ProjectAreaTemplateUpdateInput!): ProjectAreaTemplateType! + + """Update an existing project punchlist""" + updateProjectPunchlist(input: ProjectPunchlistUpdateInput!): ProjectPunchlistType! + + """Update an existing ProjectScope""" + updateProjectScope(input: ProjectScopeUpdateInput!): ProjectScopeType! + + """Update a ProjectScopeCategory""" + updateProjectScopeCategory(input: ProjectScopeCategoryUpdateInput!): ProjectScopeCategoryType! + + """Update a ProjectScopeTask""" + updateProjectScopeTask(input: ProjectScopeTaskUpdateInput!): ProjectScopeTaskType! + + """Update an existing Project Scope Template""" + updateProjectScopeTemplate(input: ProjectScopeTemplateUpdateInput!): ProjectScopeTemplateType! + + """Update an existing ProjectSession image (e.g., title)""" + updateProjectSessionImage(input: ProjectSessionImageUpdateInput!): ProjectSessionImageType! + + """Update an existing project session note""" + updateProjectSessionNote(input: ProjectSessionNoteUpdateInput!): ProjectSessionNoteType! + + """Update an existing ProjectSession video (e.g., title)""" + updateProjectSessionVideo(input: ProjectSessionVideoUpdateInput!): ProjectSessionVideoType! + + """Update a Project Task Template""" + updateProjectTaskTemplate(input: ProjectTaskTemplateUpdateInput!): ProjectTaskTemplateType! + + """Update an existing report""" + updateReport(input: ReportUpdateInput!): ReportType! + + """Update an existing revenue rate""" + updateRevenue(input: RevenueUpdateInput!): RevenueType! + + """Update an existing service schedule""" + updateSchedule(input: ScheduleUpdateInput!): ScheduleType! + + """Update an existing scope""" + updateScope(input: ScopeUpdateInput!): ScopeType! + + """Update an existing scope template""" + updateScopeTemplate(input: ScopeTemplateUpdateInput!): ScopeTemplateType! + + """Update an existing service visit""" + updateService(input: ServiceUpdateInput!): ServiceType! + + """Update an existing ServiceSession image (e.g., title)""" + updateServiceSessionImage(input: ServiceSessionImageUpdateInput!): ServiceSessionImageType! + + """Update an existing service session note""" + updateServiceSessionNote(input: ServiceSessionNoteUpdateInput!): ServiceSessionNoteType! + + """Update an existing ServiceSession video (e.g., title)""" + updateServiceSessionVideo(input: ServiceSessionVideoUpdateInput!): ServiceSessionVideoType! + + """Update an existing task""" + updateTask(input: TaskUpdateInput!): TaskType! + + """Update an existing task completion""" + updateTaskCompletion(input: TaskCompletionUpdateInput!): TaskCompletionType! + + """Update an existing task template""" + updateTaskTemplate(input: TaskTemplateUpdateInput!): TaskTemplateType! + + """Update an existing team profile""" + updateTeamProfile(input: TeamProfileUpdateInput!): TeamProfileType! + + """Upload an image to a ProjectSession""" + uploadProjectSessionImage(file: Upload!, internal: Boolean! = true, notes: String = null, sessionId: ID!, title: String = null): ProjectSessionImageType! + + """Upload a video to a ProjectSession""" + uploadProjectSessionVideo(file: Upload!, internal: Boolean! = true, notes: String = null, sessionId: ID!, title: String = null): ProjectSessionVideoType! + + """Upload an image to a ServiceSession""" + uploadServiceSessionImage(file: Upload!, internal: Boolean! = true, notes: String = null, sessionId: ID!, title: String = null): ServiceSessionImageType! + + """Upload a video to a ServiceSession""" + uploadServiceSessionVideo(file: Upload!, internal: Boolean! = true, notes: String = null, sessionId: ID!, title: String = null): ServiceSessionVideoType! +} + +input MuteConversationInput { + conversationId: ID! + isMuted: Boolean! +} + +"""An object with a Globally Unique ID""" +interface Node { + """The Globally Unique ID of this object""" + id: ID! +} + +enum NotificationChannelChoices { + EMAIL + IN_APP + SMS +} + +"""Track delivery attempts for a notification via specific channels.""" +type NotificationDeliveryType { + """Number of delivery attempts""" + attempts: Int! + + """Delivery channel (IN_APP, EMAIL, SMS)""" + channel: NotificationChannelChoices! + createdAt: DateTime! + + """Timestamp when delivery was confirmed (if supported by channel)""" + deliveredAt: DateTime + + """Error message from failed delivery attempts""" + errorMessage: String! + + """External service ID (e.g., Twilio message SID, email message ID)""" + externalId: String! + id: ID! + + """Timestamp of last delivery attempt""" + lastAttemptAt: DateTime + + """Additional delivery metadata""" + metadata: JSON! + + """Notification being delivered""" + notification: NotificationType! + + """Timestamp when successfully sent""" + sentAt: DateTime + + """Current delivery status""" + status: DeliveryStatusChoices! + updatedAt: DateTime! +} + +input NotificationRuleInput { + channels: [NotificationChannelChoices!]! + conditions: JSON = null + description: String = "" + eventTypes: [EventTypeChoices!]! + isActive: Boolean = true + name: String! + targetCustomerProfileIds: [ID!] = null + targetRoles: [RoleChoices!] = null + targetTeamProfileIds: [ID!] = null + templateBody: String = "" + templateSubject: String = "" +} + +"""Admin-defined rules for generating notifications based on events.""" +type NotificationRuleType { + """Delivery channels for notifications (IN_APP, EMAIL, SMS)""" + channels: [NotificationChannelChoices!]! + + """ + Additional conditions for when this rule applies (e.g., {'status': 'COMPLETED'}) + """ + conditions: JSON! + createdAt: DateTime! + + """Description of when and how this rule applies""" + description: String! + + """List of event types that trigger this rule""" + eventTypes: [EventTypeChoices!]! + id: ID! + + """Whether this rule is currently active""" + isActive: Boolean! + + """Descriptive name for this notification rule""" + name: String! + targetCustomerProfileIds: [ID!]! + + """ + Roles that should receive notifications (empty = all authenticated users) + """ + targetRoles: [RoleChoices!]! + targetTeamProfileIds: [ID!]! + + """Template for notification body (supports variables)""" + templateBody: String! + + """Template for notification subject (supports variables)""" + templateSubject: String! + updatedAt: DateTime! +} + +input NotificationRuleUpdateInput { + channels: [NotificationChannelChoices!] = null + conditions: JSON = null + description: String = null + eventTypes: [EventTypeChoices!] = null + id: ID! + isActive: Boolean = null + name: String = null + targetCustomerProfileIds: [ID!] = null + targetRoles: [RoleChoices!] = null + targetTeamProfileIds: [ID!] = null + templateBody: String = null + templateSubject: String = null +} + +enum NotificationStatusChoices { + FAILED + PENDING + READ + SENT +} + +"""Individual notification instance sent to a specific recipient.""" +type NotificationType { + """Optional URL for action button (e.g., link to project detail)""" + actionUrl: String! + + """Notification body content""" + body: String! + createdAt: DateTime! + + """Event that triggered this notification""" + event: EventType! + id: ID! + isRead: Boolean! + + """Additional notification metadata""" + metadata: JSON! + + """Timestamp when notification was marked as read""" + readAt: DateTime + recipientId: ID! + recipientType: String! + + """Rule that generated this notification""" + rule: NotificationRuleType + + """Current status of the notification""" + status: NotificationStatusChoices! + + """Notification subject line""" + subject: String! + updatedAt: DateTime! +} + +input OpenServiceSessionInput { + serviceId: ID! +} + +"""Information to aid in pagination.""" +type PageInfo { + """When paginating forwards, the cursor to continue.""" + endCursor: String + + """When paginating forwards, are there more items?""" + hasNextPage: Boolean! + + """When paginating backwards, are there more items?""" + hasPreviousPage: Boolean! + + """When paginating backwards, the cursor to continue.""" + startCursor: String +} + +type ParticipantType { + customerProfile: CustomerProfileType + teamProfile: TeamProfileType +} + +"""Payment choices for a transaction""" +enum PaymentChoices { + BANK_TRANSFER + CASH + CHECK + CREDIT_CARD +} + +"""Reusable category definition belonging to a ProjectScopeTemplate""" +input ProjectAreaTemplateFilter { + AND: ProjectAreaTemplateFilter + DISTINCT: Boolean + NOT: ProjectAreaTemplateFilter + OR: ProjectAreaTemplateFilter + id: UUID + + """Case-insensitive search on name""" + nameSearch: String = null + order: Int + scopeTemplateId: UUID +} + +input ProjectAreaTemplateInput { + name: String! + order: Int! = 0 + scopeTemplateId: ID! +} + +"""Reusable category definition belonging to a ProjectScopeTemplate""" +type ProjectAreaTemplateType implements Node { + """The Globally Unique ID of this object""" + id: ID! + name: String! + order: Int! + scopeTemplateId: UUID! + taskTemplates: [ProjectTaskTemplateType!]! +} + +"""A connection to a list of items.""" +type ProjectAreaTemplateTypeCursorConnection { + """Contains the nodes in this connection""" + edges: [ProjectAreaTemplateTypeCursorEdge!]! + + """Pagination data for this connection""" + pageInfo: PageInfo! + + """Total quantity of existing nodes.""" + totalCount: Int! +} + +"""An edge in a connection.""" +type ProjectAreaTemplateTypeCursorEdge { + """A cursor for use in pagination""" + cursor: String! + + """The item at the end of the edge""" + node: ProjectAreaTemplateType! +} + +input ProjectAreaTemplateUpdateInput { + id: ID! + name: String = null + order: Int = null +} + +"""Project records for customers""" +input ProjectFilter { + AND: ProjectFilter + DISTINCT: Boolean + NOT: ProjectFilter + OR: ProjectFilter + + """ + If set, the project uses this account address; otherwise, fill the address fields below + """ + accountAddressId: UUIDFilterLookup + customerId: UUIDFilterLookup + date: DateDateFilterLookup + id: UUIDFilterLookup + + """Current status of the project""" + status: ServiceChoicesFilterLookup + teamMembers: DjangoModelFilterInput +} + +input ProjectInput { + accountAddressId: ID = null + amount: Float! + calendarEventId: String = null + city: String = null + customerId: ID! + date: Date! + labor: Float! + name: String! + notes: String = null + scopeId: ID = null + state: String = null + status: String! + streetAddress: String = null + teamMemberIds: [ID!] = null + waveServiceId: String = null + zipCode: String = null +} + +"""Punchlist records for projects""" +input ProjectPunchlistFilter { + AND: ProjectPunchlistFilter + DISTINCT: Boolean + NOT: ProjectPunchlistFilter + OR: ProjectPunchlistFilter + id: UUID + projectId: UUID +} + +input ProjectPunchlistInput { + date: Date! + projectId: ID! +} + +"""Punchlist records for projects""" +type ProjectPunchlistType implements Node { + date: Date! + + """The Globally Unique ID of this object""" + id: ID! + projectId: UUID! +} + +input ProjectPunchlistUpdateInput { + date: Date = null + id: ID! + projectId: ID = null +} + +"""Category of work definition for a project""" +input ProjectScopeCategoryFilter { + AND: ProjectScopeCategoryFilter + DISTINCT: Boolean + NOT: ProjectScopeCategoryFilter + OR: ProjectScopeCategoryFilter + id: UUID + order: Int + scopeId: UUID +} + +input ProjectScopeCategoryInput { + name: String! + order: Int! = 0 + scopeId: ID! +} + +"""Category of work definition for a project""" +type ProjectScopeCategoryType implements Node { + """The Globally Unique ID of this object""" + id: ID! + name: String! + order: Int! + projectTasks: [ProjectScopeTaskType!]! + scopeId: UUID! +} + +"""A connection to a list of items.""" +type ProjectScopeCategoryTypeCursorConnection { + """Contains the nodes in this connection""" + edges: [ProjectScopeCategoryTypeCursorEdge!]! + + """Pagination data for this connection""" + pageInfo: PageInfo! + + """Total quantity of existing nodes.""" + totalCount: Int! +} + +"""An edge in a connection.""" +type ProjectScopeCategoryTypeCursorEdge { + """A cursor for use in pagination""" + cursor: String! + + """The item at the end of the edge""" + node: ProjectScopeCategoryType! +} + +input ProjectScopeCategoryUpdateInput { + id: ID! + name: String = null + order: Int = null +} + +"""Scope of work definition for a project""" +input ProjectScopeFilter { + AND: ProjectScopeFilter + DISTINCT: Boolean + NOT: ProjectScopeFilter + OR: ProjectScopeFilter + accountAddressId: UUID + accountId: UUID + id: UUID + isActive: Boolean + projectId: UUID +} + +input ProjectScopeInput { + accountAddressId: ID = null + accountId: ID = null + description: String = null + isActive: Boolean = true + name: String! + projectId: ID! +} + +"""Record of a task template being completed during a project""" +input ProjectScopeTaskCompletionFilter { + AND: ProjectScopeTaskCompletionFilter + DISTINCT: Boolean + NOT: ProjectScopeTaskCompletionFilter + OR: ProjectScopeTaskCompletionFilter + accountAddressId: UUID + accountId: UUID + completedById: UUID + id: UUID + projectId: UUID + taskId: UUID +} + +"""Record of a task template being completed during a project""" +type ProjectScopeTaskCompletionType implements Node { + accountAddressId: UUID + completedAt: DateTime! + completedById: UUID! + + """The Globally Unique ID of this object""" + id: ID! + notes: String! + projectId: UUID! + taskId: UUID! +} + +"""Specific task definition for a project""" +input ProjectScopeTaskFilter { + AND: ProjectScopeTaskFilter + DISTINCT: Boolean + NOT: ProjectScopeTaskFilter + OR: ProjectScopeTaskFilter + categoryId: UUID + id: UUID + order: Int +} + +input ProjectScopeTaskInput { + categoryId: ID! + checklistDescription: String = "" + description: String! + estimatedMinutes: Int = null + order: Int! = 0 +} + +"""Specific task definition for a project""" +type ProjectScopeTaskType implements Node { + categoryId: UUID! + checklistDescription: String! + description: String! + estimatedMinutes: Int + + """The Globally Unique ID of this object""" + id: ID! + order: Int! +} + +"""A connection to a list of items.""" +type ProjectScopeTaskTypeCursorConnection { + """Contains the nodes in this connection""" + edges: [ProjectScopeTaskTypeCursorEdge!]! + + """Pagination data for this connection""" + pageInfo: PageInfo! + + """Total quantity of existing nodes.""" + totalCount: Int! +} + +"""An edge in a connection.""" +type ProjectScopeTaskTypeCursorEdge { + """A cursor for use in pagination""" + cursor: String! + + """The item at the end of the edge""" + node: ProjectScopeTaskType! +} + +input ProjectScopeTaskUpdateInput { + checklistDescription: String = null + description: String = null + estimatedMinutes: Int = null + id: ID! + order: Int = null +} + +""" +Reusable blueprint for creating a ProjectScope with Categories and Tasks +""" +input ProjectScopeTemplateFilter { + AND: ProjectScopeTemplateFilter + DISTINCT: Boolean + NOT: ProjectScopeTemplateFilter + OR: ProjectScopeTemplateFilter + + """Case-insensitive search on description""" + descriptionSearch: String = null + id: UUID + isActive: Boolean + + """Case-insensitive search on name""" + nameSearch: String = null +} + +input ProjectScopeTemplateInput { + description: String = "" + isActive: Boolean = true + name: String! +} + +""" +Reusable blueprint for creating a ProjectScope with Categories and Tasks +""" +type ProjectScopeTemplateType implements Node { + categoryTemplates: [ProjectAreaTemplateType!]! + description: String! + + """The Globally Unique ID of this object""" + id: ID! + isActive: Boolean! + name: String! +} + +input ProjectScopeTemplateUpdateInput { + description: String = null + id: ID! + isActive: Boolean = null + name: String = null +} + +"""Scope of work definition for a project""" +type ProjectScopeType implements Node { + accountAddressId: UUID + accountId: UUID + description: String! + + """The Globally Unique ID of this object""" + id: ID! + isActive: Boolean! + name: String! + projectAreas: [ProjectScopeCategoryType!]! + projectId: UUID! +} + +input ProjectScopeUpdateInput { + accountAddressId: ID = null + accountId: ID = null + description: String = null + id: ID! + isActive: Boolean = null + name: String = null +} + +input ProjectSessionCloseInput { + completedTaskIds: [ID!] = null + projectId: ID! +} + +"""Project session records""" +input ProjectSessionFilter { + AND: ProjectSessionFilter + DISTINCT: Boolean + NOT: ProjectSessionFilter + OR: ProjectSessionFilter + accountAddressId: UUIDFilterLookup + accountId: UUIDFilterLookup + createdById: UUIDFilterLookup + customerId: UUIDFilterLookup + date: DateDateFilterLookup + end: DatetimeDatetimeFilterLookup + id: UUIDFilterLookup + projectId: UUIDFilterLookup + scopeId: UUIDFilterLookup + start: DatetimeDatetimeFilterLookup + teamMemberId: String +} + +""" +ProjectSessionImage(id, created_at, updated_at, title, content_type, width, height, uploaded_by_team_profile, notes, internal, project_session, image, thumbnail) +""" +input ProjectSessionImageFilter { + AND: ProjectSessionImageFilter + DISTINCT: Boolean + NOT: ProjectSessionImageFilter + OR: ProjectSessionImageFilter + createdAfter: DateTime = null + createdBefore: DateTime = null + id: UUID + projectSessionId: UUID + titleContains: String = null + uploadedByTeamProfileId: UUID +} + +""" +ProjectSessionImage(id, created_at, updated_at, title, content_type, width, height, uploaded_by_team_profile, notes, internal, project_session, image, thumbnail) +""" +type ProjectSessionImageType implements Node { + contentType: String! + createdAt: DateTime! + height: Int! + + """The Globally Unique ID of this object""" + id: ID! + image: DjangoImageType! + internal: Boolean! + notes: String! + projectSessionId: UUID! + thumbnail: DjangoImageType + title: String! + uploadedByTeamProfileId: UUID + width: Int! +} + +input ProjectSessionImageUpdateInput { + id: ID! + internal: Boolean = null + notes: String = null + title: String = null +} + +"""Notes attached to project sessions""" +input ProjectSessionNoteFilter { + AND: ProjectSessionNoteFilter + DISTINCT: Boolean + NOT: ProjectSessionNoteFilter + OR: ProjectSessionNoteFilter + authorId: UUID + contentContains: String = null + createdAfter: DateTime = null + createdBefore: DateTime = null + id: UUID + + """Internal notes are only visible to team members, not customers""" + internal: Boolean + sessionId: UUID +} + +input ProjectSessionNoteInput { + authorId: ID = null + content: String! + internal: Boolean! = true + sessionId: ID! +} + +"""Notes attached to project sessions""" +type ProjectSessionNoteType implements Node { + authorId: UUID + content: String! + createdAt: DateTime! + + """The Globally Unique ID of this object""" + id: ID! + + """Internal notes are only visible to team members, not customers""" + internal: Boolean! + sessionId: UUID! + updatedAt: DateTime! +} + +input ProjectSessionNoteUpdateInput { + authorId: ID = null + content: String = null + id: ID! + internal: Boolean = null +} + +input ProjectSessionRevertInput { + projectId: ID! +} + +input ProjectSessionStartInput { + projectId: ID! +} + +"""Project session records""" +type ProjectSessionType implements Node { + accountAddressId: UUID + accountId: UUID + closedById: UUID + completedTasks: [ProjectScopeTaskCompletionType!]! + createdById: UUID! + customerId: UUID! + date: Date! + durationSeconds: Int! + end: DateTime + + """The Globally Unique ID of this object""" + id: ID! + + """A session is active if it has not been closed.""" + isActive: Boolean! + notes: [ProjectSessionNoteType!]! + photos: [ProjectSessionImageType!]! + projectId: UUID! + scopeId: UUID! + start: DateTime! + videos: [ProjectSessionVideoType!]! +} + +"""Video attached to a ProjectSession for documentation.""" +input ProjectSessionVideoFilter { + AND: ProjectSessionVideoFilter + DISTINCT: Boolean + NOT: ProjectSessionVideoFilter + OR: ProjectSessionVideoFilter + createdAfter: DateTime = null + createdBefore: DateTime = null + id: UUID + internal: Boolean + maxDuration: Int = null + minDuration: Int = null + projectSessionId: UUID + titleContains: String = null + uploadedByTeamProfileId: UUID +} + +"""Video attached to a ProjectSession for documentation.""" +type ProjectSessionVideoType implements Node { + contentType: String! + createdAt: DateTime! + + """Video duration in seconds""" + durationSeconds: Int! + + """File size in bytes""" + fileSizeBytes: Int! + height: Int! + + """The Globally Unique ID of this object""" + id: ID! + internal: Boolean! + notes: String! + projectSessionId: UUID! + thumbnail: DjangoImageType + title: String! + uploadedByTeamProfileId: UUID + video: DjangoFileType! + width: Int! +} + +input ProjectSessionVideoUpdateInput { + id: ID! + internal: Boolean = null + notes: String = null + title: String = null +} + +"""Reusable task definition belonging to a ProjectAreaTemplate""" +input ProjectTaskTemplateFilter { + AND: ProjectTaskTemplateFilter + DISTINCT: Boolean + NOT: ProjectTaskTemplateFilter + OR: ProjectTaskTemplateFilter + areaTemplateId: UUID + + """Case-insensitive search on description""" + descriptionSearch: String = null + estimatedMinutes: Int + id: UUID + order: Int +} + +input ProjectTaskTemplateInput { + areaTemplateId: ID! + checklistDescription: String = "" + description: String! + estimatedMinutes: Int = null + order: Int! = 0 +} + +"""Reusable task definition belonging to a ProjectAreaTemplate""" +type ProjectTaskTemplateType implements Node { + areaTemplateId: UUID! + checklistDescription: String! + description: String! + estimatedMinutes: Int + + """The Globally Unique ID of this object""" + id: ID! + order: Int! +} + +"""A connection to a list of items.""" +type ProjectTaskTemplateTypeCursorConnection { + """Contains the nodes in this connection""" + edges: [ProjectTaskTemplateTypeCursorEdge!]! + + """Pagination data for this connection""" + pageInfo: PageInfo! + + """Total quantity of existing nodes.""" + totalCount: Int! +} + +"""An edge in a connection.""" +type ProjectTaskTemplateTypeCursorEdge { + """A cursor for use in pagination""" + cursor: String! + + """The item at the end of the edge""" + node: ProjectTaskTemplateType! +} + +input ProjectTaskTemplateUpdateInput { + checklistDescription: String = null + description: String = null + estimatedMinutes: Int = null + id: ID! + order: Int = null +} + +"""Project records for customers""" +type ProjectType implements Node { + """ + If set, the project uses this account address; otherwise, fill the address fields below + """ + accountAddressId: UUID + amount: Decimal! + + """External calendar event ID""" + calendarEventId: String + city: String + customerId: UUID! + date: Date! + + """The Globally Unique ID of this object""" + id: ID! + labor: Decimal! + name: String! + notes: String + scopeId: UUID + state: String + + """Current status of the project""" + status: ServiceChoices! + streetAddress: String + teamMembers: [DjangoModelType!]! + + """Wave service ID""" + waveServiceId: String + zipCode: String +} + +"""A connection to a list of items.""" +type ProjectTypeCursorConnection { + """Contains the nodes in this connection""" + edges: [ProjectTypeCursorEdge!]! + + """Pagination data for this connection""" + pageInfo: PageInfo! + + """Total quantity of existing nodes.""" + totalCount: Int! +} + +"""An edge in a connection.""" +type ProjectTypeCursorEdge { + """A cursor for use in pagination""" + cursor: String! + + """The item at the end of the edge""" + node: ProjectType! +} + +input ProjectUpdateInput { + accountAddressId: ID = null + amount: Float = null + calendarEventId: String = null + city: String = null + customerId: ID = null + date: Date = null + id: ID! + labor: Float = null + name: String = null + notes: String = null + scopeId: ID = null + state: String = null + status: String = null + streetAddress: String = null + teamMemberIds: [ID!] = null + waveServiceId: String = null + zipCode: String = null +} + +type Query { + account( + """The ID of the object.""" + id: ID! + ): AccountType + accountAddress( + """The ID of the object.""" + id: ID! + ): AccountAddressType + accountContact( + """The ID of the object.""" + id: ID! + ): AccountContactType + accountContacts(filters: AccountContactFilter): [AccountContactType!]! + accountPunchlist( + """The ID of the object.""" + id: ID! + ): AccountPunchlistType + accountPunchlists(filters: AccountPunchlistFilter): [AccountPunchlistType!]! + accounts(filters: AccountFilter): [AccountType!]! + + """Get the active project session for a given project""" + activeProjectSession(projectId: UUID!): ProjectSessionType + + """Get the active service session for a given service""" + activeServiceSession(serviceId: UUID!): ServiceSessionType + + """ + Consolidated dashboard data for admin/team leader users. Returns all services, projects, invoices, reports, and scope templates for the given month in a single optimized query. + """ + adminDashboard(invoiceStatus: String = null, month: String!): AdminDashboardData! + area( + """The ID of the object.""" + id: ID! + ): AreaType + areaTemplate( + """The ID of the object.""" + id: ID! + ): AreaTemplateType + areaTemplates(filters: AreaTemplateFilter): [AreaTemplateType!]! + areas(filters: AreaFilter): [AreaType!]! + conversation( + """The ID of the object.""" + id: ID! + ): ConversationType + conversations(filters: ConversationFilter): [ConversationType!]! + customer( + """The ID of the object.""" + id: ID! + ): CustomerType + customerAddress( + """The ID of the object.""" + id: ID! + ): CustomerAddressType + customerContact( + """The ID of the object.""" + id: ID! + ): CustomerContactType + customerContacts(filters: CustomerContactFilter): [CustomerContactType!]! + + """ + Consolidated dashboard data for customer users. Returns services, projects, and invoices for the customer. + """ + customerDashboard(customerId: ID!): CustomerDashboardData! + customerProfile( + """The ID of the object.""" + id: ID! + ): CustomerProfileType + customerProfiles(filters: CustomerProfileFilter): [CustomerProfileType!]! + customers(filters: CustomerFilter): [CustomerType!]! + + """Get event by ID""" + event(id: ID!): EventType + + """Get all events""" + events(limit: Int = 50, offset: Int = 0): [EventType!]! + + """ + Return conversations linked to a specific entity (Project, Service, Account, etc.) + """ + getConversationsByEntity( + """Returns the items in the list that come after the specified cursor.""" + after: String = null + + """Returns the items in the list that come before the specified cursor.""" + before: String = null + entityId: ID! + entityType: String! + filters: ConversationFilter + + """Returns the first n items from the list.""" + first: Int = null + + """Returns the items in the list that come after the specified cursor.""" + last: Int = null + ): ConversationTypeCursorConnection! + + """Return messages for a specific conversation""" + getMessagesByConversation( + """Returns the items in the list that come after the specified cursor.""" + after: String = null + + """Returns the items in the list that come before the specified cursor.""" + before: String = null + conversationId: ID! + filters: MessageFilter + + """Returns the first n items from the list.""" + first: Int = null + includeSystem: Boolean! = true + + """Returns the items in the list that come after the specified cursor.""" + last: Int = null + ): MessageTypeCursorConnection! + + """Return conversations for the authenticated user (inbox)""" + getMyConversations( + """Returns the items in the list that come after the specified cursor.""" + after: String = null + + """Returns the items in the list that come before the specified cursor.""" + before: String = null + filters: ConversationFilter + + """Returns the first n items from the list.""" + first: Int = null + includeArchived: Boolean! = false + + """Returns the items in the list that come after the specified cursor.""" + last: Int = null + ): ConversationTypeCursorConnection! + + """Return area templates for a given ProjectScopeTemplate""" + getProjectAreaTemplates( + """Returns the items in the list that come after the specified cursor.""" + after: String = null + + """Returns the items in the list that come before the specified cursor.""" + before: String = null + filters: ProjectAreaTemplateFilter + + """Returns the first n items from the list.""" + first: Int = null + + """Returns the items in the list that come after the specified cursor.""" + last: Int = null + scopeTemplateId: ID! + ): ProjectAreaTemplateTypeCursorConnection! + + """Return categories for a given ProjectScope""" + getProjectScopeCategories( + """Returns the items in the list that come after the specified cursor.""" + after: String = null + + """Returns the items in the list that come before the specified cursor.""" + before: String = null + filters: ProjectScopeCategoryFilter + + """Returns the first n items from the list.""" + first: Int = null + + """Returns the items in the list that come after the specified cursor.""" + last: Int = null + scopeId: ID! + ): ProjectScopeCategoryTypeCursorConnection! + + """Return tasks for a given ProjectScopeCategory""" + getProjectScopeTasks( + """Returns the items in the list that come after the specified cursor.""" + after: String = null + + """Returns the items in the list that come before the specified cursor.""" + before: String = null + categoryId: ID! + filters: ProjectScopeTaskFilter + + """Returns the first n items from the list.""" + first: Int = null + + """Returns the items in the list that come after the specified cursor.""" + last: Int = null + ): ProjectScopeTaskTypeCursorConnection! + + """Return task templates for a given ProjectAreaTemplate""" + getProjectTaskTemplates( + """Returns the items in the list that come after the specified cursor.""" + after: String = null + areaTemplateId: ID! + + """Returns the items in the list that come before the specified cursor.""" + before: String = null + filters: ProjectTaskTemplateFilter + + """Returns the first n items from the list.""" + first: Int = null + + """Returns the items in the list that come after the specified cursor.""" + last: Int = null + ): ProjectTaskTemplateTypeCursorConnection! + + """Return projects that include the given TeamProfile ID as a team member""" + getProjectsByTeamMember( + """Returns the items in the list that come after the specified cursor.""" + after: String = null + + """Returns the items in the list that come before the specified cursor.""" + before: String = null + filters: ProjectFilter + + """Returns the first n items from the list.""" + first: Int = null + + """Returns the items in the list that come after the specified cursor.""" + last: Int = null + ordering: DateOrdering = DESC + teamProfileId: ID! + ): ProjectTypeCursorConnection! + + """Return services that include the given TeamProfile ID as a team member""" + getServicesByTeamMember( + """Returns the items in the list that come after the specified cursor.""" + after: String = null + + """Returns the items in the list that come before the specified cursor.""" + before: String = null + filters: ServiceFilter + + """Returns the first n items from the list.""" + first: Int = null + + """Returns the items in the list that come after the specified cursor.""" + last: Int = null + ordering: DateOrdering = DESC + teamProfileId: ID! + ): ServiceTypeCursorConnection! + hhPunchlist( + """The ID of the object.""" + id: ID! + ): HungryHowiesPunchlistType + hhPunchlists(filters: HungryHowiesPunchlistFilter): [HungryHowiesPunchlistType!]! + hungryHowiesLocation( + """The ID of the object.""" + id: ID! + ): HungryHowiesLocationType + hungryHowiesLocationNote( + """The ID of the object.""" + id: ID! + ): HungryHowiesLocationNoteType + hungryHowiesLocationNotes(filters: HungryHowiesLocationNoteFilter): [HungryHowiesLocationNoteType!]! + hungryHowiesLocations(filters: HungryHowiesLocationFilter): [HungryHowiesLocationType!]! + invoice( + """The ID of the object.""" + id: ID! + ): InvoiceType + invoices(filters: InvoiceFilter): [InvoiceType!]! + labor( + """The ID of the object.""" + id: ID! + ): LaborType + labors(filters: LaborFilter): [LaborType!]! + + """Get the currently authenticated user's profile""" + me: CustomerProfileTypeTeamProfileType + message( + """The ID of the object.""" + id: ID! + ): MessageType + messages(filters: MessageFilter): [MessageType!]! + + """Get notifications for current user""" + myNotifications(limit: Int = 50, offset: Int = 0, unreadOnly: Boolean = false): [NotificationType!]! + + """Get unread notification count for current user""" + myUnreadNotificationCount: Int! + + """Get notification by ID""" + notification(id: ID!): NotificationType + + """Get notification delivery status""" + notificationDeliveries(notificationId: ID!): [NotificationDeliveryType!]! + + """Get notification rule by ID""" + notificationRule(id: ID!): NotificationRuleType + + """Get all notification rules""" + notificationRules(isActive: Boolean = null): [NotificationRuleType!]! + project( + """The ID of the object.""" + id: ID! + ): ProjectType + projectAreaTemplate( + """The ID of the object.""" + id: ID! + ): ProjectAreaTemplateType + projectAreaTemplates(filters: ProjectAreaTemplateFilter): [ProjectAreaTemplateType!]! + projectPunchlist( + """The ID of the object.""" + id: ID! + ): ProjectPunchlistType + projectPunchlists(filters: ProjectPunchlistFilter): [ProjectPunchlistType!]! + projectScope( + """The ID of the object.""" + id: ID! + ): ProjectScopeType + projectScopeCategories(filters: ProjectScopeCategoryFilter): [ProjectScopeCategoryType!]! + projectScopeCategory( + """The ID of the object.""" + id: ID! + ): ProjectScopeCategoryType + projectScopeTask( + """The ID of the object.""" + id: ID! + ): ProjectScopeTaskType + projectScopeTaskCompletion( + """The ID of the object.""" + id: ID! + ): ProjectScopeTaskCompletionType + projectScopeTaskCompletions(filters: ProjectScopeTaskCompletionFilter): [ProjectScopeTaskCompletionType!]! + projectScopeTasks(filters: ProjectScopeTaskFilter): [ProjectScopeTaskType!]! + projectScopeTemplate( + """The ID of the object.""" + id: ID! + ): ProjectScopeTemplateType + projectScopeTemplates(filters: ProjectScopeTemplateFilter): [ProjectScopeTemplateType!]! + projectScopes(filters: ProjectScopeFilter): [ProjectScopeType!]! + projectSession( + """The ID of the object.""" + id: ID! + ): ProjectSessionType + projectSessionImage( + """The ID of the object.""" + id: ID! + ): ProjectSessionImageType + projectSessionImages(filters: ProjectSessionImageFilter): [ProjectSessionImageType!]! + projectSessionNote( + """The ID of the object.""" + id: ID! + ): ProjectSessionNoteType + projectSessionNotes(filters: ProjectSessionNoteFilter): [ProjectSessionNoteType!]! + projectSessionVideo( + """The ID of the object.""" + id: ID! + ): ProjectSessionVideoType + projectSessionVideos(filters: ProjectSessionVideoFilter): [ProjectSessionVideoType!]! + projectSessions(filters: ProjectSessionFilter): [ProjectSessionType!]! + projectTaskTemplate( + """The ID of the object.""" + id: ID! + ): ProjectTaskTemplateType + projectTaskTemplates(filters: ProjectTaskTemplateFilter): [ProjectTaskTemplateType!]! + projects(filters: ProjectFilter): [ProjectType!]! + report( + """The ID of the object.""" + id: ID! + ): ReportType + reports(filters: ReportFilter): [ReportType!]! + revenue( + """The ID of the object.""" + id: ID! + ): RevenueType + revenues(filters: RevenueFilter): [RevenueType!]! + schedule( + """The ID of the object.""" + id: ID! + ): ScheduleType + schedules(filters: ScheduleFilter): [ScheduleType!]! + scope( + """The ID of the object.""" + id: ID! + ): ScopeType + scopeTemplate( + """The ID of the object.""" + id: ID! + ): ScopeTemplateType + scopeTemplates(filters: ScopeTemplateFilter): [ScopeTemplateType!]! + scopes(filters: ScopeFilter): [ScopeType!]! + service( + """The ID of the object.""" + id: ID! + ): ServiceType + serviceSession( + """The ID of the object.""" + id: ID! + ): ServiceSessionType + serviceSessionImage( + """The ID of the object.""" + id: ID! + ): ServiceSessionImageType + serviceSessionImages(filters: ServiceSessionImageFilter): [ServiceSessionImageType!]! + serviceSessionNote( + """The ID of the object.""" + id: ID! + ): ServiceSessionNoteType + serviceSessionNotes(filters: ServiceSessionNoteFilter): [ServiceSessionNoteType!]! + serviceSessionVideo( + """The ID of the object.""" + id: ID! + ): ServiceSessionVideoType + serviceSessionVideos(filters: ServiceSessionVideoFilter): [ServiceSessionVideoType!]! + serviceSessions(filters: ServiceSessionFilter): [ServiceSessionType!]! + services(filters: ServiceFilter): [ServiceType!]! + task( + """The ID of the object.""" + id: ID! + ): TaskType + taskCompletion( + """The ID of the object.""" + id: ID! + ): TaskCompletionType + taskCompletions(filters: TaskCompletionFilter): [TaskCompletionType!]! + taskTemplate( + """The ID of the object.""" + id: ID! + ): TaskTemplateType + taskTemplates(filters: TaskTemplateFilter): [TaskTemplateType!]! + tasks(filters: TaskFilter): [TaskType!]! + + """ + Consolidated dashboard data for team member users. Returns services and projects assigned to the requesting user. + """ + teamDashboard(month: String!, teamProfileId: ID!): TeamDashboardData! + teamProfile( + """The ID of the object.""" + id: ID! + ): TeamProfileType + teamProfiles: [TeamProfileType!]! + + """Get unread message count for the authenticated user""" + unreadMessageCount: Int! +} + +input RemoveParticipantInput { + conversationId: ID! + participantId: ID! +} + +"""Report records""" +input ReportFilter { + AND: ReportFilter + DISTINCT: Boolean + NOT: ReportFilter + OR: ReportFilter + date: Date + id: UUID + teamMemberId: UUID +} + +input ReportInput { + date: Date! + projectIds: [ID!] = null + serviceIds: [ID!] = null + teamMemberId: ID! +} + +"""Report records""" +type ReportType implements Node { + date: Date! + + """The Globally Unique ID of this object""" + id: ID! + laborBreakdown: LaborBreakdown! + projects: [ProjectType!]! + projectsLaborTotal: Decimal! + services: [ServiceType!]! + servicesLaborTotal: Decimal! + teamMemberId: UUID! + totalLaborValue: Decimal! +} + +input ReportUpdateInput { + date: Date = null + id: ID! + projectIds: [ID!] = null + serviceIds: [ID!] = null + teamMemberId: ID = null +} + +"""Revenue records for accounts""" +input RevenueFilter { + AND: RevenueFilter + DISTINCT: Boolean + NOT: RevenueFilter + OR: RevenueFilter + accountId: UUID + id: UUID +} + +input RevenueInput { + accountId: ID! + amount: Float! + endDate: Date = null + startDate: Date! + waveServiceId: String = null +} + +"""Revenue records for accounts""" +type RevenueType implements Node { + accountId: UUID! + amount: Decimal! + endDate: Date + + """The Globally Unique ID of this object""" + id: ID! + startDate: Date! + + """Wave service ID""" + waveServiceId: String +} + +input RevenueUpdateInput { + accountId: ID = null + amount: Float = null + endDate: Date = null + id: ID! + startDate: Date = null + waveServiceId: String = null +} + +input RevertServiceSessionInput { + serviceId: ID! +} + +enum RoleChoices { + ADMIN + TEAM_LEADER + TEAM_MEMBER +} + +"""Service schedules for accounts.""" +input ScheduleFilter { + AND: ScheduleFilter + DISTINCT: Boolean + NOT: ScheduleFilter + OR: ScheduleFilter + accountAddressId: UUID + id: UUID +} + +input ScheduleInput { + accountAddressId: ID! + endDate: Date = null + fridayService: Boolean! = false + mondayService: Boolean! = false + name: String = null + saturdayService: Boolean! = false + scheduleException: String = null + startDate: Date! + sundayService: Boolean! = false + thursdayService: Boolean! = false + tuesdayService: Boolean! = false + wednesdayService: Boolean! = false + weekendService: Boolean! = false +} + +"""Service schedules for accounts.""" +type ScheduleType implements Node { + accountAddressId: UUID + + """Optional date when this schedule expires""" + endDate: Date + fridayService: Boolean! + + """The Globally Unique ID of this object""" + id: ID! + mondayService: Boolean! + name: String + saturdayService: Boolean! + + """Notes about any exceptions or special requirements for this schedule""" + scheduleException: String + + """Date when this schedule becomes active""" + startDate: Date! + sundayService: Boolean! + thursdayService: Boolean! + tuesdayService: Boolean! + wednesdayService: Boolean! + + """ + When enabled, represents a single service visit on Friday that can be performed any time between Friday-Sunday and verified by Monday morning. Individual Fri/Sat/Sun service flags must be disabled when this is enabled. + """ + weekendService: Boolean! +} + +input ScheduleUpdateInput { + accountAddressId: ID + endDate: Date = null + fridayService: Boolean = null + id: ID! + mondayService: Boolean = null + name: String = null + saturdayService: Boolean = null + scheduleException: String = null + startDate: Date = null + sundayService: Boolean = null + thursdayService: Boolean = null + tuesdayService: Boolean = null + wednesdayService: Boolean = null + weekendService: Boolean = null +} + +"""Scope of work definition for an account address""" +input ScopeFilter { + AND: ScopeFilter + DISTINCT: Boolean + NOT: ScopeFilter + OR: ScopeFilter + accountAddressId: UUID + accountId: UUID + id: UUID + isActive: Boolean + search: String = null +} + +input ScopeInput { + accountAddressId: ID = null + accountId: ID! + description: String! = "" + isActive: Boolean! = true + name: String! +} + +"""Reusable blueprint for creating a Scope with Areas and Tasks""" +input ScopeTemplateFilter { + AND: ScopeTemplateFilter + DISTINCT: Boolean + NOT: ScopeTemplateFilter + OR: ScopeTemplateFilter + id: UUID + isActive: Boolean = null + + """Case-insensitive search on name or description""" + search: String = null +} + +input ScopeTemplateInput { + description: String = null + isActive: Boolean = true + name: String! +} + +"""Reusable blueprint for creating a Scope with Areas and Tasks""" +type ScopeTemplateType implements Node { + areaTemplates: [AreaTemplateType!]! + description: String! + + """The Globally Unique ID of this object""" + id: ID! + isActive: Boolean! + name: String! +} + +input ScopeTemplateUpdateInput { + description: String = null + id: ID! + isActive: Boolean = null + name: String = null +} + +"""Scope of work definition for an account address""" +type ScopeType implements Node { + accountAddressId: UUID + accountId: UUID! + areas: [AreaType!]! + description: String! + + """The Globally Unique ID of this object""" + id: ID! + isActive: Boolean! + name: String! +} + +input ScopeUpdateInput { + accountAddressId: ID = null + accountId: ID = null + description: String = null + id: ID! + isActive: Boolean = null + name: String = null +} + +"""Status choices for a service workflow""" +enum ServiceChoices { + CANCELLED + COMPLETED + IN_PROGRESS + SCHEDULED +} + +input ServiceChoicesFilterLookup { + """ + Case-sensitive containment test. Filter will be skipped on `null` value + """ + contains: ServiceChoices + + """Case-sensitive ends-with. Filter will be skipped on `null` value""" + endsWith: ServiceChoices + + """Exact match. Filter will be skipped on `null` value""" + exact: ServiceChoices + + """ + Case-insensitive containment test. Filter will be skipped on `null` value + """ + iContains: ServiceChoices + + """Case-insensitive ends-with. Filter will be skipped on `null` value""" + iEndsWith: ServiceChoices + + """Case-insensitive exact match. Filter will be skipped on `null` value""" + iExact: ServiceChoices + + """ + Case-insensitive regular expression match. Filter will be skipped on `null` value + """ + iRegex: ServiceChoices + + """Case-insensitive starts-with. Filter will be skipped on `null` value""" + iStartsWith: ServiceChoices + + """ + Exact match of items in a given list. Filter will be skipped on `null` value + """ + inList: [ServiceChoices!] + + """Assignment test. Filter will be skipped on `null` value""" + isNull: Boolean + + """ + Case-sensitive regular expression match. Filter will be skipped on `null` value + """ + regex: ServiceChoices + + """Case-sensitive starts-with. Filter will be skipped on `null` value""" + startsWith: ServiceChoices +} + +"""Service records for accounts""" +input ServiceFilter { + AND: ServiceFilter + DISTINCT: Boolean + NOT: ServiceFilter + OR: ServiceFilter + accountAddressId: UUIDFilterLookup + accountId: UUIDFilterLookup + date: DateDateFilterLookup + id: UUIDFilterLookup + + """Current status of the service""" + status: ServiceChoicesFilterLookup + teamMembers: DjangoModelFilterInput +} + +input ServiceGenerationInput { + accountAddressId: ID! + month: Int! + scheduleId: ID! + year: Int! +} + +input ServiceInput { + accountAddressId: ID! + accountId: ID = null + calendarEventId: String = null + date: Date! + notes: String = null + status: String! + teamMemberIds: [ID!] = null +} + +"""Service session records""" +input ServiceSessionFilter { + AND: ServiceSessionFilter + DISTINCT: Boolean + NOT: ServiceSessionFilter + OR: ServiceSessionFilter + accountAddressId: UUIDFilterLookup + createdById: UUIDFilterLookup + end: DatetimeDatetimeFilterLookup + id: UUIDFilterLookup + + """A session is active if it has not been closed.""" + isActive: Boolean = null + serviceId: UUIDFilterLookup + start: DatetimeDatetimeFilterLookup + teamMemberId: String +} + +""" +ServiceSessionImage(id, created_at, updated_at, title, content_type, width, height, uploaded_by_team_profile, notes, internal, service_session, image, thumbnail) +""" +input ServiceSessionImageFilter { + AND: ServiceSessionImageFilter + DISTINCT: Boolean + NOT: ServiceSessionImageFilter + OR: ServiceSessionImageFilter + createdAfter: DateTime = null + createdBefore: DateTime = null + id: UUID + serviceSessionId: UUID + titleContains: String = null + uploadedByTeamProfileId: UUID +} + +""" +ServiceSessionImage(id, created_at, updated_at, title, content_type, width, height, uploaded_by_team_profile, notes, internal, service_session, image, thumbnail) +""" +type ServiceSessionImageType implements Node { + contentType: String! + createdAt: DateTime! + height: Int! + + """The Globally Unique ID of this object""" + id: ID! + image: DjangoImageType! + internal: Boolean! + notes: String! + serviceSessionId: UUID! + thumbnail: DjangoImageType + title: String! + uploadedByTeamProfileId: UUID + width: Int! +} + +input ServiceSessionImageUpdateInput { + id: ID! + internal: Boolean = null + notes: String = null + title: String = null +} + +"""Notes attached to service sessions""" +input ServiceSessionNoteFilter { + AND: ServiceSessionNoteFilter + DISTINCT: Boolean + NOT: ServiceSessionNoteFilter + OR: ServiceSessionNoteFilter + authorId: UUID + contentContains: String = null + createdAfter: DateTime = null + createdBefore: DateTime = null + id: UUID + + """Internal notes are only visible to team members, not customers""" + internal: Boolean + sessionId: UUID +} + +input ServiceSessionNoteInput { + authorId: ID = null + content: String! + internal: Boolean! = true + sessionId: ID! +} + +"""Notes attached to service sessions""" +type ServiceSessionNoteType implements Node { + authorId: UUID + content: String! + createdAt: DateTime! + + """The Globally Unique ID of this object""" + id: ID! + + """Internal notes are only visible to team members, not customers""" + internal: Boolean! + sessionId: UUID! + updatedAt: DateTime! +} + +input ServiceSessionNoteUpdateInput { + authorId: ID = null + content: String = null + id: ID! + internal: Boolean = null +} + +"""Service session records""" +type ServiceSessionType implements Node { + accountAddressId: UUID! + accountId: UUID! + completedTasks: [TaskCompletionType!]! + customerId: UUID! + durationSeconds: Int! + end: DateTime + + """The Globally Unique ID of this object""" + id: ID! + + """A session is active if it has not been closed.""" + isActive: Boolean! + notes: [ServiceSessionNoteType!]! + photos: [ServiceSessionImageType!]! + scopeId: UUID! + serviceId: UUID! + start: DateTime! + videos: [ServiceSessionVideoType!]! +} + +"""Video attached to a ServiceSession for documentation.""" +input ServiceSessionVideoFilter { + AND: ServiceSessionVideoFilter + DISTINCT: Boolean + NOT: ServiceSessionVideoFilter + OR: ServiceSessionVideoFilter + createdAfter: DateTime = null + createdBefore: DateTime = null + id: UUID + internal: Boolean + maxDuration: Int = null + minDuration: Int = null + serviceSessionId: UUID + titleContains: String = null + uploadedByTeamProfileId: UUID +} + +"""Video attached to a ServiceSession for documentation.""" +type ServiceSessionVideoType implements Node { + contentType: String! + createdAt: DateTime! + + """Video duration in seconds""" + durationSeconds: Int! + + """File size in bytes""" + fileSizeBytes: Int! + height: Int! + + """The Globally Unique ID of this object""" + id: ID! + internal: Boolean! + notes: String! + serviceSessionId: UUID! + thumbnail: DjangoImageType + title: String! + uploadedByTeamProfileId: UUID + video: DjangoFileType! + width: Int! +} + +input ServiceSessionVideoUpdateInput { + id: ID! + internal: Boolean = null + notes: String = null + title: String = null +} + +"""Service records for accounts""" +type ServiceType implements Node { + accountAddressId: UUID + accountId: UUID + + """External calendar event ID""" + calendarEventId: String + date: Date! + + """The Globally Unique ID of this object""" + id: ID! + notes: String + + """Current status of the service""" + status: ServiceChoices! + teamMembers: [DjangoModelType!]! +} + +"""A connection to a list of items.""" +type ServiceTypeCursorConnection { + """Contains the nodes in this connection""" + edges: [ServiceTypeCursorEdge!]! + + """Pagination data for this connection""" + pageInfo: PageInfo! + + """Total quantity of existing nodes.""" + totalCount: Int! +} + +"""An edge in a connection.""" +type ServiceTypeCursorEdge { + """A cursor for use in pagination""" + cursor: String! + + """The item at the end of the edge""" + node: ServiceType! +} + +input ServiceUpdateInput { + accountAddressId: ID = null + accountId: ID = null + calendarEventId: String = null + date: Date = null + id: ID! + notes: String = null + status: String = null + teamMemberIds: [ID!] = null +} + +"""Status choices for a Customer, Account, or a future model""" +enum StatusChoices { + ACTIVE + INACTIVE + PENDING +} + +type Subscription { + """Subscribe to account address creation events""" + accountAddressCreated: AccountAddressType! + + """Subscribe to account address deletion events""" + accountAddressDeleted: ID! + + """Subscribe to account address updates""" + accountAddressUpdated: AccountAddressType! + + """Subscribe to account contact creation events""" + accountContactCreated: AccountContactType! + + """Subscribe to account contact deletion events""" + accountContactDeleted: ID! + + """Subscribe to account contact updates""" + accountContactUpdated: AccountContactType! + + """Subscribe to account creation events""" + accountCreated: AccountType! + + """Subscribe to account deletion events""" + accountDeleted: ID! + + """Subscribe to account punchlist creation events""" + accountPunchlistCreated: AccountPunchlistType! + + """Subscribe to account punchlist deletion events""" + accountPunchlistDeleted: ID! + + """Subscribe to account punchlist updates""" + accountPunchlistUpdated: AccountPunchlistType! + + """Subscribe to account updates""" + accountUpdated: AccountType! + + """Subscribe to area creation events""" + areaCreated: AreaType! + + """Subscribe to area deletion events""" + areaDeleted: ID! + + """Subscribe to area template creation events""" + areaTemplateCreated: AreaTemplateType! + + """Subscribe to area template deletion events""" + areaTemplateDeleted: ID! + + """Subscribe to area template updates""" + areaTemplateUpdated: AreaTemplateType! + + """Subscribe to area updates""" + areaUpdated: AreaType! + + """Subscribe to new conversations""" + conversationCreated: ConversationType! + + """Subscribe to conversation deletion events""" + conversationDeleted: ID! + + """Subscribe to conversation read events""" + conversationRead(conversationId: ID!): ConversationType! + + """Subscribe to conversation updates""" + conversationUpdated: ConversationType! + + """Subscribe to customer address creation events""" + customerAddressCreated: CustomerAddressType! + + """Subscribe to customer address deletion events""" + customerAddressDeleted: ID! + + """Subscribe to customer address updates""" + customerAddressUpdated: CustomerAddressType! + + """Subscribe to customer contact creation events""" + customerContactCreated: CustomerContactType! + + """Subscribe to customer contact deletion events""" + customerContactDeleted: ID! + + """Subscribe to customer contact updates""" + customerContactUpdated: CustomerContactType! + + """Subscribe to customer creation events""" + customerCreated: CustomerType! + + """Subscribe to customer deletion events""" + customerDeleted: ID! + + """Subscribe to customer profile creation events""" + customerProfileCreated: CustomerProfileType! + + """Subscribe to customer profile deletion events""" + customerProfileDeleted: ID! + + """Subscribe to customer profile updates""" + customerProfileUpdated: CustomerProfileType! + + """Subscribe to customer updates""" + customerUpdated: CustomerType! + + """Subscribe to hungry howies punchlist creation events""" + hhPunchlistCreated: HungryHowiesPunchlistType! + + """Subscribe to hungry howies punchlist deletion events""" + hhPunchlistDeleted: ID! + + """Subscribe to hungry howies punchlist updates""" + hhPunchlistUpdated: HungryHowiesPunchlistType! + + """Subscribe to invoice creation events""" + invoiceCreated: InvoiceType! + + """Subscribe to invoice deletion events""" + invoiceDeleted: ID! + + """Subscribe to invoice updates""" + invoiceUpdated: InvoiceType! + + """Subscribe to labor creation events""" + laborCreated: LaborType! + + """Subscribe to labor deletion events""" + laborDeleted: ID! + + """Subscribe to labor updates""" + laborUpdated: LaborType! + + """Subscribe to message deletion events""" + messageDeleted(conversationId: ID!): ID! + + """Subscribe to new messages across all conversations""" + messageReceived: MessageType! + + """Subscribe to new messages in a specific conversation""" + messageSent(conversationId: ID!): MessageType! + + """Subscribe to participant changes""" + participantAdded(conversationId: ID!): ConversationType! + + """Subscribe to project area template creation events""" + projectAreaTemplateCreated: ProjectAreaTemplateType! + + """Subscribe to project area template deletion events""" + projectAreaTemplateDeleted: ID! + + """Subscribe to project area template updates""" + projectAreaTemplateUpdated: ProjectAreaTemplateType! + + """Subscribe to project creation events""" + projectCreated: ProjectType! + + """Subscribe to project deletion events""" + projectDeleted: ID! + + """Subscribe to project punchlist creation events""" + projectPunchlistCreated: ProjectPunchlistType! + + """Subscribe to project punchlist deletion events""" + projectPunchlistDeleted: ID! + + """Subscribe to project punchlist updates""" + projectPunchlistUpdated: ProjectPunchlistType! + + """Subscribe to project scope category creation events""" + projectScopeCategoryCreated: ProjectScopeCategoryType! + + """Subscribe to project scope category deletion events""" + projectScopeCategoryDeleted: ID! + + """Subscribe to project scope category updates""" + projectScopeCategoryUpdated: ProjectScopeCategoryType! + + """Subscribe to project scope creation events""" + projectScopeCreated: ProjectScopeType! + + """Subscribe to project scope deletion events""" + projectScopeDeleted: ID! + + """Subscribe to project scope task creation events""" + projectScopeTaskCreated: ProjectScopeTaskType! + + """Subscribe to project scope task deletion events""" + projectScopeTaskDeleted: ID! + + """Subscribe to project scope task updates""" + projectScopeTaskUpdated: ProjectScopeTaskType! + + """Subscribe to project scope template creation events""" + projectScopeTemplateCreated: ProjectScopeTemplateType! + + """Subscribe to project scope template deletion events""" + projectScopeTemplateDeleted: ID! + + """Subscribe to project scope template updates""" + projectScopeTemplateUpdated: ProjectScopeTemplateType! + + """Subscribe to project scope updates""" + projectScopeUpdated: ProjectScopeType! + + """Subscribe to project task template creation events""" + projectTaskTemplateCreated: ProjectTaskTemplateType! + + """Subscribe to project task template deletion events""" + projectTaskTemplateDeleted: ID! + + """Subscribe to project task template updates""" + projectTaskTemplateUpdated: ProjectTaskTemplateType! + + """Subscribe to project updates""" + projectUpdated: ProjectType! + + """Subscribe to report creation events""" + reportCreated: ReportType! + + """Subscribe to report deletion events""" + reportDeleted: ID! + + """Subscribe to report updates""" + reportUpdated: ReportType! + + """Subscribe to revenue creation events""" + revenueCreated: RevenueType! + + """Subscribe to revenue deletion events""" + revenueDeleted: ID! + + """Subscribe to revenue updates""" + revenueUpdated: RevenueType! + + """Subscribe to schedule creation events""" + scheduleCreated: ScheduleType! + + """Subscribe to schedule deletion events""" + scheduleDeleted: ID! + + """Subscribe to schedule updates""" + scheduleUpdated: ScheduleType! + + """Subscribe to scope creation events""" + scopeCreated: ScopeType! + + """Subscribe to scopes created from a template""" + scopeCreatedFromTemplate: ScopeType! + + """Subscribe to scope deletion events""" + scopeDeleted: ID! + + """Subscribe to scope template creation events""" + scopeTemplateCreated: ScopeTemplateType! + + """Subscribe to scope template deletion events""" + scopeTemplateDeleted: ID! + + """Subscribe to scope template updates""" + scopeTemplateUpdated: ScopeTemplateType! + + """Subscribe to scope updates""" + scopeUpdated: ScopeType! + + """Subscribe to service visit creation events""" + serviceCreated: ServiceType! + + """Subscribe to service visit deletion events""" + serviceDeleted: ID! + + """Subscribe to service visit updates""" + serviceUpdated: ServiceType! + + """Subscribe to task completion creation events""" + taskCompletionCreated: TaskCompletionType! + + """Subscribe to task completion deletion events""" + taskCompletionDeleted: ID! + + """Subscribe to task completion updates""" + taskCompletionUpdated: TaskCompletionType! + + """Subscribe to task creation events""" + taskCreated: TaskType! + + """Subscribe to task deletion events""" + taskDeleted: ID! + + """Subscribe to task template creation events""" + taskTemplateCreated: TaskTemplateType! + + """Subscribe to task template deletion events""" + taskTemplateDeleted: ID! + + """Subscribe to task template updates""" + taskTemplateUpdated: TaskTemplateType! + + """Subscribe to task updates""" + taskUpdated: TaskType! + + """Subscribe to team profile creation events""" + teamProfileCreated: TeamProfileType! + + """Subscribe to team profile deletion events""" + teamProfileDeleted: ID! + + """Subscribe to team profile updates""" + teamProfileUpdated: TeamProfileType! +} + +"""Record of a task template being completed during a service visit""" +input TaskCompletionFilter { + AND: TaskCompletionFilter + DISTINCT: Boolean + NOT: TaskCompletionFilter + OR: TaskCompletionFilter + completedById: UUID + id: UUID + month: Int + serviceId: UUID + taskId: UUID + year: Int +} + +input TaskCompletionInput { + completedAt: DateTime! + completedById: ID! + notes: String! = "" + serviceId: ID! + taskId: ID! +} + +"""Record of a task template being completed during a service visit""" +type TaskCompletionType implements Node { + accountAddressId: UUID + completedAt: DateTime! + completedById: UUID! + + """The Globally Unique ID of this object""" + id: ID! + month: Int! + notes: String! + serviceId: UUID! + taskId: UUID! + year: Int! +} + +input TaskCompletionUpdateInput { + completedAt: DateTime = null + completedById: ID = null + id: ID! + notes: String = null + serviceId: ID = null + taskId: ID = null +} + +"""Individual task template within an area""" +input TaskFilter { + AND: TaskFilter + DISTINCT: Boolean + NOT: TaskFilter + OR: TaskFilter + areaId: UUID + + """How often the task should be performed""" + frequency: TaskFrequencyChoices + id: UUID +} + +enum TaskFrequencyChoices { + ANNUAL + AS_NEEDED + DAILY + MONTHLY + QUARTERLY + TRIANNUAL + WEEKLY +} + +input TaskInput { + areaId: ID! + checklistDescription: String = null + description: String! + estimatedMinutes: Int = null + frequency: String! + isConditional: Boolean! = false + order: Int! = 0 +} + +"""Reusable task definition belonging to an AreaTemplate""" +input TaskTemplateFilter { + AND: TaskTemplateFilter + DISTINCT: Boolean + NOT: TaskTemplateFilter + OR: TaskTemplateFilter + areaTemplateId: UUID + + """Case-insensitive search on description""" + descriptionSearch: String = null + + """How often the task should be performed""" + frequency: String = null + id: UUID + + """Task marked 'where applicable'""" + isConditional: Boolean = null +} + +input TaskTemplateInput { + areaTemplateId: ID! + checklistDescription: String = null + description: String! + estimatedMinutes: Int = null + frequency: String! + isConditional: Boolean = false + order: Int = 0 +} + +"""Reusable task definition belonging to an AreaTemplate""" +type TaskTemplateType implements Node { + areaTemplateId: UUID! + checklistDescription: String! + description: String! + estimatedMinutes: Int + + """How often the task should be performed""" + frequency: TaskFrequencyChoices! + + """The Globally Unique ID of this object""" + id: ID! + + """Task marked 'where applicable'""" + isConditional: Boolean! + order: Int! +} + +input TaskTemplateUpdateInput { + checklistDescription: String = null + description: String = null + estimatedMinutes: Int = null + frequency: String = null + id: ID! + isConditional: Boolean = null + order: Int = null +} + +"""Individual task template within an area""" +type TaskType implements Node { + areaId: UUID! + checklistDescription: String! + description: String! + estimatedMinutes: Int + + """How often the task should be performed""" + frequency: TaskFrequencyChoices! + + """The Globally Unique ID of this object""" + id: ID! + + """Task marked 'where applicable'""" + isConditional: Boolean! + order: Int! +} + +input TaskUpdateInput { + areaId: ID = null + checklistDescription: String = null + description: String = null + estimatedMinutes: Int = null + frequency: String = null + id: ID! + isConditional: Boolean = null + order: Int = null +} + +type TeamDashboardData { + projects: [ProjectType!]! + reports: [ReportType!]! + services: [ServiceType!]! +} + +input TeamProfileInput { + email: String = null + firstName: String! + lastName: String! + notes: String = null + phone: String = null + role: String! + status: String! = "PENDING" + userId: ID = null +} + +"""Internal team member accounts""" +type TeamProfileType implements Node { + email: String + firstName: String! + fullName: String! + + """The Globally Unique ID of this object""" + id: ID! + lastName: String! + notes: String! + + """Unique identifier from Ory Kratos authentication system""" + oryKratosId: String + phone: String + + """Role of the team member""" + role: RoleChoices! + + """Current status of the profile""" + status: StatusChoices! +} + +input TeamProfileUpdateInput { + email: String = null + firstName: String = null + id: ID! + lastName: String = null + notes: String = null + phone: String = null + role: String = null + status: String = null + userId: ID = null +} + +scalar UUID + +input UUIDFilterLookup { + """ + Case-sensitive containment test. Filter will be skipped on `null` value + """ + contains: UUID + + """Case-sensitive ends-with. Filter will be skipped on `null` value""" + endsWith: UUID + + """Exact match. Filter will be skipped on `null` value""" + exact: UUID + + """ + Case-insensitive containment test. Filter will be skipped on `null` value + """ + iContains: UUID + + """Case-insensitive ends-with. Filter will be skipped on `null` value""" + iEndsWith: UUID + + """Case-insensitive exact match. Filter will be skipped on `null` value""" + iExact: UUID + + """ + Case-insensitive regular expression match. Filter will be skipped on `null` value + """ + iRegex: UUID + + """Case-insensitive starts-with. Filter will be skipped on `null` value""" + iStartsWith: UUID + + """ + Exact match of items in a given list. Filter will be skipped on `null` value + """ + inList: [UUID!] + + """Assignment test. Filter will be skipped on `null` value""" + isNull: Boolean + + """ + Case-sensitive regular expression match. Filter will be skipped on `null` value + """ + regex: UUID + + """Case-sensitive starts-with. Filter will be skipped on `null` value""" + startsWith: UUID +} + +scalar Upload \ No newline at end of file diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..d50f880 --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,9 @@ +declare global { + namespace App { + interface Locals { + cookie: string | null; + } + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..99d82b9 --- /dev/null +++ b/src/app.html @@ -0,0 +1,26 @@ + + + + + + Nexus + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..1466898 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,8 @@ +import type { Handle } from '@sveltejs/kit'; + +export const handle: Handle = async ({ event, resolve }) => { + const cookie = event.cookies.get('ory_kratos_session'); + event.locals.cookie = cookie ? `ory_kratos_session=${cookie}` : null; + + return resolve(event); +}; diff --git a/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/src/lib/assets/floors.jpg b/src/lib/assets/floors.jpg new file mode 100644 index 0000000..a1fe338 Binary files /dev/null and b/src/lib/assets/floors.jpg differ diff --git a/src/lib/assets/hero.jpg b/src/lib/assets/hero.jpg new file mode 100644 index 0000000..986fa6e Binary files /dev/null and b/src/lib/assets/hero.jpg differ diff --git a/src/lib/assets/hh-ceilings.jpg b/src/lib/assets/hh-ceilings.jpg new file mode 100644 index 0000000..feb04e1 Binary files /dev/null and b/src/lib/assets/hh-ceilings.jpg differ diff --git a/src/lib/assets/hh-equipment.jpg b/src/lib/assets/hh-equipment.jpg new file mode 100644 index 0000000..5ee48ba Binary files /dev/null and b/src/lib/assets/hh-equipment.jpg differ diff --git a/src/lib/assets/hh-walls.jpg b/src/lib/assets/hh-walls.jpg new file mode 100644 index 0000000..e9b2841 Binary files /dev/null and b/src/lib/assets/hh-walls.jpg differ diff --git a/src/lib/assets/kitchens.jpg b/src/lib/assets/kitchens.jpg new file mode 100644 index 0000000..6016324 Binary files /dev/null and b/src/lib/assets/kitchens.jpg differ diff --git a/src/lib/assets/logo-icon.png b/src/lib/assets/logo-icon.png new file mode 100644 index 0000000..dbeff20 Binary files /dev/null and b/src/lib/assets/logo-icon.png differ diff --git a/src/lib/components/BusinessFooter.svelte b/src/lib/components/BusinessFooter.svelte new file mode 100644 index 0000000..c972b7e --- /dev/null +++ b/src/lib/components/BusinessFooter.svelte @@ -0,0 +1,72 @@ + + +
+
+
+ +
+ Nexus Cleaning +

+ Dependable commercial cleaning services for businesses that demand excellence. +

+
+ + +
+

Services

+ +
+ + +
+

Company

+ +
+
+ + +
+

+ © {currentYear} Corellon Digital. All rights reserved. +

+
+
+
diff --git a/src/lib/components/PublicBackLink.svelte b/src/lib/components/PublicBackLink.svelte new file mode 100644 index 0000000..0c75a4e --- /dev/null +++ b/src/lib/components/PublicBackLink.svelte @@ -0,0 +1,19 @@ + + + + + + + {label} + diff --git a/src/lib/components/ThemeToggle.svelte b/src/lib/components/ThemeToggle.svelte new file mode 100644 index 0000000..952d773 --- /dev/null +++ b/src/lib/components/ThemeToggle.svelte @@ -0,0 +1,56 @@ + + + diff --git a/src/lib/components/admin/AdminDashboardHeader.svelte b/src/lib/components/admin/AdminDashboardHeader.svelte new file mode 100644 index 0000000..2fee9de --- /dev/null +++ b/src/lib/components/admin/AdminDashboardHeader.svelte @@ -0,0 +1,228 @@ + + + + +
+ {#if showBackButton} + +
+ + {#if isNavigatingBack} + + + + + {:else} + + + + {/if} + +
+
+

{title}

+ +
+ {#if subtitleSnippet} +

+ {@render subtitleSnippet({ toggleMenu })} +

+ {:else if subtitle} +

{subtitle}

+ {/if} +
+ +
+
+ {:else} + +
+

{title}

+ +
+ {#if subtitleSnippet} +

+ {@render subtitleSnippet({ toggleMenu })} +

+ {:else if subtitle} +

{subtitle}

+ {/if} + {/if} +
diff --git a/src/lib/components/admin/AdminListLayout.svelte b/src/lib/components/admin/AdminListLayout.svelte new file mode 100644 index 0000000..8b3b389 --- /dev/null +++ b/src/lib/components/admin/AdminListLayout.svelte @@ -0,0 +1,282 @@ + + + + {title} - Admin - Nexus + + +
+ + +
+
+
+ + + + + +
+

{title}

+

{subtitle}

+
+
+ +
+
+ + +
+ +
+ +
+
+ + + {#if headerActions} +
+ {@render headerActions()} +
+ {/if} + + + + + + {#if onSearchChange} +
+
+ + + + +
+
+ {/if} + + +
+ {#if isLoading} + +
+
+ + + + + Loading... +
+
+ {/if} + + {#if hasItems} +
+ {#each items as itemData, index (index)} + {@render item(itemData)} + {/each} +
+ {:else if !isLoading} + +
+ {#if emptyIcon} + {@render emptyIcon()} + {:else} + + + + {/if} +

{emptyTitle}

+

{emptyText}

+ {#if emptyAction} + {@render emptyAction()} + {:else if createButtonText && onCreateClick && !searchQuery} + + {/if} +
+ {/if} +
+
+
+ + +{#if createButtonText && onCreateClick} + +{/if} diff --git a/src/lib/components/admin/AdminPageHeader.svelte b/src/lib/components/admin/AdminPageHeader.svelte new file mode 100644 index 0000000..0220ee3 --- /dev/null +++ b/src/lib/components/admin/AdminPageHeader.svelte @@ -0,0 +1,294 @@ + + + + +
+ {#if showBackButton} + +
+ + {#if isNavigatingBack} + + + + + {:else} + + + + {/if} + +
+
+

{title}

+ {#if showNavMenu} + + {/if} +
+ {#if subtitleSnippet} +

+ {@render subtitleSnippet({ toggleMenu })} +

+ {:else if subtitle} +

{subtitle}

+ {/if} +
+ + {#if onEdit} + + {:else} +
+ {/if} +
+ {:else} + +
+

{title}

+ {#if showNavMenu} + + {/if} +
+ {#if subtitleSnippet} +

+ {@render subtitleSnippet({ toggleMenu })} +

+ {:else if subtitle} +

{subtitle}

+ {/if} + {/if} +
diff --git a/src/lib/components/admin/DeleteConfirmModal.svelte b/src/lib/components/admin/DeleteConfirmModal.svelte new file mode 100644 index 0000000..af4d6af --- /dev/null +++ b/src/lib/components/admin/DeleteConfirmModal.svelte @@ -0,0 +1,142 @@ + + + + +{#if open} + + +{/if} diff --git a/src/lib/components/admin/ItemActions.svelte b/src/lib/components/admin/ItemActions.svelte new file mode 100644 index 0000000..2692620 --- /dev/null +++ b/src/lib/components/admin/ItemActions.svelte @@ -0,0 +1,41 @@ + + +
+ + +
diff --git a/src/lib/components/admin/SectionHeader.svelte b/src/lib/components/admin/SectionHeader.svelte new file mode 100644 index 0000000..7e757c7 --- /dev/null +++ b/src/lib/components/admin/SectionHeader.svelte @@ -0,0 +1,30 @@ + + +
+

{title}

+ {#if onButtonClick && buttonText} + + {/if} +
diff --git a/src/lib/components/admin/accounts/AccountForm.svelte b/src/lib/components/admin/accounts/AccountForm.svelte new file mode 100644 index 0000000..41dfac4 --- /dev/null +++ b/src/lib/components/admin/accounts/AccountForm.svelte @@ -0,0 +1,218 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+
+ + +
+ +
+ +
+ +
+ + + +
+
+
+ +
+
+ + +
+ +
+ + +
+
+ + {#if isEdit} +
+ + +
+ {/if} + +
+ + +
+
+
diff --git a/src/lib/components/admin/accounts/AddressCard.svelte b/src/lib/components/admin/accounts/AddressCard.svelte new file mode 100644 index 0000000..bd16ff0 --- /dev/null +++ b/src/lib/components/admin/accounts/AddressCard.svelte @@ -0,0 +1,362 @@ + + +
+ +
+ +
+ + +
+
+ + + {#if isExpanded} +
+ + {#if address.notes} +

{address.notes}

+ {/if} + + +
+
+

Schedules

+ +
+ + {#if currentSchedules.length > 0 || pastSchedules.length > 0} +
+ + {#each currentSchedules as schedule (schedule.id)} + {@const scheduleDays = getScheduleDays(schedule)} + {@const isCurrentSchedule = !schedule.endDate} +
+
+
+
+ {schedule.name || 'Unnamed Schedule'} + + {#if isCurrentSchedule} + As of {formatDate(schedule.startDate) || '—'} + {:else} + {formatDate(schedule.startDate) || '—'} - {formatDate(schedule.endDate)} + {/if} + + {#if isCurrentSchedule && onGenerateServices} + + {/if} +
+
+ {#if schedule.weekendService} + + Weekend + + {:else if scheduleDays.length > 0} + {#each scheduleDays as day (day)} + + {day} + + {/each} + {:else} + No days selected + {/if} +
+ {#if schedule.scheduleException} +

+ {schedule.scheduleException} +

+ {/if} +
+
+ onEditSchedule(schedule)} + onDelete={() => onDeleteSchedule(schedule.id)} + /> +
+
+
+ {/each} + + + {#if pastSchedules.length > 0} +
+ + + {#if showScheduleHistory} +
+ {#each pastSchedules as schedule (schedule.id)} + {@const scheduleDays = getScheduleDays(schedule)} +
+
+
+
+ {schedule.name || 'Unnamed Schedule'} + + {formatDate(schedule.startDate) || '—'} - {formatDate( + schedule.endDate + )} + +
+
+ {#if schedule.weekendService} + + Weekend + + {:else if scheduleDays.length > 0} + {#each scheduleDays as day (day)} + + {day} + + {/each} + {:else} + No days selected + {/if} +
+
+
+ onEditSchedule(schedule)} + onDelete={() => onDeleteSchedule(schedule.id)} + /> +
+
+
+ {/each} +
+ {/if} +
+ {/if} +
+ {:else} +

No schedules configured

+ {/if} +
+ + + + + + +
+ {/if} +
diff --git a/src/lib/components/admin/accounts/AreaForm.svelte b/src/lib/components/admin/accounts/AreaForm.svelte new file mode 100644 index 0000000..e6f3723 --- /dev/null +++ b/src/lib/components/admin/accounts/AreaForm.svelte @@ -0,0 +1,128 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+
+ + +
+ +
+ + +

Lower numbers appear first

+
+ +
+ + +
+
+
diff --git a/src/lib/components/admin/accounts/ContactListItem.svelte b/src/lib/components/admin/accounts/ContactListItem.svelte new file mode 100644 index 0000000..c321407 --- /dev/null +++ b/src/lib/components/admin/accounts/ContactListItem.svelte @@ -0,0 +1,73 @@ + + +
+ {#if contact.isPrimary || !contact.isActive} +
+ {#if contact.isPrimary} + Primary + {/if} + {#if !contact.isActive} + Inactive + {/if} +
+ {/if} +
+
+
+ {contact.fullName} + {#if contact.isPrimary} + + {/if} + {#if !contact.isActive} + + {/if} +
+
+
+ + +
+
+ {#if contact.email || contact.phone} +
+ {#if contact.email} +

{contact.email}

+ {/if} + {#if contact.phone} +

{contact.phone}

+ {/if} +
+ {/if} +
diff --git a/src/lib/components/admin/accounts/LaborForm.svelte b/src/lib/components/admin/accounts/LaborForm.svelte new file mode 100644 index 0000000..6ab54a3 --- /dev/null +++ b/src/lib/components/admin/accounts/LaborForm.svelte @@ -0,0 +1,171 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+
+ +
+ $ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +

+ Leave end date empty to mark this as the current labor rate. +

+ +
+ + +
+
+
diff --git a/src/lib/components/admin/accounts/LaborSection.svelte b/src/lib/components/admin/accounts/LaborSection.svelte new file mode 100644 index 0000000..404b50e --- /dev/null +++ b/src/lib/components/admin/accounts/LaborSection.svelte @@ -0,0 +1,128 @@ + + +
+
+

Labor

+ +
+ + {#if currentLabor} +
+
+
+ + {formatCurrency(currentLabor.amount)} + + + Current + +
+
+ + +
+
+

+ As of {formatDate(currentLabor.startDate) || '—'} +

+
+ {:else} +

No current labor rate set.

+ {/if} + + {#if historicalLabors.length > 0} + + + {#if showHistory} +
+ {#each historicalLabors as labor (labor.id)} +
+
+ {formatCurrency(labor.amount)} + + ({formatDate(labor.startDate) || '—'} - {formatDate(labor.endDate) || 'Present'}) + +
+ onEdit(labor)} + onDelete={() => onDelete(labor.id)} + /> +
+ {/each} +
+ {/if} + {/if} +
diff --git a/src/lib/components/admin/accounts/RevenueForm.svelte b/src/lib/components/admin/accounts/RevenueForm.svelte new file mode 100644 index 0000000..b1e9774 --- /dev/null +++ b/src/lib/components/admin/accounts/RevenueForm.svelte @@ -0,0 +1,216 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+
+ +
+ $ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +

+ Leave end date empty to mark this as the current revenue rate. +

+ + {#if waveProducts.length > 0} +
+ + + {#if linkedProduct} +

+ Currently linked to: {linkedProduct.name} +

+ {/if} +
+ {/if} + +
+ + +
+
+
diff --git a/src/lib/components/admin/accounts/RevenueSection.svelte b/src/lib/components/admin/accounts/RevenueSection.svelte new file mode 100644 index 0000000..36d5ca0 --- /dev/null +++ b/src/lib/components/admin/accounts/RevenueSection.svelte @@ -0,0 +1,170 @@ + + +
+ + + {#if currentRevenue} + {@const linkedProduct = getLinkedProduct(currentRevenue.waveServiceId)} + +
+
+
+
+ + {formatCurrency(currentRevenue.amount)} + + Current + {#if currentRevenue.waveServiceId} + Linked + {/if} +
+

+ As of {formatDate(currentRevenue.startDate) || '—'} +

+ {#if linkedProduct} +

+ Wave: {linkedProduct.name} +

+

+ {currentRevenue.waveServiceId} +

+ {:else if currentRevenue.waveServiceId} +

+ {currentRevenue.waveServiceId} +

+ {/if} +
+
+ + +
+
+
+ {:else} +

No current revenue rate set.

+ {/if} + + + {#if historicalRevenues.length > 0} +
+ + + {#if showHistoricalRevenues} +
+ {#each historicalRevenues as revenue (revenue.id)} + {@const linkedProduct = getLinkedProduct(revenue.waveServiceId)} +
+
+
+ {formatCurrency(revenue.amount)} + {#if revenue.waveServiceId} + Linked + {/if} +
+

+ {formatDate(revenue.startDate) || '—'} - {formatDate(revenue.endDate) || + 'Present'} +

+ {#if linkedProduct} +

Wave: {linkedProduct.name}

+

{revenue.waveServiceId}

+ {:else if revenue.waveServiceId} +

{revenue.waveServiceId}

+ {/if} +
+ onEdit(revenue)} + onDelete={() => onDelete(revenue.id)} + /> +
+ {/each} +
+ {/if} +
+ {/if} +
diff --git a/src/lib/components/admin/accounts/ScheduleForm.svelte b/src/lib/components/admin/accounts/ScheduleForm.svelte new file mode 100644 index 0000000..543c454 --- /dev/null +++ b/src/lib/components/admin/accounts/ScheduleForm.svelte @@ -0,0 +1,347 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+ +
+ + +
+ + +
+
+ + +
+ +
+ + +
+
+ +

+ Leave end date empty to mark this as the current active schedule. +

+ + +
+

+ Service Days * +

+ + +
+ + + + + + + + + + + + + +
+ + +
+ +
+
+ + +
+ + +
+ + +
+ + +
+
+
diff --git a/src/lib/components/admin/accounts/ScopeForm.svelte b/src/lib/components/admin/accounts/ScopeForm.svelte new file mode 100644 index 0000000..c81e859 --- /dev/null +++ b/src/lib/components/admin/accounts/ScopeForm.svelte @@ -0,0 +1,164 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+
+ + +
+ +
+ + +
+ +
+
+ + +
+ {#if hasExistingActiveScope && !scope?.isActive} +

+ "{existingActiveScope?.name}" is currently the active scope. Deactivate it first to make + this scope active. +

+ {/if} +
+ +
+ + +
+
+
diff --git a/src/lib/components/admin/accounts/ScopeSection.svelte b/src/lib/components/admin/accounts/ScopeSection.svelte new file mode 100644 index 0000000..14ebf36 --- /dev/null +++ b/src/lib/components/admin/accounts/ScopeSection.svelte @@ -0,0 +1,700 @@ + + +
+ + {#if scopeError} +
+ + + + {scopeError} + {#if onClearError} + + {/if} +
+ {/if} + +
+

Scopes

+
+ {#if scopeTemplates.length > 0 && onAddScopeFromTemplate} + + {#if showTemplateDropdown} + + + +
+ +
+

+ From Template +

+ {#each scopeTemplates as template (template.id)} + + {/each} +
+ {/if} + {:else} + + {/if} +
+
+ + {#if activeScopes.length > 0 || inactiveScopes.length > 0} +
+ + {#each activeScopes as scope (scope.id)} + {@const isExpanded = expandedScopes.has(scope.id)} + {@const totalAreas = scope.areas?.length ?? 0} + {@const totalTasks = getTotalTasks(scope)} + +
+ +
+ +
+ + +
+
+ + + {#if isExpanded} +
+ {#if scope.description} +

{scope.description}

+ {/if} + + +
+
+ Areas + +
+ + {#if scope.areas && scope.areas.length > 0} +
+ {#each sortedAreas(scope.areas) as area (area.id)} + {@const taskCount = area.tasks?.length ?? 0} + {@const isAreaExpanded = expandedAreas.has(area.id)} +
+ +
+ +
+ + + +
+
+ + + {#if isAreaExpanded} +
+ {#if area.tasks && area.tasks.length > 0} +
+ {#each sortedTasks(area.tasks) as task (task.id)} +
+ +
+ + +
+ +
+
+ + Scope Description + + +

{task.description}

+
+
+ + Checklist Description + + +

{task.checklistDescription || '—'}

+
+
+ Frequency +

+ {getFrequencyShortLabel(task.frequency)} +

+
+
+
+ {/each} +
+ {:else} +

No tasks yet

+ {/if} +
+ {/if} +
+ {/each} +
+ {:else} +

No areas yet

+ {/if} +
+
+ {/if} +
+ {/each} + + + {#if inactiveScopes.length > 0} +
+ + + {#if showScopeHistory} +
+ {#each inactiveScopes as scope (scope.id)} + {@const isExpanded = expandedScopes.has(scope.id)} + {@const totalAreas = scope.areas?.length ?? 0} + {@const totalTasks = getTotalTasks(scope)} + +
+ +
+ +
+ + + {#if isExpanded} +
+ {#if scope.description} +

{scope.description}

+ {/if} + + +
+ Areas + + {#if scope.areas && scope.areas.length > 0} +
+ {#each sortedAreas(scope.areas) as area (area.id)} + {@const taskCount = area.tasks?.length ?? 0} + {@const isAreaExpanded = expandedAreas.has(area.id)} +
+ +
+ +
+ + + {#if isAreaExpanded} +
+ {#if area.tasks && area.tasks.length > 0} +
+ {#each sortedTasks(area.tasks) as task (task.id)} +
+ +
+
+ + Scope Description + +

{task.description}

+
+
+ + Checklist Description + +

+ {task.checklistDescription || '—'} +

+
+
+ Frequency +

+ {getFrequencyShortLabel(task.frequency)} +

+
+
+
+ {/each} +
+ {:else} +

+ No tasks +

+ {/if} +
+ {/if} +
+ {/each} +
+ {:else} +

No areas

+ {/if} +
+
+ {/if} +
+ {/each} +
+ {/if} +
+ {/if} +
+ {:else} +

No scopes defined for this address.

+ {/if} +
diff --git a/src/lib/components/admin/accounts/TaskForm.svelte b/src/lib/components/admin/accounts/TaskForm.svelte new file mode 100644 index 0000000..021be56 --- /dev/null +++ b/src/lib/components/admin/accounts/TaskForm.svelte @@ -0,0 +1,205 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+
+ + +
+ +
+ + +

Shortened version shown on service checklists

+
+ +
+ +
+ +
+ + + +
+
+
+ +
+ + +
+ +
+ + +
+
+
diff --git a/src/lib/components/admin/calendar/EventForm.svelte b/src/lib/components/admin/calendar/EventForm.svelte new file mode 100644 index 0000000..2b5aaad --- /dev/null +++ b/src/lib/components/admin/calendar/EventForm.svelte @@ -0,0 +1,575 @@ + + +
+
+ +
+ + +
+ + +
+ + +
+ + + {#if !isAllDay} +
+ + +
+ {/if} + + +
+ +
+ + + {#if !isAllDay} + + {/if} +
+ + +
+ + + {#if !isAllDay} + + {/if} +
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ Color (optional) +
+ + + {#each EVENT_COLORS as color (color.id)} + + {/each} +
+
+ + +
+ Attendees + {#if availableAttendees.length > 0} +
+ {#each availableAttendees as profile (profile.id)} + {@const selected = isAttendeeSelected(profile.email)} + + {/each} +
+ {#if selectedAttendeeEmails.size > 0} +

+ {selectedAttendeeEmails.size} attendee{selectedAttendeeEmails.size !== 1 ? 's' : ''} selected +

+ {/if} + {:else} +

+ No team members with email addresses available. +

+ {/if} +
+ + +
+ Reminders + + +
+ + +
+ + + {#if !useDefaultReminders} +
+ {#if customReminders.length === 0} +

No reminders set. Add one below.

+ {:else} + {#each customReminders as reminder, index (index)} +
+ + + + + + + + +
+ {/each} + {/if} + + + +
+ {/if} +
+ + {#if error} +
+

{error}

+
+ {/if} +
+ + +
+ + +
+
diff --git a/src/lib/components/admin/calendar/WeekView.svelte b/src/lib/components/admin/calendar/WeekView.svelte new file mode 100644 index 0000000..024a462 --- /dev/null +++ b/src/lib/components/admin/calendar/WeekView.svelte @@ -0,0 +1,145 @@ + + +
+ {#each dayTimestamps as ts (ts)} + {@const day = getDateFromTimestamp(ts)} + {@const dateKey = formatDateKey(day)} + {@const dayEvents = eventsByDay[dateKey] ?? []} + {@const today = isToday(day)} +
+ +
+ + {day.toLocaleDateString('en-US', { weekday: 'short' })} + + + {day.getDate()} + +
+ + +
+ {#each dayEvents as event (event.id)} + {@const eventColor = getEventColor(event.colorId)} + + {/each} + {#if dayEvents.length === 0} +
No events
+ {/if} +
+
+ {/each} +
diff --git a/src/lib/components/admin/customers/CustomerForm.svelte b/src/lib/components/admin/customers/CustomerForm.svelte new file mode 100644 index 0000000..f2d953d --- /dev/null +++ b/src/lib/components/admin/customers/CustomerForm.svelte @@ -0,0 +1,206 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ + {#if isEdit} +
+ + +
+ {/if} + +
+ + +
+
+
diff --git a/src/lib/components/admin/customers/WaveCustomerLinkForm.svelte b/src/lib/components/admin/customers/WaveCustomerLinkForm.svelte new file mode 100644 index 0000000..939ac46 --- /dev/null +++ b/src/lib/components/admin/customers/WaveCustomerLinkForm.svelte @@ -0,0 +1,210 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + + +
+
+ + + + +
+
+ + +
+ {#if filteredCustomers.length === 0 && !searchQuery} +
No Wave customers found.
+ {:else if filteredCustomers.length === 0} +
No customers match your search.
+ {:else} +
+ + + + {#each filteredCustomers as customer (customer.id)} + {@const decodedId = decodeWaveId(customer.id)} + + {/each} +
+ {/if} +
+ + +
+ + +
+
diff --git a/src/lib/components/admin/customers/WaveIntegrationSection.svelte b/src/lib/components/admin/customers/WaveIntegrationSection.svelte new file mode 100644 index 0000000..fd13525 --- /dev/null +++ b/src/lib/components/admin/customers/WaveIntegrationSection.svelte @@ -0,0 +1,93 @@ + + +
+ + + {#if linkedWaveCustomer} + +
+
+
+
+ + {linkedWaveCustomer.name} + + Linked +
+ {#if linkedWaveCustomer.email} +

+ {linkedWaveCustomer.email} +

+ {/if} +

+ {waveCustomerId} +

+
+ +
+
+ {:else if waveCustomerId} + +
+
+
+
+ + Linked to unknown Wave customer + + Unknown +
+

+ {waveCustomerId} +

+
+ +
+
+ {:else} +

+ No Wave customer linked. Click "Link" to connect this customer to Wave. +

+ {/if} +
diff --git a/src/lib/components/admin/invoices/AddProjectsModal.svelte b/src/lib/components/admin/invoices/AddProjectsModal.svelte new file mode 100644 index 0000000..ffc07a0 --- /dev/null +++ b/src/lib/components/admin/invoices/AddProjectsModal.svelte @@ -0,0 +1,210 @@ + + + 0} + emptyMessage="No additional completed projects available for this customer." + showTotal={true} + {selectedTotal} + colorScheme="accent" + onSave={handleSave} + onClose={handleClose} + {selectAll} + {selectNone} +> + {#each availableProjects as project (project.id)} + + {/each} + diff --git a/src/lib/components/admin/invoices/AddRevenuesModal.svelte b/src/lib/components/admin/invoices/AddRevenuesModal.svelte new file mode 100644 index 0000000..25492c1 --- /dev/null +++ b/src/lib/components/admin/invoices/AddRevenuesModal.svelte @@ -0,0 +1,173 @@ + + + 0} + emptyMessage="No additional revenues available for this customer's accounts." + showTotal={true} + {selectedTotal} + colorScheme="secondary" + onSave={handleSave} + onClose={handleClose} + {selectAll} + {selectNone} +> + {#each availableRevenues as revenue (revenue.id)} + + {/each} + diff --git a/src/lib/components/admin/invoices/InvoiceForm.svelte b/src/lib/components/admin/invoices/InvoiceForm.svelte new file mode 100644 index 0000000..0728ec0 --- /dev/null +++ b/src/lib/components/admin/invoices/InvoiceForm.svelte @@ -0,0 +1,230 @@ + + +
+
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + {#if showPaymentFields} +
+

Payment Details

+ + +
+ + +
+ + +
+ + +
+
+ {/if} + + {#if error} +
+

{error}

+
+ {/if} +
+ + +
+ + +
+
diff --git a/src/lib/components/admin/invoices/InvoiceTabs.svelte b/src/lib/components/admin/invoices/InvoiceTabs.svelte new file mode 100644 index 0000000..cd08262 --- /dev/null +++ b/src/lib/components/admin/invoices/InvoiceTabs.svelte @@ -0,0 +1,136 @@ + + + +
+
+ + +
+ {tabs[currentTabIndex].label} + {#if getBadgeForTab(tabs[currentTabIndex].id)} + {@const badge = getBadgeForTab(tabs[currentTabIndex].id)} + {#if badge} + + {badge.text} + + {/if} + {/if} +
+ + +
+ + +
+ {#each tabs as tab, i (tab.id)} + + {/each} +
+
+ + + diff --git a/src/lib/components/admin/invoices/wave/WaveCustomerForm.svelte b/src/lib/components/admin/invoices/wave/WaveCustomerForm.svelte new file mode 100644 index 0000000..57d6622 --- /dev/null +++ b/src/lib/components/admin/invoices/wave/WaveCustomerForm.svelte @@ -0,0 +1,278 @@ + + +
{ + isSubmitting = true; + error = ''; + return async ({ result }) => { + isSubmitting = false; + if (result.type === 'failure') { + const errorMsg = result.data?.error; + error = typeof errorMsg === 'string' ? errorMsg : 'An error occurred'; + } else if (result.type === 'success') { + await invalidateAll(); + offCanvas.closeRight(); + onSuccess?.(); + } + }; + }} + class="flex h-full flex-col" +> + {#if isEditing} + + {/if} + +
+ {#if error} +
+ {error} +
+ {/if} + + +
+

Basic Information

+ +
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+
+ + +
+

Address

+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+
+ + +
+

Internal Notes

+ +
+ + +
+
+
+ +
+ + +
+
diff --git a/src/lib/components/admin/invoices/wave/WaveCustomersTab.svelte b/src/lib/components/admin/invoices/wave/WaveCustomersTab.svelte new file mode 100644 index 0000000..7a44c76 --- /dev/null +++ b/src/lib/components/admin/invoices/wave/WaveCustomersTab.svelte @@ -0,0 +1,248 @@ + + +{#snippet createFormContent()} + +{/snippet} + +{#snippet editFormContent()} + +{/snippet} + +
+ +
+
+ + + + +
+ + +
+ + + {#if filteredCustomers.length > 0} +
+ {#each filteredCustomers as customer (customer.id)} +
+
+
+

{customer.name}

+ {#if parseFloat(customer.overdueAmount.value) > 0} + + Overdue + + {/if} +
+ +
+ {#if customer.email} + + + + + {customer.email} + + {/if} + {#if customer.phone} + + + + + {customer.phone} + + {/if} +
+ + {#if parseFloat(customer.outstandingAmount.value) > 0} +
+ + Outstanding: {formatCurrency(customer.outstandingAmount.value)} + + {#if parseFloat(customer.overdueAmount.value) > 0} + + Overdue: {formatCurrency(customer.overdueAmount.value)} + + {/if} +
+ {/if} +
+ +
+ + +
+
+ {/each} +
+ {:else if searchQuery} +
+ + + +

No Customers Found

+

No customers match your search criteria.

+
+ {:else} +
+ + + +

No Customers

+

Get started by adding your first customer.

+ +
+ {/if} +
diff --git a/src/lib/components/admin/invoices/wave/WaveInvoiceEditForm.svelte b/src/lib/components/admin/invoices/wave/WaveInvoiceEditForm.svelte new file mode 100644 index 0000000..75d71b4 --- /dev/null +++ b/src/lib/components/admin/invoices/wave/WaveInvoiceEditForm.svelte @@ -0,0 +1,571 @@ + + +
{ + submitting = true; + error = ''; + return async ({ result }) => { + submitting = false; + if (result.type === 'failure') { + const errorMsg = result.data?.error; + error = typeof errorMsg === 'string' ? errorMsg : 'Failed to update invoice'; + } else if (result.type === 'success') { + onSuccess(); + } + }; + }} + class="space-y-6" +> + + + + + + +
+
+
+

+ Edit Invoice #{waveInvoice.invoiceNumber} +

+

Modify invoice details and line items.

+
+ +
+
+ + {#if error} +
+ {error} +
+ {/if} + + +
+

Invoice Details

+
+
+ + +
+
+ + +
+
+
+ + +
+
+

+ Line Items ({lineItems.length}) +

+ {#if !showAddLineItem} + + {/if} +
+ +
+ {#each lineItems as item, index (item.productId + '-' + index)} +
+
+
+ {item.productName} +
+
+ + {formatCurrency(item.quantity * item.unitPrice)} + + +
+
+ +
+
+ + updateLineItem(index, 'description', e.currentTarget.value)} + class="w-full rounded border border-theme bg-theme px-2 py-1.5 text-sm text-theme focus:border-primary-500 focus:ring-1 focus:ring-primary-500" + /> +
+
+
+ + + updateLineItem(index, 'quantity', parseFloat(e.currentTarget.value) || 0)} + class="w-full rounded border border-theme bg-theme px-2 py-1.5 text-sm text-theme focus:border-primary-500 focus:ring-1 focus:ring-primary-500" + /> +
+
+ + + updateLineItem(index, 'unitPrice', parseFloat(e.currentTarget.value) || 0)} + class="w-full rounded border border-theme bg-theme px-2 py-1.5 text-sm text-theme focus:border-primary-500 focus:ring-1 focus:ring-primary-500" + /> +
+
+
+
+ {/each} + + + {#if showAddLineItem} +
+
Add New Line Item
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ {/if} +
+ + +
+ Subtotal + + {formatCurrency(subtotal)} + +
+ + + {#if hasDiscount && discountAmount() > 0} +
+ Discount ({discountName}) + + -{formatCurrency(discountAmount())} + +
+ {/if} + + +
+ Total + + {formatCurrency(total)} + +
+
+ + +
+
+

Discount

+ +
+ + {#if hasDiscount} +
+
+
+ + +
+
+ + +
+
+ + +
+
+ {#if discountValue > 0} +

+ {#if discountType === 'PERCENTAGE'} + {discountValue}% off = -{formatCurrency(discountAmount())} + {:else} + Fixed discount of {formatCurrency(discountValue)} + {/if} +

+ {/if} +
+ {/if} +
+ + +
+

Additional Notes

+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
diff --git a/src/lib/components/admin/invoices/wave/WaveInvoiceForm.svelte b/src/lib/components/admin/invoices/wave/WaveInvoiceForm.svelte new file mode 100644 index 0000000..fcb8f6e --- /dev/null +++ b/src/lib/components/admin/invoices/wave/WaveInvoiceForm.svelte @@ -0,0 +1,714 @@ + + +
{ + submitting = true; + error = ''; + return async ({ result }) => { + submitting = false; + if (result.type === 'failure') { + const errorMsg = result.data?.error; + error = typeof errorMsg === 'string' ? errorMsg : 'Failed to create invoice'; + } else if (result.type === 'success') { + onSuccess(); + } + }; + }} + class="space-y-6" +> + + + + + + +
+
+

Create Wave Invoice

+ +
+
+ + +
+
+ + + +
+

Review before submitting

+

+ Please review and adjust the descriptions and amounts below. This will create a draft + invoice in Wave — you'll be able to preview and make final edits before sending. +

+
+
+
+ + {#if error} +
+ {error} +
+ {/if} + + +
+

Invoice Details

+
+
+ + +
+
+ + +
+
+
+ + +
+
+

Line Items

+ +
+ + + {#if showAddLineItem} +
+
Add Line Item
+
+
+ + +
+ + {#if newProductId} +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ {/if} +
+
+ {/if} + +
+ {#each lineItems as item, index (item.sourceId)} +
+
+
+ + {getSourceTypeLabel(item.sourceType)} + + {item.productName} +
+
+ + {formatCurrency(item.quantity * item.unitPrice)} + + {#if item.sourceType === 'additional'} + + {/if} +
+
+ +
+
+ + updateLineItem(index, 'description', e.currentTarget.value)} + class="w-full rounded border border-theme bg-theme px-2 py-1.5 text-sm text-theme focus:border-primary-500 focus:ring-1 focus:ring-primary-500" + /> +
+
+
+ + + updateLineItem(index, 'quantity', parseFloat(e.currentTarget.value) || 0)} + class="w-full rounded border border-theme bg-theme px-2 py-1.5 text-sm text-theme focus:border-primary-500 focus:ring-1 focus:ring-primary-500" + /> +
+
+ + + updateLineItem(index, 'unitPrice', parseFloat(e.currentTarget.value) || 0)} + class="w-full rounded border border-theme bg-theme px-2 py-1.5 text-sm text-theme focus:border-primary-500 focus:ring-1 focus:ring-primary-500" + /> +
+
+
+
+ {/each} +
+ + +
+
+ Subtotal + + {formatCurrency(subtotal)} + +
+ + {#if hasDiscount && discountValue > 0} +
+ + {discountName} + {#if discountType === 'PERCENTAGE'} + ({discountValue}%) + {/if} + + + -{formatCurrency(discountAmount())} + +
+ {/if} + +
+ Total + + {formatCurrency(total)} + +
+
+
+ + +
+
+

Discount

+ +
+ + {#if hasDiscount} +
+
+
+ + +
+
+ + +
+
+ +
+ {#if discountType === 'FIXED'} + $ + {/if} + + {#if discountType === 'PERCENTAGE'} + % + {/if} +
+
+
+
+ {/if} +
+ + +
+

Additional Notes

+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
diff --git a/src/lib/components/admin/invoices/wave/WaveInvoicePreview.svelte b/src/lib/components/admin/invoices/wave/WaveInvoicePreview.svelte new file mode 100644 index 0000000..daf1524 --- /dev/null +++ b/src/lib/components/admin/invoices/wave/WaveInvoicePreview.svelte @@ -0,0 +1,416 @@ + + +
+ +
+
+
+
+ + + +
+
+
+

+ Invoice #{waveInvoice.invoiceNumber} +

+ + {getStatusLabel(waveInvoice.status)} + +
+

{waveInvoice.customer.name}

+
+
+ +
+ + + + + Download PDF + + {#if canEdit && onEdit} + + {/if} + {#if canApprove && onApprove} + + {/if} + {#if canSend} + + {/if} + + {#if canDelete && onDelete} + + {/if} +
+
+
+ + +
+

Invoice Summary

+ +
+
+ Subtotal + + {formatCurrency(waveInvoice.subtotal.value, waveInvoice.subtotal.currency.symbol)} + +
+ + {#if parseFloat(waveInvoice.discountTotal.value) > 0} +
+ Discounts + + -{formatCurrency( + waveInvoice.discountTotal.value, + waveInvoice.discountTotal.currency.symbol + )} + +
+ {/if} + + {#if parseFloat(waveInvoice.taxTotal.value) > 0} +
+ Tax + + {formatCurrency(waveInvoice.taxTotal.value, waveInvoice.taxTotal.currency.symbol)} + +
+ {/if} + +
+ Total + + {formatCurrency(waveInvoice.total.value, waveInvoice.total.currency.symbol)} + +
+ + {#if parseFloat(waveInvoice.amountPaid.value) > 0} +
+ Amount Paid + + {formatCurrency(waveInvoice.amountPaid.value, waveInvoice.amountPaid.currency.symbol)} + +
+ {/if} + + {#if parseFloat(waveInvoice.amountDue.value) > 0} +
+ Amount Due + + {formatCurrency(waveInvoice.amountDue.value, waveInvoice.amountDue.currency.symbol)} + +
+ {/if} +
+
+ + +
+

+ Line Items ({waveInvoice.items.length}) +

+ +
+ {#each waveInvoice.items as item, index (index)} +
+
+
+

{item.product.name}

+ {#if item.description} +

{item.description}

+ {/if} +

+ {item.quantity} x {formatCurrency(String(item.unitPrice))} +

+
+ + {formatCurrency(item.total.value)} + +
+
+ {/each} +
+
+ + +
+

Invoice Details

+ +
+
+

Invoice Date

+

{formatDate(waveInvoice.invoiceDate)}

+
+
+

Due Date

+

{formatDate(waveInvoice.dueDate)}

+
+ {#if waveInvoice.poNumber} +
+

PO Number

+

{waveInvoice.poNumber}

+
+ {/if} + {#if waveInvoice.lastSentAt} +
+

Last Sent

+

+ {formatDateTime(waveInvoice.lastSentAt)} + {#if waveInvoice.lastSentVia} + via {waveInvoice.lastSentVia} + {/if} +

+
+ {/if} + {#if waveInvoice.lastViewedAt} +
+

Last Viewed

+

{formatDateTime(waveInvoice.lastViewedAt)}

+
+ {/if} +
+
+ + + {#if waveInvoice.memo || waveInvoice.footer} +
+

Notes

+ +
+ {#if waveInvoice.memo} +
+

Memo

+

{waveInvoice.memo}

+
+ {/if} + {#if waveInvoice.footer} +
+

Footer

+

{waveInvoice.footer}

+
+ {/if} +
+
+ {/if} +
diff --git a/src/lib/components/admin/invoices/wave/WaveLinkProductModal.svelte b/src/lib/components/admin/invoices/wave/WaveLinkProductModal.svelte new file mode 100644 index 0000000..1a19c33 --- /dev/null +++ b/src/lib/components/admin/invoices/wave/WaveLinkProductModal.svelte @@ -0,0 +1,293 @@ + + +{#if open} + + +{/if} diff --git a/src/lib/components/admin/invoices/wave/WaveProductForm.svelte b/src/lib/components/admin/invoices/wave/WaveProductForm.svelte new file mode 100644 index 0000000..cf4b42f --- /dev/null +++ b/src/lib/components/admin/invoices/wave/WaveProductForm.svelte @@ -0,0 +1,163 @@ + + +
{ + isSubmitting = true; + error = ''; + return async ({ result }) => { + isSubmitting = false; + if (result.type === 'failure') { + const errorMsg = result.data?.error; + error = typeof errorMsg === 'string' ? errorMsg : 'An error occurred'; + } else if (result.type === 'success') { + await invalidateAll(); + offCanvas.closeRight(); + onSuccess?.(); + } + }; + }} + class="flex h-full flex-col" +> + {#if isEditing} + + {/if} + +
+ {#if error} +
+ {error} +
+ {/if} + +
+ + +
+ +
+ +
+ $ + +
+
+ +
+ + +
+ + {#if incomeAccounts.length > 0} +
+ + +
+ {/if} +
+ +
+ + +
+
diff --git a/src/lib/components/admin/invoices/wave/WaveProductsTab.svelte b/src/lib/components/admin/invoices/wave/WaveProductsTab.svelte new file mode 100644 index 0000000..d228ed5 --- /dev/null +++ b/src/lib/components/admin/invoices/wave/WaveProductsTab.svelte @@ -0,0 +1,260 @@ + + +{#snippet createFormContent()} + +{/snippet} + +{#snippet editFormContent()} + +{/snippet} + +
+ +
+
+
+ + + + +
+ + {#if archivedProducts.length > 0} + + {/if} +
+ + {#if !showArchived} + + {/if} +
+ + + {#if activeProducts.length > 0} +
+ {#each activeProducts as product (product.id)} +
+
+
+

{product.name}

+ {#if showArchived} + + Archived + + {/if} + {#if product.isSold} + + Sold + + {/if} + {#if product.isBought} + + Bought + + {/if} +
+ {#if product.description} +

{product.description}

+ {/if} +
+

+ {formatCurrency(product.unitPrice)} +

+ {#if product.incomeAccount} + + {product.incomeAccount.name} + + {/if} +
+
+ + {#if !showArchived} +
+ + +
+ {/if} +
+ {/each} +
+ {:else if searchQuery} +
+ + + +

No Products Found

+

No products match your search criteria.

+
+ {:else} +
+ + + +

No Products

+

Get started by creating your first product or service.

+ +
+ {/if} +
diff --git a/src/lib/components/admin/invoices/wave/WaveTabs.svelte b/src/lib/components/admin/invoices/wave/WaveTabs.svelte new file mode 100644 index 0000000..46d9039 --- /dev/null +++ b/src/lib/components/admin/invoices/wave/WaveTabs.svelte @@ -0,0 +1,129 @@ + + + +
+
+ + +
+ {tabs[currentTabIndex].label} + + {getCountForTab(tabs[currentTabIndex].id)} + +
+ + +
+ + +
+ {#each tabs as tab, i (tab.id)} + + {/each} +
+
+ + + diff --git a/src/lib/components/admin/invoices/wave/WaveValidationSection.svelte b/src/lib/components/admin/invoices/wave/WaveValidationSection.svelte new file mode 100644 index 0000000..d51ca2f --- /dev/null +++ b/src/lib/components/admin/invoices/wave/WaveValidationSection.svelte @@ -0,0 +1,356 @@ + + +
+ +
+ {#if isValid} +
+
+ + + +
+
+

Ready to create Wave invoice

+

+ All items are linked to Wave. Click below to create the invoice. +

+
+
+ + {:else if !hasLinkedItems && customerLinked && totalErrors === 0} +
+
+ + + +
+
+

No line items

+

+ Add at least one linked revenue or project to create the invoice. +

+
+
+ {:else} +
+
+ + + +
+
+

+ {totalErrors} item{totalErrors !== 1 ? 's' : ''} need{totalErrors === 1 ? 's' : ''} linking +

+

+ Link all items to Wave products before creating the invoice. +

+
+
+ {/if} +
+ + +
+

Customer

+ +
+
+
+
+ {customer.customerName} + {#if customerLinked} + Linked + {:else} + Not Linked + {/if} +
+ {#if customerLinked} +

+ Wave Customer ID: {customer.waveCustomerId} +

+ {:else} +

+ Link this customer to a Wave customer to continue. +

+ {/if} +
+ +
+
+
+ + +
+
+

Revenues

+ {#if unlinkedRevenues.length > 0} + + {unlinkedRevenues.length} unlinked + + {:else if revenues.length > 0} + + All linked + + {/if} +
+ + {#if revenues.length === 0} +

No revenues on this invoice.

+ {:else} +
+ {#each revenues as revenue (revenue.id)} + {@const accountName = getAccountName(revenue.accountId)} + {@const isLinked = !!revenue.waveServiceId} +
+
+
+
+ {accountName} + {#if isLinked} + Linked + {:else} + Not Linked + {/if} +
+

+ {formatShortDate(revenue.startDate)} + {#if revenue.endDate} + - {formatShortDate(revenue.endDate)} + {:else} + - Ongoing + {/if} +

+ {#if isLinked} +

+ Product: {getProductName(revenue.waveServiceId)} +

+ {/if} +
+
+ + {formatCurrency(revenue.amount)} + + +
+
+
+ {/each} +
+ {/if} +
+ + +
+
+

Projects

+ {#if unlinkedProjects.length > 0} + + {unlinkedProjects.length} unlinked + + {:else if projects.length > 0} + + All linked + + {/if} +
+ + {#if projects.length === 0} +

No projects on this invoice.

+ {:else} +
+ {#each projects as project (project.id)} + {@const isLinked = !!project.waveServiceId} +
+
+
+
+ {project.name} + {#if isLinked} + Linked + {:else} + Not Linked + {/if} +
+

{formatShortDate(project.date)}

+ {#if isLinked} +

+ Product: {getProductName(project.waveServiceId)} +

+ {/if} +
+
+ + {formatCurrency(project.amount)} + + +
+
+
+ {/each} +
+ {/if} +
+
diff --git a/src/lib/components/admin/invoices/wave/types.ts b/src/lib/components/admin/invoices/wave/types.ts new file mode 100644 index 0000000..52ccb6d --- /dev/null +++ b/src/lib/components/admin/invoices/wave/types.ts @@ -0,0 +1,146 @@ +// Wave types (duplicated from page.server.ts since we can't import from there) +export interface WaveAddress { + addressLine1: string | null; + addressLine2: string | null; + city: string | null; + province: { code: string; name: string } | null; + country: { code: string; name: string } | null; + postalCode: string | null; +} + +export interface WaveCustomer { + id: string; + name: string; + firstName: string | null; + lastName: string | null; + email: string | null; + phone: string | null; + mobile: string | null; + address: WaveAddress | null; + currency: { code: string; symbol: string } | null; + internalNotes: string | null; + outstandingAmount: { value: string; currency: { code: string } }; + overdueAmount: { value: string; currency: { code: string } }; + isArchived: boolean | null; + createdAt: string; + modifiedAt: string; +} + +// Base WaveProduct with required fields - used across all pages +export interface WaveProduct { + id: string; + name: string; + description: string | null; + unitPrice: number; + isArchived: boolean; + // Optional extended fields - available on Wave admin page + isSold?: boolean; + isBought?: boolean; + incomeAccount?: { id: string; name: string } | null; + expenseAccount?: { id: string; name: string } | null; + defaultSalesTaxes?: Array<{ id: string; name: string; abbreviation: string }>; + createdAt?: string; + modifiedAt?: string; +} + +export interface WaveInvoiceItem { + product: { id: string; name: string }; + description: string | null; + quantity: number; + unitPrice: number; + subtotal: { value: string }; + total: { value: string }; +} + +export interface WaveInvoice { + id: string; + invoiceNumber: string; + invoiceDate: string; + dueDate: string; + status: string; + title: string; + subhead: string | null; + poNumber: string | null; + memo: string | null; + footer: string | null; + pdfUrl: string; + viewUrl: string; + customer: { id: string; name: string; email: string | null }; + items: WaveInvoiceItem[]; + subtotal: { value: string; currency: { code: string; symbol: string } }; + taxTotal: { value: string; currency: { code: string; symbol: string } }; + discountTotal: { value: string; currency: { code: string; symbol: string } }; + total: { value: string; currency: { code: string; symbol: string } }; + amountDue: { value: string; currency: { code: string; symbol: string } }; + amountPaid: { value: string; currency: { code: string; symbol: string } }; + lastSentAt: string | null; + lastSentVia: string | null; + lastViewedAt: string | null; +} + +// Types for revenue and project data from Nexus invoice +export interface NexusRevenue { + id: string; + accountId: string; + amount: string | number; + startDate: string; + endDate: string | null; + waveServiceId: string | null; +} + +export interface NexusProject { + id: string; + name: string; + date: string; + amount: string | number; + customerId: string; + accountAddressId: string | null; + waveServiceId: string | null; +} + +export interface NexusInvoice { + id: string; + date: string; + customerId: string; + status: string; + datePaid: string | null; + paymentType: string | null; + waveInvoiceId: string | null; + revenues: NexusRevenue[]; + projects: NexusProject[]; +} + +// Types for the invoice form +export interface WaveLineItem { + sourceType: 'revenue' | 'project' | 'additional'; + sourceId: string; + productId: string; + productName: string; + description: string; + quantity: number; + unitPrice: number; +} + +export interface WaveDiscount { + type: 'PERCENTAGE' | 'FIXED'; + name: string; + value: number; // percentage (0-100) or fixed amount +} + +export interface WaveInvoiceFormData { + invoiceDate: string; + dueDate: string; + memo: string; + footer: string; + items: WaveLineItem[]; + discount?: WaveDiscount; +} + +// Validation types +export interface ValidationIssue { + type: 'customer' | 'revenue' | 'project'; + id: string; + description: string; +} + +export type InvoiceTab = 'details' | 'wave'; diff --git a/src/lib/components/admin/notifications/ChannelSelector.svelte b/src/lib/components/admin/notifications/ChannelSelector.svelte new file mode 100644 index 0000000..8b81c73 --- /dev/null +++ b/src/lib/components/admin/notifications/ChannelSelector.svelte @@ -0,0 +1,95 @@ + + +
+ + Notification Channels * + + +
+ {#each channels as channel (channel.value)} + + {/each} +
+ + {#if selected.length === 0} +

Please select at least one channel

+ {/if} +
diff --git a/src/lib/components/admin/notifications/EventTypeSelector.svelte b/src/lib/components/admin/notifications/EventTypeSelector.svelte new file mode 100644 index 0000000..1cd4fed --- /dev/null +++ b/src/lib/components/admin/notifications/EventTypeSelector.svelte @@ -0,0 +1,241 @@ + + +
+
+ + Event Types * + ({selectedSet.size} selected) + +
+ + +
+
+ + +
+ + + + +
+ + +
+ {#each filteredCategories as category (category.name)} + {@const selectedCount = getSelectedCountInCategory(category.name)} + {@const totalCount = category.types.length} + {@const isExpanded = expandedCategories.has(category.name)} + +
+ + + + + {#if isExpanded} +
+ +
+ + +
+ + +
+ {#each category.types as eventType (eventType.value)} + + {/each} +
+
+ {/if} +
+ {/each} + + {#if filteredCategories.length === 0} +

+ No event types match "{searchFilter}" +

+ {/if} +
+ + {#if selectedSet.size === 0} +

Please select at least one event type

+ {/if} +
diff --git a/src/lib/components/admin/notifications/NotificationRuleForm.svelte b/src/lib/components/admin/notifications/NotificationRuleForm.svelte new file mode 100644 index 0000000..5baca37 --- /dev/null +++ b/src/lib/components/admin/notifications/NotificationRuleForm.svelte @@ -0,0 +1,396 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+ +
+

Basic Info

+ +
+ + +
+ +
+ + +
+ + + +
+ +
+ + +
+

+ Trigger Events +

+ +
+ +
+ + +
+

+ Delivery Channels +

+ +
+ +
+ + +
+

Recipients

+ + +
+ Target by Role +

+ Leave empty to notify all roles. Select specific roles to limit recipients. +

+
+ {#each roleOptions as role (role.value)} + + {/each} +
+ {#if targetRoles.length === 0} +

+ All team members will receive notifications +

+ {/if} +
+ + + + + + +
+ +
+ + +
+

+ Message Template +

+ +
+ + +
+ + + {#if showAdvanced} +
+
+ +

+ Add JSON conditions to filter events. Example: {conditionsExample} +

+ + {#if conditionsError} +

{conditionsError}

+ {/if} +
+
+ {/if} +
+ + +
+ + +
+
+
diff --git a/src/lib/components/admin/notifications/TargetSelector.svelte b/src/lib/components/admin/notifications/TargetSelector.svelte new file mode 100644 index 0000000..311e2cf --- /dev/null +++ b/src/lib/components/admin/notifications/TargetSelector.svelte @@ -0,0 +1,205 @@ + + +
+ + + {#if expanded} +
+ +
+
+ + + + +
+
+ + +
+ + + {selectedSet.size} selected +
+ + +
+ {#if filteredProfiles.length === 0} +

No profiles found

+ {:else} + {#each filteredProfiles as profile (profile.id)} + + {/each} + {/if} +
+
+ {/if} +
diff --git a/src/lib/components/admin/notifications/TemplateEditor.svelte b/src/lib/components/admin/notifications/TemplateEditor.svelte new file mode 100644 index 0000000..867dacb --- /dev/null +++ b/src/lib/components/admin/notifications/TemplateEditor.svelte @@ -0,0 +1,113 @@ + + +
+
+

Message Template

+ +
+ + {#if showVariableHints} +
+ +
+

Universal Variables:

+
+ {#each UNIVERSAL_FIELDS as field (field.name)} + + + {`{${field.name}}`} + + + + {/each} +
+
+ + + {#if activeDomains.length > 0} + {#each activeDomains as domain (domain.name)} +
+

{domain.label} Fields:

+
+ {#each domain.fields as field (field.name)} + + + {`{${field.name}}`} + + + + {/each} +
+
+ {/each} + {:else} +

+ Select event types above to see available metadata fields. +

+ {/if} +
+ {/if} + +
+ + +
+ +
+ + +
+
diff --git a/src/lib/components/admin/profiles/CustomerProfileForm.svelte b/src/lib/components/admin/profiles/CustomerProfileForm.svelte new file mode 100644 index 0000000..a94f802 --- /dev/null +++ b/src/lib/components/admin/profiles/CustomerProfileForm.svelte @@ -0,0 +1,259 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+ {#if isEdit && profile} +
+ Profile ID +

{fromGlobalId(profile.id)}

+
+ {/if} + +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ Customer Access +

Select which customers this profile can access.

+ {#if activeCustomers.length > 0} +
+ {#each activeCustomers as customer (customer.id)} + + {/each} +
+ {:else} +

No active customers available.

+ {/if} +
+ +
+ + +
+ +
+ + +
+
+
diff --git a/src/lib/components/admin/profiles/TeamProfileForm.svelte b/src/lib/components/admin/profiles/TeamProfileForm.svelte new file mode 100644 index 0000000..c1f40d9 --- /dev/null +++ b/src/lib/components/admin/profiles/TeamProfileForm.svelte @@ -0,0 +1,242 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+ {#if isEdit && profile} +
+ Profile ID +

{fromGlobalId(profile.id)}

+
+ {/if} + +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
diff --git a/src/lib/components/admin/projects/ProjectCategoryForm.svelte b/src/lib/components/admin/projects/ProjectCategoryForm.svelte new file mode 100644 index 0000000..70e2b91 --- /dev/null +++ b/src/lib/components/admin/projects/ProjectCategoryForm.svelte @@ -0,0 +1,126 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+
+ + +
+ +
+ + +

Lower numbers appear first

+
+ +
+ + +
+
+
diff --git a/src/lib/components/admin/projects/ProjectDetailsCard.svelte b/src/lib/components/admin/projects/ProjectDetailsCard.svelte new file mode 100644 index 0000000..16e5559 --- /dev/null +++ b/src/lib/components/admin/projects/ProjectDetailsCard.svelte @@ -0,0 +1,298 @@ + + +
+
+

Project Details

+ {#if calendarEventId} + + + + + View Event + + {:else if onScheduleEvent} + + {/if} +
+
+ +
+

Status

+
+ + {status.replace('_', ' ')} + + {#if isDispatched} + + Dispatched + + {/if} +
+
+ + +
+

Date

+

{formatDate(date)}

+
+ + +
+

Amount

+

{formatCurrency(amount)}

+
+ + +
+

Labor

+

{formatCurrency(labor)}

+
+
+ + + {#if address} +
+

Address

+

{address}

+
+ {/if} + + + {#if teamMembers.length > 0} +
+
+

Team

+ {#if messagableMembers.length >= 2} + + {/if} +
+
+ {#each teamMembers as member (member.id)} +
+ + {member.role === 'TEAM_LEADER' ? 'TL' : 'TM'} + + {#if member.role !== 'ADMIN'} + + {:else} + + + {/if} + {member.fullName} +
+ {/each} +
+
+ {/if} + + + {#if waveServiceId} +
+

Wave Integration

+
+ Linked + {#if linkedProduct} + {linkedProduct.name} + {/if} +
+

{waveServiceId}

+
+ {/if} + + + {#if notes} +
+

Notes

+

{notes}

+
+ {/if} +
diff --git a/src/lib/components/admin/projects/ProjectForm.svelte b/src/lib/components/admin/projects/ProjectForm.svelte new file mode 100644 index 0000000..b12b2d7 --- /dev/null +++ b/src/lib/components/admin/projects/ProjectForm.svelte @@ -0,0 +1,769 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+ +
+ +
+ +
+ + + +
+
+
+ + + {#if customerId} +
+
+

Project Location

+ {#if customerAccounts().length > 0} + + {/if} +
+ + {#if !useManualAddress && customerAccounts().length > 0} + +
+ +
+ +
+ + + +
+
+
+ + + {#if selectedAccountId && accountAddresses().length > 0} +
+ + {#if accountAddresses().length === 1} + + {@const address = accountAddresses()[0]} +
+
+ + + + +
+

{address.streetAddress}

+

+ {address.city}, {address.state} + {address.zipCode} +

+
+
+
+ {:else} + +
+ +
+ + + +
+
+ {/if} +
+ + + {#if accountAddresses().length > 1 && selectedAddressDetails()} + {@const details = selectedAddressDetails()} +
+
+ + + + +
+

{details?.address.streetAddress}

+

+ {details?.address.city}, {details?.address.state} + {details?.address.zipCode} +

+
+
+
+ {/if} + {:else if selectedAccountId && accountAddresses().length === 0} +

+ This account has no addresses. Please enter address manually. +

+ {/if} + {:else} + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ {/if} +
+ {/if} + + +
+ + +
+ + +
+ + +
+ + +
+
+ +
+ $ + +
+
+
+ +
+ $ + +
+
+
+ + + {#if isEdit} +
+ +
+ +
+ + + +
+
+
+ {/if} + + +
+
+

Dispatched

+

Required before assigning team members

+
+ +
+ + +
+

Team Members

+
+ {#if !isDispatched} +

Enable "Dispatched" to assign team members

+ {:else if nonAdminTeamMembers.length > 0} + {#each nonAdminTeamMembers as member (member.id)} + + {/each} + {:else} +

No team members available

+ {/if} +
+
+ + +
+ + +
+ + + {#if waveProducts.length > 0} +
+ + + {#if linkedProduct} +

+ Currently linked to: {linkedProduct.name} +

+ {/if} +
+ {/if} + + +
+ + +
+
+
diff --git a/src/lib/components/admin/projects/ProjectScopeForm.svelte b/src/lib/components/admin/projects/ProjectScopeForm.svelte new file mode 100644 index 0000000..22b1970 --- /dev/null +++ b/src/lib/components/admin/projects/ProjectScopeForm.svelte @@ -0,0 +1,142 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ +
+ + +
+
+
diff --git a/src/lib/components/admin/projects/ProjectScopeSection.svelte b/src/lib/components/admin/projects/ProjectScopeSection.svelte new file mode 100644 index 0000000..88612af --- /dev/null +++ b/src/lib/components/admin/projects/ProjectScopeSection.svelte @@ -0,0 +1,365 @@ + + +
+ +
+
+
+

{scope.name}

+ {#if scope.isActive} + + Active + + {/if} +
+ {#if scope.description} +

{scope.description}

+ {/if} +

+ {scope.projectAreas?.length ?? 0} + {(scope.projectAreas?.length ?? 0) === 1 ? 'category' : 'categories'}, {getTotalTasks()} + {getTotalTasks() === 1 ? 'task' : 'tasks'} +

+
+ {#if editable && onEditScope && onDeleteScope} +
+ + +
+ {/if} +
+ + +
+
+

Categories

+ {#if editable && onAddCategory} + + {/if} +
+ + {#if scope.projectAreas && scope.projectAreas.length > 0} +
+ {#each sortedCategories(scope.projectAreas) as category (category.id)} + {@const taskCount = category.projectTasks?.length ?? 0} + {@const isCategoryExpanded = expandedCategories.has(category.id)} +
+ +
+ + {#if editable && onAddTask && onEditCategory && onDeleteCategory} +
+ + + +
+ {/if} +
+ + + {#if isCategoryExpanded} +
+ {#if category.projectTasks && category.projectTasks.length > 0} +
+ {#each sortedTasks(category.projectTasks) as task (task.id)} +
+ + {#if editable && onEditTask && onDeleteTask} +
+ + +
+ {/if} + +
+
+ + Scope Description + + +

{task.description}

+
+
+ + Checklist Description + + +

{task.checklistDescription || '—'}

+
+
+
+ {/each} +
+ {:else} +

No tasks yet

+ {/if} +
+ {/if} +
+ {/each} +
+ {:else} +

No categories defined for this scope.

+ {/if} +
+
diff --git a/src/lib/components/admin/projects/ProjectTaskForm.svelte b/src/lib/components/admin/projects/ProjectTaskForm.svelte new file mode 100644 index 0000000..8354567 --- /dev/null +++ b/src/lib/components/admin/projects/ProjectTaskForm.svelte @@ -0,0 +1,164 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+
+ + +
+ +
+ + +

Shortened version shown on project checklists

+
+ +
+ + +

Estimated time to complete this task

+
+ +
+ + +
+ +
+ + +
+
+
diff --git a/src/lib/components/admin/projects/ScopeTemplateDropdown.svelte b/src/lib/components/admin/projects/ScopeTemplateDropdown.svelte new file mode 100644 index 0000000..d5ea822 --- /dev/null +++ b/src/lib/components/admin/projects/ScopeTemplateDropdown.svelte @@ -0,0 +1,83 @@ + + +
+ {#if templates.length > 0} + + {#if isOpen} + + + +
+ +
+

From Template

+ {#each templates as template (template.id)} + + {/each} +
+ {/if} + {:else} + + {/if} +
diff --git a/src/lib/components/admin/reports/AddProjectsModal.svelte b/src/lib/components/admin/reports/AddProjectsModal.svelte new file mode 100644 index 0000000..0ea9b1c --- /dev/null +++ b/src/lib/components/admin/reports/AddProjectsModal.svelte @@ -0,0 +1,297 @@ + + +{#if open} + +
e.key === 'Escape' && handleClose()} + > +
+ +
+

Add Projects

+

+ Select completed projects to add to this report. +

+
+ + +
+ {#if loading} +
+ + + + +
+ {:else if availableProjects.length === 0} +

+ No additional projects available for this team member in this month. +

+ {:else} + +
+ + + + {selectedProjectIds.size} selected + +
+ +
+ {#each availableProjects as project (project.id)} + + {/each} +
+ {/if} + + {#if error} +
+

{error}

+
+ {/if} +
+ + +
+ + +
+
+
+{/if} diff --git a/src/lib/components/admin/reports/AddServicesModal.svelte b/src/lib/components/admin/reports/AddServicesModal.svelte new file mode 100644 index 0000000..2888b11 --- /dev/null +++ b/src/lib/components/admin/reports/AddServicesModal.svelte @@ -0,0 +1,267 @@ + + +{#if open} + +
e.key === 'Escape' && handleClose()} + > +
+ +
+

Add Services

+

+ Select completed services to add to this report. +

+
+ + +
+ {#if loading} +
+ + + + +
+ {:else if availableServices.length === 0} +

+ No additional services available for this team member in this month. +

+ {:else} + +
+ + + + {selectedServiceIds.size} selected + +
+ +
+ {#each availableServices as service (service.id)} + + {/each} +
+ {/if} + + {#if error} +
+

{error}

+
+ {/if} +
+ + +
+ + +
+
+
+{/if} diff --git a/src/lib/components/admin/reports/ReportForm.svelte b/src/lib/components/admin/reports/ReportForm.svelte new file mode 100644 index 0000000..f3d82f7 --- /dev/null +++ b/src/lib/components/admin/reports/ReportForm.svelte @@ -0,0 +1,134 @@ + + +
+
+ +
+ + +
+ + +
+ + + {#if date} +

{formatDate(date)}

+ {/if} +
+ + +
+

+ After creating the report, you can add completed services and projects from the report + detail page. +

+
+ + {#if error} +
+

{error}

+
+ {/if} +
+ + +
+ + +
+
diff --git a/src/lib/components/admin/scopes/AreaSection.svelte b/src/lib/components/admin/scopes/AreaSection.svelte new file mode 100644 index 0000000..a17b765 --- /dev/null +++ b/src/lib/components/admin/scopes/AreaSection.svelte @@ -0,0 +1,266 @@ + + +
+
+ +
+ + {#if onMoveUp} + + {/if} + {#if onMoveDown} + + {/if} + +
+
+ + {#if isExpanded} +
+ + {#if isShowingNewTask} +
+ onNewTaskDescriptionChange(e.currentTarget.value)} + placeholder="Task description..." + class="placeholder-theme-muted w-full rounded border border-theme bg-theme-card px-3 py-2 text-sm text-theme focus:border-secondary-500 focus:outline-none" + onkeydown={(e) => { + if (e.key === 'Enter') { + onCreateTask(); + } else if (e.key === 'Escape') { + onHideNewTask(); + onNewTaskDescriptionChange(''); + } + }} + /> +
+ + +
+
+ {/if} + + + {#each sortedTasks as task, index (task.id)} + onStartEditTask(task.id)} + onCancelEdit={onCancelEditTask} + onSave={(updates) => onUpdateTask(task.id, updates)} + onDelete={() => onDeleteTask(task.id)} + onMoveUp={index === 0 ? undefined : () => onMoveTaskUp(task.id)} + onMoveDown={index === sortedTasks.length - 1 ? undefined : () => onMoveTaskDown(task.id)} + /> + {/each} + + {#if area.taskTemplates.length === 0 && !isShowingNewTask} +
+ No tasks yet. Click + to add one. +
+ {/if} +
+ {/if} +
diff --git a/src/lib/components/admin/scopes/TaskTemplateCard.svelte b/src/lib/components/admin/scopes/TaskTemplateCard.svelte new file mode 100644 index 0000000..5f7b04b --- /dev/null +++ b/src/lib/components/admin/scopes/TaskTemplateCard.svelte @@ -0,0 +1,287 @@ + + +
+ {#if isEditing} + +
+
+
+ + +
+
+ + +
+ {#if variant === 'service'} +
+
+ + +
+
+ + +
+
+ {:else} +
+ + +
+ {/if} +
+ + +
+
+
+ {:else} + +
+ +
+ {#if onMoveUp} + + {/if} + {#if onMoveDown} + + {/if} + +
+
+ {/if} +
diff --git a/src/lib/components/admin/scopes/TemplateEditor.svelte b/src/lib/components/admin/scopes/TemplateEditor.svelte new file mode 100644 index 0000000..91fb0e9 --- /dev/null +++ b/src/lib/components/admin/scopes/TemplateEditor.svelte @@ -0,0 +1,278 @@ + + + +
+
+
+ {#if isEditingName} + { + if (e.key === 'Enter') { + onUpdateTemplateName(e.currentTarget.value); + } else if (e.key === 'Escape') { + onCancelEditTemplateName(); + } + }} + onblur={(e) => onUpdateTemplateName(e.currentTarget.value)} + /> + {:else} +

+ {template.name} +

+ {/if} + + {template.isActive ? 'Active' : 'Inactive'} + +
+ +
+ +
+ +
+
+ +
+ + {#each areas as area, index (area.id)} + onToggleArea(area.id)} + onUpdateName={(name) => onUpdateAreaName(area.id, name)} + onDelete={() => onDeleteArea(area.id)} + onStartEditName={() => onStartEditAreaName(area.id)} + onCancelEditName={onCancelEditAreaName} + onShowNewTask={() => onShowNewTask(area.id)} + {onHideNewTask} + {onNewTaskDescriptionChange} + onCreateTask={() => onCreateTask(area.id)} + {onStartEditTask} + {onCancelEditTask} + {onUpdateTask} + {onDeleteTask} + onMoveUp={index === 0 ? undefined : () => onMoveAreaUp(area.id)} + onMoveDown={index === areas.length - 1 ? undefined : () => onMoveAreaDown(area.id)} + onMoveTaskUp={(taskId) => onMoveTaskUp(taskId, area.id)} + onMoveTaskDown={(taskId) => onMoveTaskDown(taskId, area.id)} + /> + {/each} + + + {#if isShowingNewArea} +
+ onNewAreaNameChange(e.currentTarget.value)} + placeholder="{areaLabel} name..." + class="placeholder-theme-muted w-full rounded-lg border border-theme bg-theme px-3 py-2 text-sm text-theme focus:border-secondary-500 focus:outline-none" + onkeydown={(e) => { + if (e.key === 'Enter') { + onCreateArea(); + } else if (e.key === 'Escape') { + onHideNewArea(); + onNewAreaNameChange(''); + } + }} + /> +
+ + +
+
+ {:else} + + {/if} +
diff --git a/src/lib/components/admin/scopes/TemplateList.svelte b/src/lib/components/admin/scopes/TemplateList.svelte new file mode 100644 index 0000000..f340bf6 --- /dev/null +++ b/src/lib/components/admin/scopes/TemplateList.svelte @@ -0,0 +1,129 @@ + + + +{#if showNewInput} +
+ onNewNameChange(e.currentTarget.value)} + placeholder="Template name..." + class="placeholder-theme-muted w-full rounded-lg border border-theme bg-theme px-3 py-2 text-sm text-theme focus:border-secondary-500 focus:ring-1 focus:ring-secondary-500 focus:outline-none" + onkeydown={(e) => { + if (e.key === 'Enter') { + onCreate(); + } else if (e.key === 'Escape') { + onShowNewInput(false); + onNewNameChange(''); + } + }} + /> +
+ + +
+
+{/if} + + +
+ {#each templates as template (template.id)} + + {/each} + {#if templates.length === 0} +
+

No {variant} templates yet

+

Click + to create one

+
+ {/if} +
diff --git a/src/lib/components/admin/services/GenerateServicesModal.svelte b/src/lib/components/admin/services/GenerateServicesModal.svelte new file mode 100644 index 0000000..df9979c --- /dev/null +++ b/src/lib/components/admin/services/GenerateServicesModal.svelte @@ -0,0 +1,523 @@ + + + + +{#if open} + +{/if} diff --git a/src/lib/components/admin/services/ServiceForm.svelte b/src/lib/components/admin/services/ServiceForm.svelte new file mode 100644 index 0000000..cdcdee1 --- /dev/null +++ b/src/lib/components/admin/services/ServiceForm.svelte @@ -0,0 +1,559 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+ + {#if !isEdit} +
+ +
+ +
+ + + +
+
+
+ + + {#if customerId} +
+

Service Location

+ + {#if customerAccounts().length > 0} + +
+ +
+ +
+ + + +
+
+
+ + + {#if selectedAccountId && accountAddresses().length > 0} +
+ + {#if accountAddresses().length === 1} + + {@const address = accountAddresses()[0]} +
+
+ + + + +
+

{address.streetAddress}

+

+ {address.city}, {address.state} + {address.zipCode} +

+
+
+
+ {:else} + +
+ +
+ + + +
+
+ {/if} +
+ + + {#if accountAddresses().length > 1 && selectedAddressDetails()} + {@const details = selectedAddressDetails()} +
+
+ + + + +
+

{details?.address.streetAddress}

+

+ {details?.address.city}, {details?.address.state} + {details?.address.zipCode} +

+
+
+
+ {/if} + {:else if selectedAccountId && accountAddresses().length === 0} +

+ This account has no addresses. Please select a different account or add an address + to this account first. +

+ {/if} + {:else} +

+ This customer has no accounts. Please create an account first. +

+ {/if} +
+ {/if} + {/if} + + +
+ + +
+ + +
+
+

Dispatched

+

Required before assigning team members

+
+ +
+ + +
+

Team Members

+
+ {#if !isDispatched} +

Enable "Dispatched" to assign team members

+ {:else if nonAdminTeamMembers.length > 0} + {#each nonAdminTeamMembers as member (member.id)} + + {/each} + {:else} +

No team members available

+ {/if} +
+
+ + +
+ + +
+ + +
+ + +
+
+
diff --git a/src/lib/components/admin/services/assign/AssignColumn.svelte b/src/lib/components/admin/services/assign/AssignColumn.svelte new file mode 100644 index 0000000..1768466 --- /dev/null +++ b/src/lib/components/admin/services/assign/AssignColumn.svelte @@ -0,0 +1,328 @@ + + +
+ + + + {#if services.length > 0} +
+
+ +
+ + {#if hasSelection} + + {/if} +
+ + + {#if hasSelection} +
+ {#if column === 'readyToAssign'} + +
+ + {#if showBulkTeamMemberDropdown} + +
e.stopPropagation()} + class="absolute top-full right-0 z-30 mt-1 max-h-48 w-48 overflow-y-auto rounded-lg border border-theme bg-theme-card py-1 shadow-lg" + > + {#each nonAdminTeamMembers as member (member.id)} + {@const memberPk = atob(member.id).split(':')[1]} + + {/each} +
+ {/if} +
+ {/if} + +
+ {/if} +
+
+ {/if} + +
+ {#each [...groupedServices.entries()] as [groupKey, groupServices] (groupKey)} + onToggleGroup(groupKey)} + {getTeamMemberNames} + {getNonDispatchTeamMemberNames} + {getAvailableTeamMembers} + {getStagedTeamMemberDetails} + {hasStagedMembers} + {onAddDispatch} + {onRemoveDispatch} + {onSubmitStaged} + {onRemoveNonDispatch} + {onStageTeamMember} + {onUnstageTeamMember} + {onToggleDropdown} + {onToggleSelection} + {onUpdateDate} + {onDeleteService} + {onStartEditDate} + {onCancelEditDate} + /> + {/each} + {#if services.length === 0} +
+ + + + {getEmptyMessage()} +
+ {/if} +
+
diff --git a/src/lib/components/admin/services/assign/AssignColumnHeader.svelte b/src/lib/components/admin/services/assign/AssignColumnHeader.svelte new file mode 100644 index 0000000..1707f51 --- /dev/null +++ b/src/lib/components/admin/services/assign/AssignColumnHeader.svelte @@ -0,0 +1,56 @@ + + +
+
+
+
+

{config.title}

+
+ + {count} + +
+

+ {config.description} +

+
diff --git a/src/lib/components/admin/services/assign/AssignServiceCard.svelte b/src/lib/components/admin/services/assign/AssignServiceCard.svelte new file mode 100644 index 0000000..410616d --- /dev/null +++ b/src/lib/components/admin/services/assign/AssignServiceCard.svelte @@ -0,0 +1,380 @@ + + +
+ {#if isUpdating} +
+ +
+ {/if} + + + {#if showCheckbox} + + {/if} + + + {#if column === 'unassigned' && isEditingDate} +
+ + + +
+ {:else if column === 'unassigned'} + + {:else} +
{formatServiceDate(service.date)}
+ {/if} + +
+ {service.accountName} +
+
{service.addressName ?? service.address}
+ + {#if column === 'unassigned'} + +
+ +
+ {#if teamMemberNames.length === 0} + No team assigned + {:else} + {teamMemberNames.join(', ')} + {/if} +
+ +
+ {:else if column === 'readyToAssign'} + +
+ {#each stagedMembers as member (member.pk)} + + {member.name} + + + {/each} + +
+ + {#if isDropdownOpen} + +
e.stopPropagation()} + class="absolute top-full left-0 z-20 mt-1 w-48 rounded-lg border border-theme bg-theme-card py-1 shadow-lg" + > + {#if availableMembers.length === 0} +
No available members
+ {:else} + {#each availableMembers as member (member.pk)} + + {/each} + {/if} +
+ {/if} +
+
+ +
+ + +
+ {:else} + +
+ +
+ Team: + {nonDispatchTeamMemberNames.join(', ')} +
+
+ {/if} +
diff --git a/src/lib/components/admin/services/assign/AssignServiceGroup.svelte b/src/lib/components/admin/services/assign/AssignServiceGroup.svelte new file mode 100644 index 0000000..5bffc92 --- /dev/null +++ b/src/lib/components/admin/services/assign/AssignServiceGroup.svelte @@ -0,0 +1,167 @@ + + +
+ + {#if isExpanded} +
+ {#each services as service (service.id)} + {@const availableMembers = + column === 'readyToAssign' ? getAvailableTeamMembers(service) : []} + {@const stagedMembers = + column === 'readyToAssign' ? getStagedTeamMemberDetails(service.id) : []} + {@const hasStaged = column === 'readyToAssign' ? hasStagedMembers(service.id) : false} + {@const isSelected = selectedServices?.has(service.id) ?? false} + onAddDispatch(service)} + onRemoveDispatch={() => onRemoveDispatch(service)} + onSubmitStaged={() => onSubmitStaged(service)} + onRemoveNonDispatch={() => onRemoveNonDispatch(service)} + onStageTeamMember={(pk) => onStageTeamMember(service.id, pk)} + onUnstageTeamMember={(pk) => onUnstageTeamMember(service.id, pk)} + onToggleDropdown={() => + onToggleDropdown(openTeamMemberDropdown === service.id ? null : service.id)} + onToggleSelection={() => onToggleSelection?.(service.id)} + onUpdateDate={(newDate) => onUpdateDate?.(service, newDate)} + onDelete={() => onDeleteService?.(service)} + onStartEditDate={() => onStartEditDate?.(service.id)} + {onCancelEditDate} + /> + {/each} +
+ {/if} +
diff --git a/src/lib/components/chat/ChatPanel.svelte b/src/lib/components/chat/ChatPanel.svelte new file mode 100644 index 0000000..46ff168 --- /dev/null +++ b/src/lib/components/chat/ChatPanel.svelte @@ -0,0 +1,113 @@ + + + + +{#if chat.isOpen} + + + + + +{/if} diff --git a/src/lib/components/chat/ChatToggle.svelte b/src/lib/components/chat/ChatToggle.svelte new file mode 100644 index 0000000..4d47445 --- /dev/null +++ b/src/lib/components/chat/ChatToggle.svelte @@ -0,0 +1,41 @@ + + + diff --git a/src/lib/components/chat/ConversationList.svelte b/src/lib/components/chat/ConversationList.svelte new file mode 100644 index 0000000..0a6a3b3 --- /dev/null +++ b/src/lib/components/chat/ConversationList.svelte @@ -0,0 +1,117 @@ + + +
+
+

Conversations

+ +
+ +
+ + + + + {#each chat.conversations as conversation (conversation.id)} + + {/each} + + {#if chat.conversations.length === 0} +
+ No conversations yet +
+ {/if} +
+
diff --git a/src/lib/components/chat/Message.svelte b/src/lib/components/chat/Message.svelte new file mode 100644 index 0000000..1c8cd3a --- /dev/null +++ b/src/lib/components/chat/Message.svelte @@ -0,0 +1,39 @@ + + +
+
+ + {#if message.tool_calls && message.tool_calls.length > 0} +
+ {#each message.tool_calls as tool, i} + r.tool_use_id === tool.id)?.result} + /> + {/each} +
+ {/if} + + + {#if message.content} +
+ {@html message.content.replace(/\n/g, '
')} +
+ {/if} +
+
diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte new file mode 100644 index 0000000..1024c21 --- /dev/null +++ b/src/lib/components/chat/MessageInput.svelte @@ -0,0 +1,99 @@ + + +
+
+ + + +
+ + {#if chat.connectionStatus === 'error'} +

+ Connection error. + +

+ {/if} +
diff --git a/src/lib/components/chat/MessageList.svelte b/src/lib/components/chat/MessageList.svelte new file mode 100644 index 0000000..c0e0f54 --- /dev/null +++ b/src/lib/components/chat/MessageList.svelte @@ -0,0 +1,93 @@ + + +
+ {#if chat.messages.length === 0 && !chat.isStreaming} + + {#if chat.introMessage} +
+
+
+ {@html chat.introMessage + .replace(/\*\*(.+?)\*\*/g, '$1') + .replace(/^• /gm, '') + .replace(/\n\n/g, '

') + .replace(/\n/g, '
')} +

+
+
+ {:else} +
+ + + +

Connecting to your AI assistant...

+
+ {/if} + {:else} + + {#each chat.messages as message (message.id)} + + {/each} + + + {#if chat.isStreaming} +
+
+ + {#if chat.currentToolCalls.length > 0} +
+ {#each chat.currentToolCalls as tool} + + {/each} +
+ {/if} + + + {#if chat.streamingContent} +
+ {@html chat.streamingContent.replace(/\n/g, '
')} +
+ {:else if chat.currentToolCalls.length === 0} + +
+
+
+
+
+ {/if} +
+
+ {/if} + {/if} +
diff --git a/src/lib/components/chat/ToolCallCard.svelte b/src/lib/components/chat/ToolCallCard.svelte new file mode 100644 index 0000000..a14860b --- /dev/null +++ b/src/lib/components/chat/ToolCallCard.svelte @@ -0,0 +1,89 @@ + + +
+
+ + {#if isStreaming} + + + + + {:else} + + {#if isReadOnly} + + {:else} + + {/if} + + {/if} + + {displayName} +
+ + + {#if result && !isStreaming} +
+ {#if typeof result === 'object' && result !== null} + {#if Array.isArray(result)} + {result.length} result{result.length !== 1 ? 's' : ''} + {:else if 'error' in (result as Record)} + Error: {(result as Record).error} + {:else} + Completed + {/if} + {:else} + {String(result).substring(0, 100)} + {/if} +
+ {/if} +
diff --git a/src/lib/components/customer/CustomerPageHeader.svelte b/src/lib/components/customer/CustomerPageHeader.svelte new file mode 100644 index 0000000..9b5433e --- /dev/null +++ b/src/lib/components/customer/CustomerPageHeader.svelte @@ -0,0 +1,245 @@ + + + + +
+ {#if showBackButton} + +
+ + {#if isNavigatingBack} + + + + + {:else} + + + + {/if} + +
+
+

{title}

+ {#if showNavMenu} + + {/if} +
+ {#if subtitleSnippet} +

+ {@render subtitleSnippet({ toggleMenu })} +

+ {:else if subtitle} +

{subtitle}

+ {/if} +
+ +
+
+ {:else} + +
+

{title}

+ {#if showNavMenu} + + {/if} +
+ {#if subtitleSnippet} +

+ {@render subtitleSnippet({ toggleMenu })} +

+ {:else if subtitle} +

{subtitle}

+ {/if} + {/if} +
diff --git a/src/lib/components/entity/EntityDetailsCard.svelte b/src/lib/components/entity/EntityDetailsCard.svelte new file mode 100644 index 0000000..916df81 --- /dev/null +++ b/src/lib/components/entity/EntityDetailsCard.svelte @@ -0,0 +1,255 @@ + + +
+
+
+ {#if entityType === 'service'} + + + + {:else} + + + + {/if} +
+

{title}

+
+ +
+
+ +
+ + {formatStatus(status)} + + + {formatDate(date)} + +
+ + + {#if entityType === 'project' && accountInfo} +
+

Account

+

{accountInfo.accountName}

+

{accountInfo.addressName}

+
+ {:else if entityType === 'project' && customerInfo} +
+

Customer

+

{customerInfo.customerName}

+
+ {/if} + + + {#if address} +
+

Address

+

{address}

+
+ {/if} + + +
+
+

Team Members

+ {#if messagableMembers.length >= 2} + + {/if} +
+ {#if teamMembers.length === 0} +

No team members assigned

+ {:else} +
+ {#each teamMembers as member (member.id)} +
+ + {member.role === 'TEAM_LEADER' ? 'TL' : 'TM'} + + {#if member.role !== 'ADMIN'} + + {:else} + + + {/if} + {member.fullName} +
+ {/each} +
+ {/if} +
+ + + {#if notes} +
+

Notes

+

{notes}

+
+ {/if} +
+
+
diff --git a/src/lib/components/entity/index.ts b/src/lib/components/entity/index.ts new file mode 100644 index 0000000..bde4bf5 --- /dev/null +++ b/src/lib/components/entity/index.ts @@ -0,0 +1 @@ +export { default as EntityDetailsCard } from './EntityDetailsCard.svelte'; diff --git a/src/lib/components/icons/IconBack.svelte b/src/lib/components/icons/IconBack.svelte new file mode 100644 index 0000000..642b404 --- /dev/null +++ b/src/lib/components/icons/IconBack.svelte @@ -0,0 +1,11 @@ + + + + + diff --git a/src/lib/components/icons/IconChevronRight.svelte b/src/lib/components/icons/IconChevronRight.svelte new file mode 100644 index 0000000..f526f07 --- /dev/null +++ b/src/lib/components/icons/IconChevronRight.svelte @@ -0,0 +1,11 @@ + + + + + diff --git a/src/lib/components/icons/IconEdit.svelte b/src/lib/components/icons/IconEdit.svelte new file mode 100644 index 0000000..1d01791 --- /dev/null +++ b/src/lib/components/icons/IconEdit.svelte @@ -0,0 +1,16 @@ + + + + + diff --git a/src/lib/components/icons/IconPlus.svelte b/src/lib/components/icons/IconPlus.svelte new file mode 100644 index 0000000..b68c715 --- /dev/null +++ b/src/lib/components/icons/IconPlus.svelte @@ -0,0 +1,11 @@ + + + + + diff --git a/src/lib/components/icons/IconSpinner.svelte b/src/lib/components/icons/IconSpinner.svelte new file mode 100644 index 0000000..a695cd9 --- /dev/null +++ b/src/lib/components/icons/IconSpinner.svelte @@ -0,0 +1,16 @@ + + + + + + diff --git a/src/lib/components/icons/IconTrash.svelte b/src/lib/components/icons/IconTrash.svelte new file mode 100644 index 0000000..45d5119 --- /dev/null +++ b/src/lib/components/icons/IconTrash.svelte @@ -0,0 +1,16 @@ + + + + + diff --git a/src/lib/components/icons/NavIcon.svelte b/src/lib/components/icons/NavIcon.svelte new file mode 100644 index 0000000..9134cd9 --- /dev/null +++ b/src/lib/components/icons/NavIcon.svelte @@ -0,0 +1,168 @@ + + +{#if name === 'customers'} + + + + + + + + + + + + + + + +{:else if name === 'accounts'} + + + + + + + + + + + + + + + + +{:else if name === 'services'} + + + + + + + + + + + +{:else if name === 'projects'} + + + + + + + + + + + + + + + + + +{:else if name === 'scopes'} + + + + + + + + + + + + + + + + +{:else if name === 'reports'} + + + + + + + + + +{:else if name === 'invoices'} + + + + + + + +{:else if name === 'calendar'} + + + + + + + + + + + + + + + + +{/if} diff --git a/src/lib/components/icons/index.ts b/src/lib/components/icons/index.ts new file mode 100644 index 0000000..5becab1 --- /dev/null +++ b/src/lib/components/icons/index.ts @@ -0,0 +1,6 @@ +export { default as IconBack } from './IconBack.svelte'; +export { default as IconEdit } from './IconEdit.svelte'; +export { default as IconTrash } from './IconTrash.svelte'; +export { default as IconPlus } from './IconPlus.svelte'; +export { default as IconChevronRight } from './IconChevronRight.svelte'; +export { default as IconSpinner } from './IconSpinner.svelte'; diff --git a/src/lib/components/layout/ContentContainer.svelte b/src/lib/components/layout/ContentContainer.svelte new file mode 100644 index 0000000..a628b10 --- /dev/null +++ b/src/lib/components/layout/ContentContainer.svelte @@ -0,0 +1,27 @@ + + +
+ {@render children()} +
diff --git a/src/lib/components/layout/MenuOverlay.svelte b/src/lib/components/layout/MenuOverlay.svelte new file mode 100644 index 0000000..2337b9c --- /dev/null +++ b/src/lib/components/layout/MenuOverlay.svelte @@ -0,0 +1,395 @@ + + + + +{#if open} + + +
e.key === 'Enter' && onClose()} + > + +
+ +
e.stopPropagation()} + onkeydown={(e) => e.stopPropagation()} + > + +
+ + + Logo + Nexus + +
+
+ + + + + +
+ {#if user} + + + + + Sign Out + + {:else} + + + + + Sign In + + {/if} +
+
+
+
+{/if} diff --git a/src/lib/components/layout/OffCanvasRight.svelte b/src/lib/components/layout/OffCanvasRight.svelte new file mode 100644 index 0000000..ae37394 --- /dev/null +++ b/src/lib/components/layout/OffCanvasRight.svelte @@ -0,0 +1,91 @@ + + + + +{#if isOpen} + + + + + +{/if} diff --git a/src/lib/components/nav/AdminNav.svelte b/src/lib/components/nav/AdminNav.svelte new file mode 100644 index 0000000..92eae4c --- /dev/null +++ b/src/lib/components/nav/AdminNav.svelte @@ -0,0 +1,194 @@ + + + e.key === 'Escape' && addMenuOpen && closeAddMenu()} /> + + diff --git a/src/lib/components/nav/CustomerNav.svelte b/src/lib/components/nav/CustomerNav.svelte new file mode 100644 index 0000000..82a8784 --- /dev/null +++ b/src/lib/components/nav/CustomerNav.svelte @@ -0,0 +1,55 @@ + + + diff --git a/src/lib/components/nav/TeamNav.svelte b/src/lib/components/nav/TeamNav.svelte new file mode 100644 index 0000000..5f03362 --- /dev/null +++ b/src/lib/components/nav/TeamNav.svelte @@ -0,0 +1,58 @@ + + + diff --git a/src/lib/components/nav/TopNav.svelte b/src/lib/components/nav/TopNav.svelte new file mode 100644 index 0000000..7aece39 --- /dev/null +++ b/src/lib/components/nav/TopNav.svelte @@ -0,0 +1,404 @@ + + + diff --git a/src/lib/components/session/SessionContent.svelte b/src/lib/components/session/SessionContent.svelte new file mode 100644 index 0000000..051fb87 --- /dev/null +++ b/src/lib/components/session/SessionContent.svelte @@ -0,0 +1,244 @@ + + + + + 0} + onTabChange={(tab) => (activeTab = tab)} +/> + +{#if activeTab === 'summary'} + +{:else if activeTab === 'tasks'} + 0} + {selectedTaskIds} + {completedTasksByArea} + {readyToSubmitByArea} + {availableTasksCount} + {tasks} + {completedTaskIds} + {areas} + isSubmitting={isActive ? isSubmitting : undefined} + submittingTaskId={isActive ? submittingTaskId : undefined} + removingTaskId={isActive ? removingTaskId : undefined} + {getTeamMemberName} + onToggleTask={isActive && onToggleTask ? onToggleTask : noop} + onSubmitTask={isActive && onSubmitTask ? onSubmitTask : noop} + onRemoveTask={isActive && onRemoveTask ? onRemoveTask : noop} + onRemoveCompletedTask={isActive && onRemoveCompletedTask ? onRemoveCompletedTask : noop} + onSubmitAllTasks={isActive && onSubmitAllTasks ? onSubmitAllTasks : noop} + onClearSelection={isActive && onClearSelection ? onClearSelection : noop} + /> +{:else if activeTab === 'media'} + +{:else if activeTab === 'notes'} + +{/if} diff --git a/src/lib/components/session/SessionHeader.svelte b/src/lib/components/session/SessionHeader.svelte new file mode 100644 index 0000000..85ead5b --- /dev/null +++ b/src/lib/components/session/SessionHeader.svelte @@ -0,0 +1,136 @@ + + +
+
+
+ {#if entityName} +

{entityName}

+ {/if} + + {#if scopeName} +
+ + + + + {scopeName} + + {#if scopeDescription} +

{scopeDescription}

+ {/if} +
+ {/if} +
+ + {#if isActive && onClose} +
+ + {#if !canClose} +

+ Complete at least one task to close +

+ {/if} + {#if onRevert} + + {/if} +
+ {:else if !isActive} +
+ + + + + Session Closed + +
+ {/if} +
+
diff --git a/src/lib/components/session/SessionMediaTab.svelte b/src/lib/components/session/SessionMediaTab.svelte new file mode 100644 index 0000000..8cee1f3 --- /dev/null +++ b/src/lib/components/session/SessionMediaTab.svelte @@ -0,0 +1,1123 @@ + + +
+ + {#if isActive} +
+ + +
+ {/if} + + + {#if showUploadForm} +
+

+ Upload {uploadType === 'photo' ? 'Photos' : 'Video'} +

+ + {#if uploadType === 'photo'} + +
+
+ + +
+ + + {#if isConvertingPhotos} +
+ + Converting images for preview... +
+ {/if} + + + {#if stagedPhotos.length > 0} +
+ {#each stagedPhotos as photo (photo.id)} +
+ +
+ {photo.title} + +
+ + + handleStagedTitleInput(photo.id, e)} + placeholder="Title..." + disabled={photo.uploading} + class="w-full rounded border border-theme bg-theme px-2 py-1 text-sm text-theme placeholder:text-theme-muted focus:border-primary-500 focus:outline-none disabled:opacity-50" + /> + + + + + +
+ + {#if photo.error} + {photo.error} + {/if} +
+
+ {/each} +
+ {/if} + + + {#if uploadProgress} +
+
+ + Uploading {uploadProgress.completed} of {uploadProgress.total}... + + {#if uploadProgress.failed > 0} + {uploadProgress.failed} failed + {/if} +
+
+
+
+
+ {/if} + + +
+ +
+ {#if stagedPhotos.length > 1 && !isUploading} + + {/if} + +
+
+
+ {:else} + +
+
+ + + {#if uploadVideoPreviewUrl} +
+ + +
+ {/if} +
+ +
+ + +
+ +
+ + +
+ + + +
+ + +
+
+ {/if} +
+ {/if} + + + {#if photos.length > 0} +
+

Photos ({photos.length})

+
+ {#each photos as photo (photo.id)} + {@const thumbnailUrl = photo.thumbnail?.url || photo.image.url} +
+ + + {#if photo.internal} + + Internal + + {/if} + + {#if isActive} +
+ + +
+ {/if} + +
+

{photo.title || 'Untitled'}

+ {#if photo.notes} +

{photo.notes}

+ {/if} +

+ {getTeamMemberName( + 'uploadedByTeamProfileId' in photo ? photo.uploadedByTeamProfileId : null + )} +

+
+
+ {/each} +
+
+ {/if} + + + {#if videos.length > 0} +
+

Videos ({videos.length})

+
+ {#each videos as video (video.id)} +
+ + + {#if video.internal} + + Internal + + {/if} + + {#if isActive} +
+ + +
+ {/if} + +
+

{video.title || 'Untitled'}

+ {#if video.notes} +

{video.notes}

+ {/if} +

+ {getTeamMemberName(video.uploadedByTeamProfileId)} +

+
+
+ {/each} +
+
+ {/if} + + + {#if photos.length === 0 && videos.length === 0} +
+ + + +

No Media Yet

+

+ {#if isActive} + Add photos and videos to document your work. + {:else} + No photos or videos were added to this session. + {/if} +

+
+ {/if} +
+ + +{#if editingId} +
+
+

Edit Details

+ +
+
+ + +
+ +
+ + +
+ + + +
+ + +
+
+
+
+{/if} diff --git a/src/lib/components/session/SessionNotesTab.svelte b/src/lib/components/session/SessionNotesTab.svelte new file mode 100644 index 0000000..616301a --- /dev/null +++ b/src/lib/components/session/SessionNotesTab.svelte @@ -0,0 +1,314 @@ + + +
+ + {#if isActive} + {#if showAddForm} +
+

Add Note

+ +
+
+ +
+ + + +
+ + +
+
+
+ {:else} + + {/if} + {/if} + + + {#if sortedNotes.length > 0} +
+ {#each sortedNotes as note (note.id)} +
+ {#if editingId === note.id} + +
+ + + + +
+ + +
+
+ {:else} + +
+
+
+ {#if note.authorId} + + {getTeamMemberName(note.authorId)} + + {/if} + {#if note.internal} + Internal + {/if} +
+ +

{note.content}

+ +

+ {formatDateTime(note.createdAt)} + {#if note.updatedAt && note.updatedAt !== note.createdAt} + (edited) + {/if} +

+
+ + {#if isActive} +
+ + +
+ {/if} +
+ {/if} +
+ {/each} +
+ {:else} + +
+ + + +

No Notes Yet

+

+ {#if isActive} + Add notes to document important information. + {:else} + No notes were added to this session. + {/if} +

+
+ {/if} +
diff --git a/src/lib/components/session/SessionSectionHeader.svelte b/src/lib/components/session/SessionSectionHeader.svelte new file mode 100644 index 0000000..02762cf --- /dev/null +++ b/src/lib/components/session/SessionSectionHeader.svelte @@ -0,0 +1,71 @@ + + +
+
+ {#if isActive} + + + + {:else} + + + + {/if} +
+

{title}

+ {#if isActive} + + + + + {/if} +
diff --git a/src/lib/components/session/SessionSummaryTab.svelte b/src/lib/components/session/SessionSummaryTab.svelte new file mode 100644 index 0000000..d478f88 --- /dev/null +++ b/src/lib/components/session/SessionSummaryTab.svelte @@ -0,0 +1,160 @@ + + + +
+ {#each statItems as stat (stat.label)} +
+
+ + + +
+

{stat.value}

+

{stat.label}

+
+ {/each} +
+ + +
+

Session Details

+ +
+ +
+
+

Started

+

{formatDateTime(sessionDetails.start)}

+
+ {#if sessionDetails.end} +
+

Ended

+

{formatDateTime(sessionDetails.end)}

+
+ {:else if sessionDetails.isActive} +
+

Status

+

In Progress

+
+ {/if} +
+ + {#if sessionDetails.durationSeconds} +
+

Duration

+

{formatDuration(sessionDetails.durationSeconds)}

+
+ {/if} + + + {#if entityName || addressInfo} +
+ {#if entityName} +

+ {sessionType === 'service' ? 'Account' : 'Project'} +

+

{entityName}

+ {/if} + {#if addressInfo} +

{addressInfo}

+ {/if} +
+ {/if} + + + {#if teamMembers.length > 0} +
+

Team Members

+
+ {#each teamMembers as member (member.id)} +
+ + {member.role === 'TEAM_LEADER' ? 'TL' : 'TM'} + + {member.fullName} +
+ {/each} +
+
+ {/if} +
+
diff --git a/src/lib/components/session/SessionTabs.svelte b/src/lib/components/session/SessionTabs.svelte new file mode 100644 index 0000000..b884e2d --- /dev/null +++ b/src/lib/components/session/SessionTabs.svelte @@ -0,0 +1,136 @@ + + + +
+
+ + +
+ {tabs[currentTabIndex].label} + {#if getCount(tabs[currentTabIndex]) !== null} + + {getCount(tabs[currentTabIndex])} + + {/if} +
+ + +
+ + +
+ {#each tabs as tab, i (tab.id)} + + {/each} +
+
+ + + diff --git a/src/lib/components/session/SessionTasksTab.svelte b/src/lib/components/session/SessionTasksTab.svelte new file mode 100644 index 0000000..91f1afa --- /dev/null +++ b/src/lib/components/session/SessionTasksTab.svelte @@ -0,0 +1,579 @@ + + +{#if !hasScope} +
+ + + +

No Scope Assigned

+

This {sessionType} doesn't have a scope of work assigned.

+
+{:else} +
+ + {#if selectedTaskIds.size > 0} +
+
+

+ Ready to Submit ({selectedTaskIds.size}) +

+
+ + +
+
+ +
+ {#each readyToSubmitByArea as { area, tasks: areaTasks } (area?.id ?? 'unknown')} + {#if area} +
+ + + {#if expandedReadyAreas.has(area.id)} +
+ {#each areaTasks as task (task.uuid)} +
+
+

+ {task.checklistDescription || task.description} +

+
+ {#if sessionType === 'service' && 'frequency' in task && task.frequency} + + {formatFrequency(task.frequency)} + + {/if} + {#if task.estimatedMinutes} + {task.estimatedMinutes} min + {/if} +
+
+
+ + +
+
+ {/each} +
+ {/if} +
+ {/if} + {/each} +
+
+ {/if} + + + {#if completedTasksByArea.length > 0} +
+ + + {#if completedExpanded} +
+ {#each completedTasksByArea as { area, completions } (area.id)} +
+ + + {#if expandedCompletedAreas.has(area.id)} +
+ {#each completions as completion (completion.id)} + {@const task = tasks.find((t) => t.uuid === completion.taskId)} +
+ + + +
+

+ {#if task} + {task.checklistDescription || task.description} + {:else} + Task + {/if} +

+ {#if task && sessionType === 'service' && 'frequency' in task && task.frequency} +
+ + {formatFrequency(task.frequency)} + +
+ {/if} +

+ {getTeamMemberName(completion.completedById)} · {formatDateTime( + completion.completedAt + )} +

+
+ {#if isActive} + + {/if} +
+ {/each} +
+ {/if} +
+ {/each} +
+ {/if} +
+ {/if} + + + {#if isActive && availableTasksCount > 0} +
+ + + {#if availableExpanded} +
+ {#each availableTasksByArea() as { area, tasks: areaTasks } (area?.id ?? 'unknown')} + {#if area} +
+ + + {#if expandedAreas.has(area.id)} +
+ {#each areaTasks as task (task.uuid)} +
+
+

+ {task.checklistDescription || task.description} +

+
+ {#if sessionType === 'service' && 'frequency' in task && task.frequency} + + {formatFrequency(task.frequency)} + + {/if} + {#if task.estimatedMinutes} + {task.estimatedMinutes} min + {/if} +
+
+ +
+ {/each} +
+ {/if} +
+ {/if} + {/each} +
+ {/if} +
+ {:else if !isActive} +
+

+ This session is closed. Tasks cannot be added or modified. +

+
+ {/if} +
+{/if} diff --git a/src/lib/components/session/StartSessionButton.svelte b/src/lib/components/session/StartSessionButton.svelte new file mode 100644 index 0000000..ef9f595 --- /dev/null +++ b/src/lib/components/session/StartSessionButton.svelte @@ -0,0 +1,33 @@ + + +
+ + +
diff --git a/src/lib/components/session/index.ts b/src/lib/components/session/index.ts new file mode 100644 index 0000000..966e97d --- /dev/null +++ b/src/lib/components/session/index.ts @@ -0,0 +1,13 @@ +// Session components barrel export +export { default as SessionHeader } from './SessionHeader.svelte'; +export { default as SessionTabs } from './SessionTabs.svelte'; +export { default as SessionSummaryTab } from './SessionSummaryTab.svelte'; +export { default as SessionTasksTab } from './SessionTasksTab.svelte'; +export { default as SessionMediaTab } from './SessionMediaTab.svelte'; +export { default as SessionNotesTab } from './SessionNotesTab.svelte'; +export { default as SessionSectionHeader } from './SessionSectionHeader.svelte'; +export { default as SessionContent } from './SessionContent.svelte'; +export { default as StartSessionButton } from './StartSessionButton.svelte'; + +// Types and utilities +export * from './types'; diff --git a/src/lib/components/session/types.ts b/src/lib/components/session/types.ts new file mode 100644 index 0000000..7421d08 --- /dev/null +++ b/src/lib/components/session/types.ts @@ -0,0 +1,176 @@ +// Session component shared types +import type { + ServiceSession$result, + ProjectSession$result, + Scope$result, + ProjectScope$result +} from '$houdini'; + +// Session type discriminator +export type SessionType = 'service' | 'project'; + +// Tab type +export type SessionTab = 'summary' | 'tasks' | 'media' | 'notes'; + +// Service session types +export type ServiceSession = NonNullable; +export type ServiceSessionPhoto = ServiceSession['photos'][number]; +export type ServiceSessionVideo = ServiceSession['videos'][number]; +export type ServiceSessionNote = ServiceSession['notes'][number]; +export type ServiceSessionCompletedTask = ServiceSession['completedTasks'][number]; + +// Project session types +export type ProjectSession = NonNullable; +export type ProjectSessionPhoto = ProjectSession['photos'][number]; +export type ProjectSessionVideo = ProjectSession['videos'][number]; +export type ProjectSessionNote = ProjectSession['notes'][number]; +export type ProjectSessionCompletedTask = ProjectSession['completedTasks'][number]; + +// Union types for components that handle both +export type SessionPhoto = ServiceSessionPhoto | ProjectSessionPhoto; +export type SessionVideo = ServiceSessionVideo | ProjectSessionVideo; +export type SessionNote = ServiceSessionNote | ProjectSessionNote; +export type SessionCompletedTask = ServiceSessionCompletedTask | ProjectSessionCompletedTask; + +// Scope types +export type ServiceScope = NonNullable; +export type ServiceArea = ServiceScope['areas'][number]; +export type ServiceTask = ServiceArea['tasks'][number]; + +export type ProjectScope = NonNullable; +export type ProjectArea = ProjectScope['projectAreas'][number]; +export type ProjectTask = ProjectArea['projectTasks'][number]; + +// Extended task type with additional fields for both types +export type ExtendedServiceTask = ServiceTask & { + uuid: string; + areaId: string; + areaName: string; +}; + +export type ExtendedProjectTask = ProjectTask & { + uuid: string; + areaId: string; + areaName: string; +}; + +export type ExtendedTask = ExtendedServiceTask | ExtendedProjectTask; + +// Area with completions (for completed tasks section) +export interface ServiceAreaWithCompletions { + area: ServiceArea; + completions: ServiceSessionCompletedTask[]; +} + +export interface ProjectAreaWithCompletions { + area: ProjectArea; + completions: ProjectSessionCompletedTask[]; +} + +export type AreaWithCompletions = ServiceAreaWithCompletions | ProjectAreaWithCompletions; + +// Area with tasks (for ready to submit section) +export interface ServiceAreaWithTasks { + area: ServiceArea; + tasks: ExtendedServiceTask[]; +} + +export interface ProjectAreaWithTasks { + area: ProjectArea; + tasks: ExtendedProjectTask[]; +} + +export type AreaWithTasks = ServiceAreaWithTasks | ProjectAreaWithTasks; + +// Props interfaces for components +export interface SessionTabsCounts { + tasks: number; + photos: number; + videos: number; + notes: number; +} + +// Helper function to format error messages +export function getErrorMessage(errors: { message: string }[] | null | undefined): string { + return errors?.map((e) => e.message).join(', ') ?? ''; +} + +// Frequency badge colors - matches TaskFrequencyChoices enum from backend (lowercase) +export const frequencyColors: Record = { + daily: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400', + weekly: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400', + monthly: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400', + quarterly: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400', + triannual: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400', + annual: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400', + as_needed: 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400' +}; + +// Format frequency for display +export function formatFrequency(frequency: string | null | undefined): string { + if (!frequency) return ''; + return frequency.replace('_', ' ').toLowerCase(); +} + +// Format date/time +export function formatDateTime(dateStr: string | null | undefined): string { + if (!dateStr) return ''; + const date = new Date(dateStr); + return date.toLocaleString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + hour: 'numeric', + minute: '2-digit' + }); +} + +export function formatDate(dateStr: string | null | undefined): string { + if (!dateStr) return ''; + const date = new Date(dateStr); + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }); +} + +export function formatTime(dateStr: string | null | undefined): string { + if (!dateStr) return ''; + const date = new Date(dateStr); + return date.toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit' + }); +} + +// Format duration +export function formatDuration(seconds: number | null | undefined): string { + if (!seconds) return '0m'; + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + if (hours > 0) { + return `${hours}h ${minutes}m`; + } + return `${minutes}m`; +} + +// API base URL for authenticated media requests +export const API_BASE_URL = 'https://api.example.com'; + +// Helper to get authenticated image URL by fetching the image and creating an object URL +export async function getAuthenticatedImageUrl(url: string): Promise { + try { + // Add /v1 prefix for Oathkeeper routing if URL starts with /api/ + const apiPath = url.startsWith('/api/') ? `/v1${url}` : url; + const fullUrl = `${API_BASE_URL}${apiPath}`; + const response = await fetch(fullUrl, { + credentials: 'include' + }); + const blob = await response.blob(); + return URL.createObjectURL(blob); + } catch (err) { + console.error('Failed to fetch authenticated image:', err); + return url; // Fallback to original URL + } +} diff --git a/src/lib/components/shared/AddressForm.svelte b/src/lib/components/shared/AddressForm.svelte new file mode 100644 index 0000000..4ac5391 --- /dev/null +++ b/src/lib/components/shared/AddressForm.svelte @@ -0,0 +1,321 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+ {#if entityType === 'account'} +
+ + +
+ {/if} + +
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ + {#if entityType === 'customer'} +
+ + +
+ {/if} +
+ + {#if entityType === 'account'} +
+ + +
+ {/if} + +
+ + + {#if showActiveToggle} + + {/if} +
+ +
+ + +
+
+
diff --git a/src/lib/components/shared/ContactForm.svelte b/src/lib/components/shared/ContactForm.svelte new file mode 100644 index 0000000..f5fe3a2 --- /dev/null +++ b/src/lib/components/shared/ContactForm.svelte @@ -0,0 +1,272 @@ + + +
+ {#if error} +
+ {error} +
+ {/if} + +
+
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + {#if showActiveToggle} + + {/if} +
+ +
+ + +
+
+
diff --git a/src/lib/components/shared/SelectionModal.svelte b/src/lib/components/shared/SelectionModal.svelte new file mode 100644 index 0000000..c6af219 --- /dev/null +++ b/src/lib/components/shared/SelectionModal.svelte @@ -0,0 +1,198 @@ + + +{#if open} + +
e.key === 'Escape' && handleClose()} + > +
+ +
+

{title}

+

{subtitle}

+
+ + +
+ {#if loading} +
+ + + + +
+ {:else if !hasItems} +

+ {emptyMessage} +

+ {:else} + + {#if selectAll && selectNone} +
+ + + + {selectedCount} selected + +
+ {/if} + +
+ {@render children()} +
+ + + {#if showTotal && selectedCount > 0} +
+
+ Selected Total: + + {formatCurrency(selectedTotal)} + +
+
+ {/if} + {/if} + + {#if error} +
+

{error}

+
+ {/if} +
+ + +
+ + +
+
+
+{/if} diff --git a/src/lib/components/shared/TabFilter.svelte b/src/lib/components/shared/TabFilter.svelte new file mode 100644 index 0000000..808bc2a --- /dev/null +++ b/src/lib/components/shared/TabFilter.svelte @@ -0,0 +1,54 @@ + + +
+ {#each tabs as tab (tab.id)} + + {/each} +
diff --git a/src/lib/components/shared/UnreadBadge.svelte b/src/lib/components/shared/UnreadBadge.svelte new file mode 100644 index 0000000..0fb1f94 --- /dev/null +++ b/src/lib/components/shared/UnreadBadge.svelte @@ -0,0 +1,19 @@ + + +{#if showBadge} + + {displayCount} + +{/if} diff --git a/src/lib/components/team/MonthSelector.svelte b/src/lib/components/team/MonthSelector.svelte new file mode 100644 index 0000000..1c0741d --- /dev/null +++ b/src/lib/components/team/MonthSelector.svelte @@ -0,0 +1,158 @@ + + + + +
+ + + + + + + + + + + {#if isPickerOpen} +
+
+ {#each pickerMonths() as month (month.value)} + + {/each} +
+
+ {/if} +
+ + +{#if isPickerOpen} + +{/if} diff --git a/src/lib/components/team/StatusTabs.svelte b/src/lib/components/team/StatusTabs.svelte new file mode 100644 index 0000000..92d754d --- /dev/null +++ b/src/lib/components/team/StatusTabs.svelte @@ -0,0 +1,138 @@ + + +{#if compact} + +
+ + + + + +
+{:else} + +
+ {#each tabs as tab (tab.id)} + {@const isActive = current === tab.id} + {@const count = counts?.[tab.countKey]} + + {/each} +
+{/if} diff --git a/src/lib/components/team/TeamPageHeader.svelte b/src/lib/components/team/TeamPageHeader.svelte new file mode 100644 index 0000000..95ebbaf --- /dev/null +++ b/src/lib/components/team/TeamPageHeader.svelte @@ -0,0 +1,238 @@ + + + + +
+ {#if showBackButton} + +
+ + {#if isNavigatingBack} + + + + + {:else} + + + + {/if} + +
+
+

{title}

+ {#if showNavMenu} + + {/if} +
+ {#if subtitleSnippet} +

+ {@render subtitleSnippet({ toggleMenu })} +

+ {:else if subtitle} +

{subtitle}

+ {/if} +
+ +
+
+ {:else} + +
+

{title}

+ {#if showNavMenu} + + {/if} +
+ {#if subtitleSnippet} +

+ {@render subtitleSnippet({ toggleMenu })} +

+ {:else if subtitle} +

{subtitle}

+ {/if} + {/if} +
diff --git a/src/lib/components/team/WorkListLayout.svelte b/src/lib/components/team/WorkListLayout.svelte new file mode 100644 index 0000000..e6e138d --- /dev/null +++ b/src/lib/components/team/WorkListLayout.svelte @@ -0,0 +1,131 @@ + + + + {title} - Team - Nexus + + +
+ + + + + +
+ +
+ +
+
+ + + + + +
+ {#if isLoading} + +
+
+ + + + + Loading... +
+
+ {/if} + + {#if hasItems} +
+ {#each items as itemData, index (index)} + {@render item(itemData)} + {/each} +
+ {:else if !isLoading} + +
+ {#if emptyIcon} + {@render emptyIcon()} + {:else} + + + + {/if} +

{emptyTitle}

+

{emptyText}

+
+ {/if} +
+
+
diff --git a/src/lib/data/eventTypes.ts b/src/lib/data/eventTypes.ts new file mode 100644 index 0000000..6908424 --- /dev/null +++ b/src/lib/data/eventTypes.ts @@ -0,0 +1,286 @@ +export interface EventType { + value: string; + label: string; +} + +export interface EventTypeCategory { + name: string; + label: string; + types: EventType[]; +} + +export const EVENT_TYPE_CATEGORIES: EventTypeCategory[] = [ + { + name: 'customer', + label: 'Customer', + types: [ + { value: 'CUSTOMER_CREATED', label: 'Customer Created' }, + { value: 'CUSTOMER_UPDATED', label: 'Customer Updated' }, + { value: 'CUSTOMER_DELETED', label: 'Customer Deleted' }, + { value: 'CUSTOMER_STATUS_CHANGED', label: 'Customer Status Changed' }, + { value: 'CUSTOMER_ADDRESS_CREATED', label: 'Customer Address Created' }, + { value: 'CUSTOMER_ADDRESS_UPDATED', label: 'Customer Address Updated' }, + { value: 'CUSTOMER_ADDRESS_DELETED', label: 'Customer Address Deleted' }, + { value: 'CUSTOMER_CONTACT_CREATED', label: 'Customer Contact Created' }, + { value: 'CUSTOMER_CONTACT_UPDATED', label: 'Customer Contact Updated' }, + { value: 'CUSTOMER_CONTACT_DELETED', label: 'Customer Contact Deleted' } + ] + }, + { + name: 'account', + label: 'Account', + types: [ + { value: 'ACCOUNT_CREATED', label: 'Account Created' }, + { value: 'ACCOUNT_UPDATED', label: 'Account Updated' }, + { value: 'ACCOUNT_DELETED', label: 'Account Deleted' }, + { value: 'ACCOUNT_STATUS_CHANGED', label: 'Account Status Changed' }, + { value: 'ACCOUNT_ADDRESS_CREATED', label: 'Account Address Created' }, + { value: 'ACCOUNT_ADDRESS_UPDATED', label: 'Account Address Updated' }, + { value: 'ACCOUNT_ADDRESS_DELETED', label: 'Account Address Deleted' }, + { value: 'ACCOUNT_CONTACT_CREATED', label: 'Account Contact Created' }, + { value: 'ACCOUNT_CONTACT_UPDATED', label: 'Account Contact Updated' }, + { value: 'ACCOUNT_CONTACT_DELETED', label: 'Account Contact Deleted' } + ] + }, + { + name: 'service', + label: 'Service', + types: [ + { value: 'SERVICE_CREATED', label: 'Service Created' }, + { value: 'SERVICE_UPDATED', label: 'Service Updated' }, + { value: 'SERVICE_DELETED', label: 'Service Deleted' }, + { value: 'SERVICE_STATUS_CHANGED', label: 'Service Status Changed' }, + { value: 'SERVICE_COMPLETED', label: 'Service Completed' }, + { value: 'SERVICE_CANCELLED', label: 'Service Cancelled' }, + { value: 'SERVICE_DISPATCHED', label: 'Service Dispatched' }, + { value: 'SERVICE_TEAM_ASSIGNED', label: 'Team Assigned to Service' }, + { value: 'SERVICE_TEAM_UNASSIGNED', label: 'Team Unassigned from Service' }, + { value: 'SERVICES_BULK_GENERATED', label: 'Services Bulk Generated' } + ] + }, + { + name: 'service_session', + label: 'Service Session', + types: [ + { value: 'SERVICE_SESSION_OPENED', label: 'Service Session Opened' }, + { value: 'SERVICE_SESSION_CLOSED', label: 'Service Session Closed' }, + { value: 'SERVICE_SESSION_REVERTED', label: 'Service Session Reverted' }, + { value: 'SERVICE_TASK_COMPLETED', label: 'Service Task Completed' }, + { value: 'SERVICE_TASK_UNCOMPLETED', label: 'Service Task Uncompleted' } + ] + }, + { + name: 'schedule', + label: 'Schedule', + types: [ + { value: 'SCHEDULE_CREATED', label: 'Schedule Created' }, + { value: 'SCHEDULE_UPDATED', label: 'Schedule Updated' }, + { value: 'SCHEDULE_DELETED', label: 'Schedule Deleted' }, + { value: 'SCHEDULE_FREQUENCY_CHANGED', label: 'Schedule Frequency Changed' } + ] + }, + { + name: 'project', + label: 'Project', + types: [ + { value: 'PROJECT_CREATED', label: 'Project Created' }, + { value: 'PROJECT_UPDATED', label: 'Project Updated' }, + { value: 'PROJECT_STATUS_CHANGED', label: 'Project Status Changed' }, + { value: 'PROJECT_COMPLETED', label: 'Project Completed' }, + { value: 'PROJECT_CANCELLED', label: 'Project Cancelled' }, + { value: 'PROJECT_DISPATCHED', label: 'Project Dispatched' } + ] + }, + { + name: 'project_session', + label: 'Project Session', + types: [ + { value: 'PROJECT_SESSION_OPENED', label: 'Project Session Opened' }, + { value: 'PROJECT_SESSION_CLOSED', label: 'Project Session Closed' }, + { value: 'PROJECT_SESSION_REVERTED', label: 'Project Session Reverted' }, + { value: 'PROJECT_TASK_COMPLETED', label: 'Project Task Completed' }, + { value: 'PROJECT_TASK_UNCOMPLETED', label: 'Project Task Uncompleted' } + ] + }, + { + name: 'project_scope', + label: 'Project Scope', + types: [ + { value: 'PROJECT_SCOPE_CREATED', label: 'Project Scope Created' }, + { value: 'PROJECT_SCOPE_UPDATED', label: 'Project Scope Updated' }, + { value: 'PROJECT_SCOPE_DELETED', label: 'Project Scope Deleted' }, + { value: 'PROJECT_SCOPE_CATEGORY_CREATED', label: 'Project Scope Category Created' }, + { value: 'PROJECT_SCOPE_CATEGORY_UPDATED', label: 'Project Scope Category Updated' }, + { value: 'PROJECT_SCOPE_CATEGORY_DELETED', label: 'Project Scope Category Deleted' }, + { value: 'PROJECT_SCOPE_TASK_CREATED', label: 'Project Scope Task Created' }, + { value: 'PROJECT_SCOPE_TASK_UPDATED', label: 'Project Scope Task Updated' }, + { value: 'PROJECT_SCOPE_TASK_DELETED', label: 'Project Scope Task Deleted' }, + { value: 'PROJECT_SCOPE_TEMPLATE_INSTANTIATED', label: 'Project Scope Template Instantiated' } + ] + }, + { + name: 'scope', + label: 'Scope', + types: [ + { value: 'SCOPE_CREATED', label: 'Scope Created' }, + { value: 'SCOPE_UPDATED', label: 'Scope Updated' }, + { value: 'SCOPE_DELETED', label: 'Scope Deleted' }, + { value: 'AREA_CREATED', label: 'Area Created' }, + { value: 'AREA_UPDATED', label: 'Area Updated' }, + { value: 'AREA_DELETED', label: 'Area Deleted' }, + { value: 'TASK_CREATED', label: 'Task Created' }, + { value: 'TASK_UPDATED', label: 'Task Updated' }, + { value: 'TASK_DELETED', label: 'Task Deleted' }, + { value: 'TASK_COMPLETION_RECORDED', label: 'Task Completion Recorded' } + ] + }, + { + name: 'scope_template', + label: 'Scope Template', + types: [ + { value: 'SCOPE_TEMPLATE_CREATED', label: 'Scope Template Created' }, + { value: 'SCOPE_TEMPLATE_UPDATED', label: 'Scope Template Updated' }, + { value: 'SCOPE_TEMPLATE_DELETED', label: 'Scope Template Deleted' }, + { value: 'SCOPE_TEMPLATE_INSTANTIATED', label: 'Scope Template Instantiated' }, + { value: 'AREA_TEMPLATE_CREATED', label: 'Area Template Created' }, + { value: 'AREA_TEMPLATE_UPDATED', label: 'Area Template Updated' }, + { value: 'AREA_TEMPLATE_DELETED', label: 'Area Template Deleted' }, + { value: 'TASK_TEMPLATE_CREATED', label: 'Task Template Created' }, + { value: 'TASK_TEMPLATE_UPDATED', label: 'Task Template Updated' }, + { value: 'TASK_TEMPLATE_DELETED', label: 'Task Template Deleted' } + ] + }, + { + name: 'team_profile', + label: 'Team Profile', + types: [ + { value: 'TEAM_PROFILE_CREATED', label: 'Team Profile Created' }, + { value: 'TEAM_PROFILE_UPDATED', label: 'Team Profile Updated' }, + { value: 'TEAM_PROFILE_DELETED', label: 'Team Profile Deleted' }, + { value: 'TEAM_PROFILE_ROLE_CHANGED', label: 'Team Profile Role Changed' } + ] + }, + { + name: 'customer_profile', + label: 'Customer Profile', + types: [ + { value: 'CUSTOMER_PROFILE_CREATED', label: 'Customer Profile Created' }, + { value: 'CUSTOMER_PROFILE_UPDATED', label: 'Customer Profile Updated' }, + { value: 'CUSTOMER_PROFILE_DELETED', label: 'Customer Profile Deleted' }, + { value: 'CUSTOMER_PROFILE_ACCESS_GRANTED', label: 'Customer Profile Access Granted' }, + { value: 'CUSTOMER_PROFILE_ACCESS_REVOKED', label: 'Customer Profile Access Revoked' } + ] + }, + { + name: 'punchlist', + label: 'Punchlist', + types: [ + { value: 'ACCOUNT_PUNCHLIST_CREATED', label: 'Account Punchlist Created' }, + { value: 'ACCOUNT_PUNCHLIST_UPDATED', label: 'Account Punchlist Updated' }, + { value: 'ACCOUNT_PUNCHLIST_DELETED', label: 'Account Punchlist Deleted' }, + { value: 'PROJECT_PUNCHLIST_CREATED', label: 'Project Punchlist Created' }, + { value: 'PROJECT_PUNCHLIST_UPDATED', label: 'Project Punchlist Updated' }, + { value: 'PROJECT_PUNCHLIST_DELETED', label: 'Project Punchlist Deleted' }, + { value: 'PUNCHLIST_STATUS_CHANGED', label: 'Punchlist Status Changed' }, + { value: 'PUNCHLIST_PRIORITY_CHANGED', label: 'Punchlist Priority Changed' } + ] + }, + { + name: 'media', + label: 'Session Media', + types: [ + { value: 'SESSION_IMAGE_UPLOADED', label: 'Session Image Uploaded' }, + { value: 'SESSION_IMAGE_UPDATED', label: 'Session Image Updated' }, + { value: 'SESSION_IMAGE_DELETED', label: 'Session Image Deleted' }, + { value: 'SESSION_VIDEO_UPLOADED', label: 'Session Video Uploaded' }, + { value: 'SESSION_VIDEO_UPDATED', label: 'Session Video Updated' }, + { value: 'SESSION_VIDEO_DELETED', label: 'Session Video Deleted' }, + { value: 'SESSION_MEDIA_INTERNAL_FLAGGED', label: 'Session Media Flagged as Internal' } + ] + }, + { + name: 'notes', + label: 'Session Notes', + types: [ + { value: 'SESSION_NOTE_CREATED', label: 'Session Note Created' }, + { value: 'SESSION_NOTE_UPDATED', label: 'Session Note Updated' }, + { value: 'SESSION_NOTE_DELETED', label: 'Session Note Deleted' } + ] + }, + { + name: 'report', + label: 'Report', + types: [ + { value: 'REPORT_CREATED', label: 'Report Created' }, + { value: 'REPORT_SUBMITTED', label: 'Report Submitted' }, + { value: 'REPORT_APPROVED', label: 'Report Approved' } + ] + }, + { + name: 'invoice', + label: 'Invoice', + types: [ + { value: 'INVOICE_GENERATED', label: 'Invoice Generated' }, + { value: 'INVOICE_SENT', label: 'Invoice Sent' }, + { value: 'INVOICE_PAID', label: 'Invoice Paid' }, + { value: 'INVOICE_OVERDUE', label: 'Invoice Overdue' }, + { value: 'INVOICE_CANCELLED', label: 'Invoice Cancelled' } + ] + }, + { + name: 'labor_revenue', + label: 'Labor & Revenue', + types: [ + { value: 'LABOR_RATE_CREATED', label: 'Labor Rate Created' }, + { value: 'LABOR_RATE_UPDATED', label: 'Labor Rate Updated' }, + { value: 'LABOR_RATE_DELETED', label: 'Labor Rate Deleted' }, + { value: 'REVENUE_RATE_CREATED', label: 'Revenue Rate Created' }, + { value: 'REVENUE_RATE_UPDATED', label: 'Revenue Rate Updated' }, + { value: 'REVENUE_RATE_DELETED', label: 'Revenue Rate Deleted' } + ] + }, + { + name: 'messaging', + label: 'Messaging', + types: [ + { value: 'CONVERSATION_CREATED', label: 'Conversation Created' }, + { value: 'CONVERSATION_ARCHIVED', label: 'Conversation Archived' }, + { value: 'CONVERSATION_PARTICIPANT_ADDED', label: 'Participant Added to Conversation' }, + { value: 'CONVERSATION_PARTICIPANT_REMOVED', label: 'Participant Removed from Conversation' }, + { value: 'MESSAGE_SENT', label: 'Message Sent' }, + { value: 'MESSAGE_RECEIVED', label: 'Message Received' }, + { value: 'MESSAGE_READ', label: 'Message Read' }, + { value: 'MESSAGE_DELETED', label: 'Message Deleted' } + ] + }, + { + name: 'monitoring', + label: 'Monitoring', + types: [ + { value: 'MONITORING_COMMAND_EXECUTED', label: 'Monitoring Command Executed' }, + { value: 'MONITORING_INCOMPLETE_WORK_REMINDER', label: 'Incomplete Work Reminder' }, + { value: 'MONITORING_NIGHTLY_ASSIGNMENTS', label: 'Nightly Assignments' } + ] + } +]; + +// Flat list of all event types for validation +export const ALL_EVENT_TYPES = EVENT_TYPE_CATEGORIES.flatMap((cat) => + cat.types.map((t) => t.value) +); + +// Lookup map for quick label retrieval +export const EVENT_TYPE_LABELS: Record = Object.fromEntries( + EVENT_TYPE_CATEGORIES.flatMap((cat) => cat.types.map((t) => [t.value, t.label])) +); + +// Get label for an event type +export function getEventTypeLabel(eventType: string): string { + return ( + EVENT_TYPE_LABELS[eventType] || + eventType + .replace(/_/g, ' ') + .toLowerCase() + .replace(/\b\w/g, (c) => c.toUpperCase()) + ); +} diff --git a/src/lib/data/notificationMetadata.ts b/src/lib/data/notificationMetadata.ts new file mode 100644 index 0000000..eb5cdf6 --- /dev/null +++ b/src/lib/data/notificationMetadata.ts @@ -0,0 +1,121 @@ +// Notification template metadata field definitions +// Based on backend metadata enrichment per event domain + +export interface MetadataField { + name: string; + description: string; +} + +export interface MetadataDomain { + name: string; + label: string; + fields: MetadataField[]; + eventPrefixes: string[]; +} + +export const METADATA_DOMAINS: MetadataDomain[] = [ + { + name: 'service', + label: 'Service', + eventPrefixes: ['SERVICE_'], + fields: [ + { name: 'date', description: 'Service date (e.g., 2025-01-15)' }, + { name: 'account_name', description: 'Account name' }, + { name: 'customer_name', description: 'Parent customer name' }, + { name: 'address', description: 'Street and city' }, + { name: 'service_id', description: 'Service UUID (session events only)' } + ] + }, + { + name: 'project', + label: 'Project', + eventPrefixes: ['PROJECT_'], + fields: [ + { name: 'project_name', description: 'Project name' }, + { name: 'date', description: 'Project date' }, + { name: 'account_name', description: 'Account name (or customer name if direct)' }, + { name: 'customer_name', description: 'Parent customer name' }, + { name: 'address', description: 'Project location' }, + { name: 'project_id', description: 'Project UUID (session/punchlist events)' } + ] + }, + { + name: 'account', + label: 'Account', + eventPrefixes: ['ACCOUNT_'], + fields: [ + { name: 'account_name', description: 'Account name' }, + { name: 'customer_name', description: 'Parent customer name' }, + { name: 'account_id', description: 'Account UUID (address/contact/punchlist events)' }, + { name: 'address', description: 'Address street and city (address events only)' } + ] + }, + { + name: 'customer', + label: 'Customer', + eventPrefixes: ['CUSTOMER_'], + fields: [ + { name: 'customer_name', description: 'Customer name' }, + { name: 'customer_id', description: 'Customer UUID (address/contact events)' }, + { name: 'address', description: 'Address street and city (address events only)' } + ] + }, + { + name: 'schedule', + label: 'Schedule', + eventPrefixes: ['SCHEDULE_'], + fields: [ + { name: 'account_name', description: 'Account name' }, + { name: 'customer_name', description: 'Parent customer name' } + ] + }, + { + name: 'scope', + label: 'Scope', + eventPrefixes: ['SCOPE_', 'AREA_', 'TASK_'], + fields: [ + { name: 'account_name', description: 'Account name' }, + { name: 'customer_name', description: 'Parent customer name' } + ] + }, + { + name: 'punchlist', + label: 'Punchlist', + eventPrefixes: ['PUNCHLIST_'], + fields: [ + { name: 'account_name', description: 'Account name' }, + { name: 'customer_name', description: 'Parent customer name' }, + { name: 'project_name', description: 'Project name (project punchlists only)' } + ] + } +]; + +// Universal fields available for all events +export const UNIVERSAL_FIELDS: MetadataField[] = [ + { name: 'event_type', description: 'The type of event (e.g., SERVICE_COMPLETED)' }, + { name: 'entity_type', description: 'The type of entity (e.g., Service, Project)' }, + { name: 'entity_id', description: 'The ID of the related entity' }, + { name: 'timestamp', description: 'When the event occurred' } +]; + +/** + * Get the relevant metadata domains based on selected event types + */ +export function getActiveDomainsForEvents(eventTypes: string[]): MetadataDomain[] { + if (!eventTypes || eventTypes.length === 0) { + return []; + } + + const activeDomains = new Set(); + + for (const eventType of eventTypes) { + for (const domain of METADATA_DOMAINS) { + if (domain.eventPrefixes.some((prefix) => eventType.startsWith(prefix))) { + activeDomains.add(domain); + break; + } + } + } + + return Array.from(activeDomains); +} diff --git a/src/lib/graphql/client.ts b/src/lib/graphql/client.ts new file mode 100644 index 0000000..4a1208c --- /dev/null +++ b/src/lib/graphql/client.ts @@ -0,0 +1,29 @@ +import { HoudiniClient, fetch as houdiniFetch } from '$houdini'; + +const API_URL = 'https://api.example.com/v1/graphql/'; + +export default new HoudiniClient({ + url: API_URL, + plugins: [ + houdiniFetch(async ({ fetch, text, variables, metadata }) => { + const headers: Record = { + 'Content-Type': 'application/json' + }; + + // Server-side: forward cookie from metadata + const cookie = (metadata as { cookie?: string } | undefined)?.cookie; + if (cookie) { + headers['Cookie'] = cookie; + } + + const res = await fetch(API_URL, { + method: 'POST', + headers, + credentials: 'include', // Client-side: include cookies automatically + body: JSON.stringify({ query: text, variables }) + }); + + return await res.json(); + }) + ] +}); diff --git a/src/lib/graphql/mutations/accounts/CreateAccount.graphql b/src/lib/graphql/mutations/accounts/CreateAccount.graphql new file mode 100644 index 0000000..2f38107 --- /dev/null +++ b/src/lib/graphql/mutations/accounts/CreateAccount.graphql @@ -0,0 +1,42 @@ +mutation CreateAccount($input: AccountInput!) { + createAccount(input: $input) { + id + customerId + name + startDate + endDate + status + isActive + primaryAddress { + id + streetAddress + city + state + zipCode + isActive + isPrimary + notes + } + addresses { + id + streetAddress + city + state + zipCode + isActive + isPrimary + notes + } + contacts { + id + firstName + lastName + fullName + email + phone + isActive + isPrimary + notes + } + } +} diff --git a/src/lib/graphql/mutations/accounts/CreateAccountAddress.graphql b/src/lib/graphql/mutations/accounts/CreateAccountAddress.graphql new file mode 100644 index 0000000..a3ac273 --- /dev/null +++ b/src/lib/graphql/mutations/accounts/CreateAccountAddress.graphql @@ -0,0 +1,13 @@ +mutation CreateAccountAddress($input: AccountAddressInput!) { + createAccountAddress(input: $input) { + id + streetAddress + name + city + state + zipCode + isActive + isPrimary + notes + } +} diff --git a/src/lib/graphql/mutations/accounts/CreateAccountContact.graphql b/src/lib/graphql/mutations/accounts/CreateAccountContact.graphql new file mode 100644 index 0000000..1f9eb5a --- /dev/null +++ b/src/lib/graphql/mutations/accounts/CreateAccountContact.graphql @@ -0,0 +1,13 @@ +mutation CreateAccountContact($input: AccountContactInput!) { + createAccountContact(input: $input) { + id + firstName + lastName + fullName + email + phone + isActive + isPrimary + notes + } +} diff --git a/src/lib/graphql/mutations/accounts/CreateLabor.graphql b/src/lib/graphql/mutations/accounts/CreateLabor.graphql new file mode 100644 index 0000000..1837705 --- /dev/null +++ b/src/lib/graphql/mutations/accounts/CreateLabor.graphql @@ -0,0 +1,8 @@ +mutation CreateLabor($input: LaborInput!) { + createLabor(input: $input) { + id + amount + startDate + endDate + } +} diff --git a/src/lib/graphql/mutations/accounts/CreateRevenue.graphql b/src/lib/graphql/mutations/accounts/CreateRevenue.graphql new file mode 100644 index 0000000..3470db6 --- /dev/null +++ b/src/lib/graphql/mutations/accounts/CreateRevenue.graphql @@ -0,0 +1,9 @@ +mutation CreateRevenue($input: RevenueInput!) { + createRevenue(input: $input) { + id + amount + startDate + endDate + waveServiceId + } +} diff --git a/src/lib/graphql/mutations/accounts/DeleteAccount.graphql b/src/lib/graphql/mutations/accounts/DeleteAccount.graphql new file mode 100644 index 0000000..ee354ac --- /dev/null +++ b/src/lib/graphql/mutations/accounts/DeleteAccount.graphql @@ -0,0 +1,3 @@ +mutation DeleteAccount($id: ID!) { + deleteAccount(id: $id) +} diff --git a/src/lib/graphql/mutations/accounts/DeleteAccountAddress.graphql b/src/lib/graphql/mutations/accounts/DeleteAccountAddress.graphql new file mode 100644 index 0000000..4bf5f53 --- /dev/null +++ b/src/lib/graphql/mutations/accounts/DeleteAccountAddress.graphql @@ -0,0 +1,3 @@ +mutation DeleteAccountAddress($id: ID!) { + deleteAccountAddress(id: $id) +} diff --git a/src/lib/graphql/mutations/accounts/DeleteAccountContact.graphql b/src/lib/graphql/mutations/accounts/DeleteAccountContact.graphql new file mode 100644 index 0000000..89c9f26 --- /dev/null +++ b/src/lib/graphql/mutations/accounts/DeleteAccountContact.graphql @@ -0,0 +1,3 @@ +mutation DeleteAccountContact($id: ID!) { + deleteAccountContact(id: $id) +} diff --git a/src/lib/graphql/mutations/accounts/DeleteLabor.graphql b/src/lib/graphql/mutations/accounts/DeleteLabor.graphql new file mode 100644 index 0000000..f6e6e28 --- /dev/null +++ b/src/lib/graphql/mutations/accounts/DeleteLabor.graphql @@ -0,0 +1,3 @@ +mutation DeleteLabor($id: ID!) { + deleteLabor(id: $id) +} diff --git a/src/lib/graphql/mutations/accounts/DeleteRevenue.graphql b/src/lib/graphql/mutations/accounts/DeleteRevenue.graphql new file mode 100644 index 0000000..c054151 --- /dev/null +++ b/src/lib/graphql/mutations/accounts/DeleteRevenue.graphql @@ -0,0 +1,3 @@ +mutation DeleteRevenue($id: ID!) { + deleteRevenue(id: $id) +} diff --git a/src/lib/graphql/mutations/accounts/UpdateAccount.graphql b/src/lib/graphql/mutations/accounts/UpdateAccount.graphql new file mode 100644 index 0000000..5347de9 --- /dev/null +++ b/src/lib/graphql/mutations/accounts/UpdateAccount.graphql @@ -0,0 +1,11 @@ +mutation UpdateAccount($input: AccountUpdateInput!) { + updateAccount(input: $input) { + id + customerId + name + startDate + endDate + status + isActive + } +} diff --git a/src/lib/graphql/mutations/accounts/UpdateAccountAddress.graphql b/src/lib/graphql/mutations/accounts/UpdateAccountAddress.graphql new file mode 100644 index 0000000..b10120c --- /dev/null +++ b/src/lib/graphql/mutations/accounts/UpdateAccountAddress.graphql @@ -0,0 +1,13 @@ +mutation UpdateAccountAddress($input: AccountAddressUpdateInput!) { + updateAccountAddress(input: $input) { + id + streetAddress + name + city + state + zipCode + isActive + isPrimary + notes + } +} diff --git a/src/lib/graphql/mutations/accounts/UpdateAccountContact.graphql b/src/lib/graphql/mutations/accounts/UpdateAccountContact.graphql new file mode 100644 index 0000000..ccef8ca --- /dev/null +++ b/src/lib/graphql/mutations/accounts/UpdateAccountContact.graphql @@ -0,0 +1,13 @@ +mutation UpdateAccountContact($input: AccountContactUpdateInput!) { + updateAccountContact(input: $input) { + id + firstName + lastName + fullName + email + phone + isActive + isPrimary + notes + } +} diff --git a/src/lib/graphql/mutations/accounts/UpdateLabor.graphql b/src/lib/graphql/mutations/accounts/UpdateLabor.graphql new file mode 100644 index 0000000..fc0f2f7 --- /dev/null +++ b/src/lib/graphql/mutations/accounts/UpdateLabor.graphql @@ -0,0 +1,8 @@ +mutation UpdateLabor($input: LaborUpdateInput!) { + updateLabor(input: $input) { + id + amount + startDate + endDate + } +} diff --git a/src/lib/graphql/mutations/accounts/UpdateRevenue.graphql b/src/lib/graphql/mutations/accounts/UpdateRevenue.graphql new file mode 100644 index 0000000..b951f2d --- /dev/null +++ b/src/lib/graphql/mutations/accounts/UpdateRevenue.graphql @@ -0,0 +1,9 @@ +mutation UpdateRevenue($input: RevenueUpdateInput!) { + updateRevenue(input: $input) { + id + amount + startDate + endDate + waveServiceId + } +} diff --git a/src/lib/graphql/mutations/customers/CreateCustomer.graphql b/src/lib/graphql/mutations/customers/CreateCustomer.graphql new file mode 100644 index 0000000..080e119 --- /dev/null +++ b/src/lib/graphql/mutations/customers/CreateCustomer.graphql @@ -0,0 +1,33 @@ +mutation CreateCustomer($input: CustomerInput!) { + createCustomer(input: $input) { + id + name + billingEmail + billingTerms + startDate + endDate + status + isActive + addresses { + id + streetAddress + city + state + zipCode + addressType + isActive + isPrimary + } + contacts { + id + firstName + lastName + fullName + email + phone + isActive + isPrimary + notes + } + } +} diff --git a/src/lib/graphql/mutations/customers/CreateCustomerAddress.graphql b/src/lib/graphql/mutations/customers/CreateCustomerAddress.graphql new file mode 100644 index 0000000..f88727d --- /dev/null +++ b/src/lib/graphql/mutations/customers/CreateCustomerAddress.graphql @@ -0,0 +1,12 @@ +mutation CreateCustomerAddress($input: CustomerAddressInput!) { + createCustomerAddress(input: $input) { + id + streetAddress + city + state + zipCode + addressType + isActive + isPrimary + } +} diff --git a/src/lib/graphql/mutations/customers/CreateCustomerContact.graphql b/src/lib/graphql/mutations/customers/CreateCustomerContact.graphql new file mode 100644 index 0000000..2e6d564 --- /dev/null +++ b/src/lib/graphql/mutations/customers/CreateCustomerContact.graphql @@ -0,0 +1,13 @@ +mutation CreateCustomerContact($input: CustomerContactInput!) { + createCustomerContact(input: $input) { + id + firstName + lastName + fullName + email + phone + isActive + isPrimary + notes + } +} diff --git a/src/lib/graphql/mutations/customers/DeleteCustomer.graphql b/src/lib/graphql/mutations/customers/DeleteCustomer.graphql new file mode 100644 index 0000000..7ff0221 --- /dev/null +++ b/src/lib/graphql/mutations/customers/DeleteCustomer.graphql @@ -0,0 +1,3 @@ +mutation DeleteCustomer($id: ID!) { + deleteCustomer(id: $id) +} diff --git a/src/lib/graphql/mutations/customers/DeleteCustomerAddress.graphql b/src/lib/graphql/mutations/customers/DeleteCustomerAddress.graphql new file mode 100644 index 0000000..775764f --- /dev/null +++ b/src/lib/graphql/mutations/customers/DeleteCustomerAddress.graphql @@ -0,0 +1,3 @@ +mutation DeleteCustomerAddress($id: ID!) { + deleteCustomerAddress(id: $id) +} diff --git a/src/lib/graphql/mutations/customers/DeleteCustomerContact.graphql b/src/lib/graphql/mutations/customers/DeleteCustomerContact.graphql new file mode 100644 index 0000000..1ffdab1 --- /dev/null +++ b/src/lib/graphql/mutations/customers/DeleteCustomerContact.graphql @@ -0,0 +1,3 @@ +mutation DeleteCustomerContact($id: ID!) { + deleteCustomerContact(id: $id) +} diff --git a/src/lib/graphql/mutations/customers/UpdateCustomer.graphql b/src/lib/graphql/mutations/customers/UpdateCustomer.graphql new file mode 100644 index 0000000..25f522b --- /dev/null +++ b/src/lib/graphql/mutations/customers/UpdateCustomer.graphql @@ -0,0 +1,13 @@ +mutation UpdateCustomer($input: CustomerUpdateInput!) { + updateCustomer(input: $input) { + id + name + billingEmail + billingTerms + startDate + endDate + status + isActive + waveCustomerId + } +} diff --git a/src/lib/graphql/mutations/customers/UpdateCustomerAddress.graphql b/src/lib/graphql/mutations/customers/UpdateCustomerAddress.graphql new file mode 100644 index 0000000..d981fb9 --- /dev/null +++ b/src/lib/graphql/mutations/customers/UpdateCustomerAddress.graphql @@ -0,0 +1,12 @@ +mutation UpdateCustomerAddress($input: CustomerAddressUpdateInput!) { + updateCustomerAddress(input: $input) { + id + streetAddress + city + state + zipCode + addressType + isActive + isPrimary + } +} diff --git a/src/lib/graphql/mutations/customers/UpdateCustomerContact.graphql b/src/lib/graphql/mutations/customers/UpdateCustomerContact.graphql new file mode 100644 index 0000000..7f62397 --- /dev/null +++ b/src/lib/graphql/mutations/customers/UpdateCustomerContact.graphql @@ -0,0 +1,13 @@ +mutation UpdateCustomerContact($input: CustomerContactUpdateInput!) { + updateCustomerContact(input: $input) { + id + firstName + lastName + fullName + email + phone + isActive + isPrimary + notes + } +} diff --git a/src/lib/graphql/mutations/invoice/CreateInvoice.graphql b/src/lib/graphql/mutations/invoice/CreateInvoice.graphql new file mode 100644 index 0000000..6332005 --- /dev/null +++ b/src/lib/graphql/mutations/invoice/CreateInvoice.graphql @@ -0,0 +1,10 @@ +mutation CreateInvoice($input: InvoiceInput!) { + createInvoice(input: $input) { + id + date + customerId + status + datePaid + paymentType + } +} diff --git a/src/lib/graphql/mutations/invoice/DeleteInvoice.graphql b/src/lib/graphql/mutations/invoice/DeleteInvoice.graphql new file mode 100644 index 0000000..47076a3 --- /dev/null +++ b/src/lib/graphql/mutations/invoice/DeleteInvoice.graphql @@ -0,0 +1,3 @@ +mutation DeleteInvoice($id: ID!) { + deleteInvoice(id: $id) +} diff --git a/src/lib/graphql/mutations/invoice/UpdateInvoice.graphql b/src/lib/graphql/mutations/invoice/UpdateInvoice.graphql new file mode 100644 index 0000000..b03610f --- /dev/null +++ b/src/lib/graphql/mutations/invoice/UpdateInvoice.graphql @@ -0,0 +1,11 @@ +mutation UpdateInvoice($input: InvoiceUpdateInput!) { + updateInvoice(input: $input) { + id + date + customerId + status + datePaid + paymentType + waveInvoiceId + } +} diff --git a/src/lib/graphql/mutations/messages/ArchiveConversation.graphql b/src/lib/graphql/mutations/messages/ArchiveConversation.graphql new file mode 100644 index 0000000..823d12c --- /dev/null +++ b/src/lib/graphql/mutations/messages/ArchiveConversation.graphql @@ -0,0 +1,6 @@ +mutation ArchiveConversation($input: ArchiveConversationInput!) { + archiveConversation(input: $input) { + id + isArchived + } +} diff --git a/src/lib/graphql/mutations/messages/CreateConversation.graphql b/src/lib/graphql/mutations/messages/CreateConversation.graphql new file mode 100644 index 0000000..7fdfe41 --- /dev/null +++ b/src/lib/graphql/mutations/messages/CreateConversation.graphql @@ -0,0 +1,38 @@ +mutation CreateConversation($input: ConversationInput!) { + createConversation(input: $input) { + id + subject + conversationType + lastMessageAt + isArchived + metadata + createdAt + createdBy { + teamProfile { + id + fullName + } + customerProfile { + id + fullName + } + } + entity { + entityType + entityId + } + participants { + id + participant { + teamProfile { + id + fullName + } + customerProfile { + id + fullName + } + } + } + } +} diff --git a/src/lib/graphql/mutations/messages/DeleteConversation.graphql b/src/lib/graphql/mutations/messages/DeleteConversation.graphql new file mode 100644 index 0000000..bc6eccf --- /dev/null +++ b/src/lib/graphql/mutations/messages/DeleteConversation.graphql @@ -0,0 +1,3 @@ +mutation DeleteConversation($id: ID!) { + deleteConversation(id: $id) +} diff --git a/src/lib/graphql/mutations/messages/DeleteMessage.graphql b/src/lib/graphql/mutations/messages/DeleteMessage.graphql new file mode 100644 index 0000000..a8537cc --- /dev/null +++ b/src/lib/graphql/mutations/messages/DeleteMessage.graphql @@ -0,0 +1,3 @@ +mutation DeleteMessage($id: ID!) { + deleteMessage(id: $id) +} diff --git a/src/lib/graphql/mutations/messages/MarkConversationAsRead.graphql b/src/lib/graphql/mutations/messages/MarkConversationAsRead.graphql new file mode 100644 index 0000000..18c4d6a --- /dev/null +++ b/src/lib/graphql/mutations/messages/MarkConversationAsRead.graphql @@ -0,0 +1,6 @@ +mutation MarkConversationAsRead($input: MarkAsReadInput!) { + markConversationAsRead(input: $input) { + id + unreadCount + } +} diff --git a/src/lib/graphql/mutations/messages/MuteConversation.graphql b/src/lib/graphql/mutations/messages/MuteConversation.graphql new file mode 100644 index 0000000..e66cfd2 --- /dev/null +++ b/src/lib/graphql/mutations/messages/MuteConversation.graphql @@ -0,0 +1,5 @@ +mutation MuteConversation($input: MuteConversationInput!) { + muteConversation(input: $input) { + id + } +} diff --git a/src/lib/graphql/mutations/messages/SendMessage.graphql b/src/lib/graphql/mutations/messages/SendMessage.graphql new file mode 100644 index 0000000..480f4f3 --- /dev/null +++ b/src/lib/graphql/mutations/messages/SendMessage.graphql @@ -0,0 +1,26 @@ +mutation SendMessage($input: MessageInput!) { + sendMessage(input: $input) { + id + body + isSystemMessage + attachments + metadata + createdAt + updatedAt + canDelete + sender { + teamProfile { + id + fullName + } + customerProfile { + id + fullName + } + } + replyTo { + id + body + } + } +} diff --git a/src/lib/graphql/mutations/notifications/CreateNotificationRule.graphql b/src/lib/graphql/mutations/notifications/CreateNotificationRule.graphql new file mode 100644 index 0000000..5900864 --- /dev/null +++ b/src/lib/graphql/mutations/notifications/CreateNotificationRule.graphql @@ -0,0 +1,16 @@ +mutation CreateNotificationRule($input: NotificationRuleInput!) { + createNotificationRule(input: $input) { + id + name + description + eventTypes + channels + targetRoles + targetTeamProfileIds + targetCustomerProfileIds + isActive + templateSubject + templateBody + conditions + } +} diff --git a/src/lib/graphql/mutations/notifications/DeleteNotification.graphql b/src/lib/graphql/mutations/notifications/DeleteNotification.graphql new file mode 100644 index 0000000..d43ba75 --- /dev/null +++ b/src/lib/graphql/mutations/notifications/DeleteNotification.graphql @@ -0,0 +1,3 @@ +mutation DeleteNotification($id: ID!) { + deleteNotification(id: $id) +} diff --git a/src/lib/graphql/mutations/notifications/DeleteNotificationRule.graphql b/src/lib/graphql/mutations/notifications/DeleteNotificationRule.graphql new file mode 100644 index 0000000..294a08d --- /dev/null +++ b/src/lib/graphql/mutations/notifications/DeleteNotificationRule.graphql @@ -0,0 +1,3 @@ +mutation DeleteNotificationRule($id: ID!) { + deleteNotificationRule(id: $id) +} diff --git a/src/lib/graphql/mutations/notifications/MarkAllNotificationsAsRead.graphql b/src/lib/graphql/mutations/notifications/MarkAllNotificationsAsRead.graphql new file mode 100644 index 0000000..2c20a1d --- /dev/null +++ b/src/lib/graphql/mutations/notifications/MarkAllNotificationsAsRead.graphql @@ -0,0 +1,3 @@ +mutation MarkAllNotificationsAsRead { + markAllNotificationsAsRead +} diff --git a/src/lib/graphql/mutations/notifications/MarkNotificationAsRead.graphql b/src/lib/graphql/mutations/notifications/MarkNotificationAsRead.graphql new file mode 100644 index 0000000..ede9567 --- /dev/null +++ b/src/lib/graphql/mutations/notifications/MarkNotificationAsRead.graphql @@ -0,0 +1,7 @@ +mutation MarkNotificationAsRead($id: ID!) { + markNotificationAsRead(id: $id) { + id + isRead + readAt + } +} diff --git a/src/lib/graphql/mutations/notifications/UpdateNotificationRule.graphql b/src/lib/graphql/mutations/notifications/UpdateNotificationRule.graphql new file mode 100644 index 0000000..35860ec --- /dev/null +++ b/src/lib/graphql/mutations/notifications/UpdateNotificationRule.graphql @@ -0,0 +1,16 @@ +mutation UpdateNotificationRule($input: NotificationRuleUpdateInput!) { + updateNotificationRule(input: $input) { + id + name + description + eventTypes + channels + targetRoles + targetTeamProfileIds + targetCustomerProfileIds + isActive + templateSubject + templateBody + conditions + } +} diff --git a/src/lib/graphql/mutations/profiles/CreateCustomerProfile.graphql b/src/lib/graphql/mutations/profiles/CreateCustomerProfile.graphql new file mode 100644 index 0000000..d231fa3 --- /dev/null +++ b/src/lib/graphql/mutations/profiles/CreateCustomerProfile.graphql @@ -0,0 +1,16 @@ +mutation CreateCustomerProfile($input: CustomerProfileInput!) { + createCustomerProfile(input: $input) { + id + firstName + lastName + fullName + email + phone + status + notes + customers { + id + name + } + } +} diff --git a/src/lib/graphql/mutations/profiles/CreateTeamProfile.graphql b/src/lib/graphql/mutations/profiles/CreateTeamProfile.graphql new file mode 100644 index 0000000..abd8090 --- /dev/null +++ b/src/lib/graphql/mutations/profiles/CreateTeamProfile.graphql @@ -0,0 +1,13 @@ +mutation CreateTeamProfile($input: TeamProfileInput!) { + createTeamProfile(input: $input) { + id + firstName + lastName + fullName + email + phone + status + notes + role + } +} diff --git a/src/lib/graphql/mutations/profiles/DeleteCustomerProfile.graphql b/src/lib/graphql/mutations/profiles/DeleteCustomerProfile.graphql new file mode 100644 index 0000000..0dff2f7 --- /dev/null +++ b/src/lib/graphql/mutations/profiles/DeleteCustomerProfile.graphql @@ -0,0 +1,3 @@ +mutation DeleteCustomerProfile($id: ID!) { + deleteCustomerProfile(id: $id) +} diff --git a/src/lib/graphql/mutations/profiles/DeleteTeamProfile.graphql b/src/lib/graphql/mutations/profiles/DeleteTeamProfile.graphql new file mode 100644 index 0000000..5d43297 --- /dev/null +++ b/src/lib/graphql/mutations/profiles/DeleteTeamProfile.graphql @@ -0,0 +1,3 @@ +mutation DeleteTeamProfile($id: ID!) { + deleteTeamProfile(id: $id) +} diff --git a/src/lib/graphql/mutations/profiles/UpdateCustomerProfile.graphql b/src/lib/graphql/mutations/profiles/UpdateCustomerProfile.graphql new file mode 100644 index 0000000..c5ef495 --- /dev/null +++ b/src/lib/graphql/mutations/profiles/UpdateCustomerProfile.graphql @@ -0,0 +1,16 @@ +mutation UpdateCustomerProfile($input: CustomerProfileUpdateInput!) { + updateCustomerProfile(input: $input) { + id + firstName + lastName + fullName + email + phone + status + notes + customers { + id + name + } + } +} diff --git a/src/lib/graphql/mutations/profiles/UpdateTeamProfile.graphql b/src/lib/graphql/mutations/profiles/UpdateTeamProfile.graphql new file mode 100644 index 0000000..46443f3 --- /dev/null +++ b/src/lib/graphql/mutations/profiles/UpdateTeamProfile.graphql @@ -0,0 +1,13 @@ +mutation UpdateTeamProfile($input: TeamProfileUpdateInput!) { + updateTeamProfile(input: $input) { + id + firstName + lastName + fullName + email + phone + status + notes + role + } +} diff --git a/src/lib/graphql/mutations/projectScopes/CreateProjectScope.graphql b/src/lib/graphql/mutations/projectScopes/CreateProjectScope.graphql new file mode 100644 index 0000000..c77ca47 --- /dev/null +++ b/src/lib/graphql/mutations/projectScopes/CreateProjectScope.graphql @@ -0,0 +1,23 @@ +mutation CreateProjectScope($input: ProjectScopeInput!) { + createProjectScope(input: $input) { + id + projectId + name + description + isActive + projectAreas { + id + name + order + scopeId + projectTasks { + id + categoryId + description + checklistDescription + estimatedMinutes + order + } + } + } +} diff --git a/src/lib/graphql/mutations/projectScopes/CreateProjectScopeCategory.graphql b/src/lib/graphql/mutations/projectScopes/CreateProjectScopeCategory.graphql new file mode 100644 index 0000000..1bbba82 --- /dev/null +++ b/src/lib/graphql/mutations/projectScopes/CreateProjectScopeCategory.graphql @@ -0,0 +1,16 @@ +mutation CreateProjectScopeCategory($input: ProjectScopeCategoryInput!) { + createProjectScopeCategory(input: $input) { + id + name + order + scopeId + projectTasks { + id + categoryId + description + checklistDescription + estimatedMinutes + order + } + } +} diff --git a/src/lib/graphql/mutations/projectScopes/CreateProjectScopeFromTemplate.graphql b/src/lib/graphql/mutations/projectScopes/CreateProjectScopeFromTemplate.graphql new file mode 100644 index 0000000..ebfcb5f --- /dev/null +++ b/src/lib/graphql/mutations/projectScopes/CreateProjectScopeFromTemplate.graphql @@ -0,0 +1,23 @@ +mutation CreateProjectScopeFromTemplate($input: CreateProjectScopeFromTemplateInput!) { + createProjectScopeFromTemplate(input: $input) { + id + projectId + name + description + isActive + projectAreas { + id + name + order + scopeId + projectTasks { + id + categoryId + description + checklistDescription + estimatedMinutes + order + } + } + } +} diff --git a/src/lib/graphql/mutations/projectScopes/CreateProjectScopeTask.graphql b/src/lib/graphql/mutations/projectScopes/CreateProjectScopeTask.graphql new file mode 100644 index 0000000..e3f82b0 --- /dev/null +++ b/src/lib/graphql/mutations/projectScopes/CreateProjectScopeTask.graphql @@ -0,0 +1,10 @@ +mutation CreateProjectScopeTask($input: ProjectScopeTaskInput!) { + createProjectScopeTask(input: $input) { + id + categoryId + description + checklistDescription + estimatedMinutes + order + } +} diff --git a/src/lib/graphql/mutations/projectScopes/DeleteProjectScope.graphql b/src/lib/graphql/mutations/projectScopes/DeleteProjectScope.graphql new file mode 100644 index 0000000..f7bdb4d --- /dev/null +++ b/src/lib/graphql/mutations/projectScopes/DeleteProjectScope.graphql @@ -0,0 +1,3 @@ +mutation DeleteProjectScope($id: ID!) { + deleteProjectScope(id: $id) +} diff --git a/src/lib/graphql/mutations/projectScopes/DeleteProjectScopeCategory.graphql b/src/lib/graphql/mutations/projectScopes/DeleteProjectScopeCategory.graphql new file mode 100644 index 0000000..e4aa6c7 --- /dev/null +++ b/src/lib/graphql/mutations/projectScopes/DeleteProjectScopeCategory.graphql @@ -0,0 +1,3 @@ +mutation DeleteProjectScopeCategory($id: ID!) { + deleteProjectScopeCategory(id: $id) +} diff --git a/src/lib/graphql/mutations/projectScopes/DeleteProjectScopeTask.graphql b/src/lib/graphql/mutations/projectScopes/DeleteProjectScopeTask.graphql new file mode 100644 index 0000000..502a7c8 --- /dev/null +++ b/src/lib/graphql/mutations/projectScopes/DeleteProjectScopeTask.graphql @@ -0,0 +1,3 @@ +mutation DeleteProjectScopeTask($id: ID!) { + deleteProjectScopeTask(id: $id) +} diff --git a/src/lib/graphql/mutations/projectScopes/UpdateProjectScope.graphql b/src/lib/graphql/mutations/projectScopes/UpdateProjectScope.graphql new file mode 100644 index 0000000..5bf8085 --- /dev/null +++ b/src/lib/graphql/mutations/projectScopes/UpdateProjectScope.graphql @@ -0,0 +1,23 @@ +mutation UpdateProjectScope($input: ProjectScopeUpdateInput!) { + updateProjectScope(input: $input) { + id + projectId + name + description + isActive + projectAreas { + id + name + order + scopeId + projectTasks { + id + categoryId + description + checklistDescription + estimatedMinutes + order + } + } + } +} diff --git a/src/lib/graphql/mutations/projectScopes/UpdateProjectScopeCategory.graphql b/src/lib/graphql/mutations/projectScopes/UpdateProjectScopeCategory.graphql new file mode 100644 index 0000000..718bb05 --- /dev/null +++ b/src/lib/graphql/mutations/projectScopes/UpdateProjectScopeCategory.graphql @@ -0,0 +1,16 @@ +mutation UpdateProjectScopeCategory($input: ProjectScopeCategoryUpdateInput!) { + updateProjectScopeCategory(input: $input) { + id + name + order + scopeId + projectTasks { + id + categoryId + description + checklistDescription + estimatedMinutes + order + } + } +} diff --git a/src/lib/graphql/mutations/projectScopes/UpdateProjectScopeTask.graphql b/src/lib/graphql/mutations/projectScopes/UpdateProjectScopeTask.graphql new file mode 100644 index 0000000..1901fe9 --- /dev/null +++ b/src/lib/graphql/mutations/projectScopes/UpdateProjectScopeTask.graphql @@ -0,0 +1,10 @@ +mutation UpdateProjectScopeTask($input: ProjectScopeTaskUpdateInput!) { + updateProjectScopeTask(input: $input) { + id + categoryId + description + checklistDescription + estimatedMinutes + order + } +} diff --git a/src/lib/graphql/mutations/projects/CreateProject.graphql b/src/lib/graphql/mutations/projects/CreateProject.graphql new file mode 100644 index 0000000..703d894 --- /dev/null +++ b/src/lib/graphql/mutations/projects/CreateProject.graphql @@ -0,0 +1,22 @@ +mutation CreateProject($input: ProjectInput!) { + createProject(input: $input) { + id + name + date + status + amount + labor + customerId + accountAddressId + city + state + streetAddress + zipCode + notes + scopeId + waveServiceId + teamMembers { + pk + } + } +} diff --git a/src/lib/graphql/mutations/projects/DeleteProject.graphql b/src/lib/graphql/mutations/projects/DeleteProject.graphql new file mode 100644 index 0000000..475f020 --- /dev/null +++ b/src/lib/graphql/mutations/projects/DeleteProject.graphql @@ -0,0 +1,3 @@ +mutation DeleteProject($id: ID!) { + deleteProject(id: $id) +} diff --git a/src/lib/graphql/mutations/projects/UpdateProject.graphql b/src/lib/graphql/mutations/projects/UpdateProject.graphql new file mode 100644 index 0000000..4a70ab0 --- /dev/null +++ b/src/lib/graphql/mutations/projects/UpdateProject.graphql @@ -0,0 +1,23 @@ +mutation UpdateProject($input: ProjectUpdateInput!) { + updateProject(input: $input) { + id + name + date + status + amount + labor + customerId + accountAddressId + city + state + streetAddress + zipCode + notes + scopeId + calendarEventId + waveServiceId + teamMembers { + pk + } + } +} diff --git a/src/lib/graphql/mutations/report/CreateReport.graphql b/src/lib/graphql/mutations/report/CreateReport.graphql new file mode 100644 index 0000000..db6f0c3 --- /dev/null +++ b/src/lib/graphql/mutations/report/CreateReport.graphql @@ -0,0 +1,10 @@ +mutation CreateReport($input: ReportInput!) { + createReport(input: $input) { + id + date + teamMemberId + servicesLaborTotal + projectsLaborTotal + totalLaborValue + } +} diff --git a/src/lib/graphql/mutations/report/DeleteReport.graphql b/src/lib/graphql/mutations/report/DeleteReport.graphql new file mode 100644 index 0000000..9d979dd --- /dev/null +++ b/src/lib/graphql/mutations/report/DeleteReport.graphql @@ -0,0 +1,3 @@ +mutation DeleteReport($id: ID!) { + deleteReport(id: $id) +} diff --git a/src/lib/graphql/mutations/report/UpdateReport.graphql b/src/lib/graphql/mutations/report/UpdateReport.graphql new file mode 100644 index 0000000..3cc77ed --- /dev/null +++ b/src/lib/graphql/mutations/report/UpdateReport.graphql @@ -0,0 +1,10 @@ +mutation UpdateReport($input: ReportUpdateInput!) { + updateReport(input: $input) { + id + date + teamMemberId + servicesLaborTotal + projectsLaborTotal + totalLaborValue + } +} diff --git a/src/lib/graphql/mutations/schedule/CreateSchedule.graphql b/src/lib/graphql/mutations/schedule/CreateSchedule.graphql new file mode 100644 index 0000000..88bade6 --- /dev/null +++ b/src/lib/graphql/mutations/schedule/CreateSchedule.graphql @@ -0,0 +1,17 @@ +mutation CreateSchedule($input: ScheduleInput!) { + createSchedule(input: $input) { + id + name + startDate + endDate + mondayService + tuesdayService + wednesdayService + thursdayService + fridayService + saturdayService + sundayService + weekendService + scheduleException + } +} diff --git a/src/lib/graphql/mutations/schedule/DeleteSchedule.graphql b/src/lib/graphql/mutations/schedule/DeleteSchedule.graphql new file mode 100644 index 0000000..38715a9 --- /dev/null +++ b/src/lib/graphql/mutations/schedule/DeleteSchedule.graphql @@ -0,0 +1,3 @@ +mutation DeleteSchedule($id: ID!) { + deleteSchedule(id: $id) +} diff --git a/src/lib/graphql/mutations/schedule/UpdateSchedule.graphql b/src/lib/graphql/mutations/schedule/UpdateSchedule.graphql new file mode 100644 index 0000000..f608e11 --- /dev/null +++ b/src/lib/graphql/mutations/schedule/UpdateSchedule.graphql @@ -0,0 +1,18 @@ +mutation UpdateSchedule($input: ScheduleUpdateInput!) { + updateSchedule(input: $input) { + id + name + startDate + endDate + mondayService + tuesdayService + wednesdayService + thursdayService + fridayService + saturdayService + sundayService + weekendService + scheduleException + accountAddressId + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/CreateAreaTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/CreateAreaTemplate.graphql new file mode 100644 index 0000000..bea2064 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/CreateAreaTemplate.graphql @@ -0,0 +1,8 @@ +mutation CreateAreaTemplate($input: AreaTemplateInput!) { + createAreaTemplate(input: $input) { + id + name + order + scopeTemplateId + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/CreateProjectAreaTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/CreateProjectAreaTemplate.graphql new file mode 100644 index 0000000..539f1ec --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/CreateProjectAreaTemplate.graphql @@ -0,0 +1,8 @@ +mutation CreateProjectAreaTemplate($input: ProjectAreaTemplateInput!) { + createProjectAreaTemplate(input: $input) { + id + name + order + scopeTemplateId + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/CreateProjectScopeTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/CreateProjectScopeTemplate.graphql new file mode 100644 index 0000000..e944d89 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/CreateProjectScopeTemplate.graphql @@ -0,0 +1,8 @@ +mutation CreateProjectScopeTemplate($input: ProjectScopeTemplateInput!) { + createProjectScopeTemplate(input: $input) { + id + name + description + isActive + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/CreateProjectScopeTemplateFromJson.graphql b/src/lib/graphql/mutations/scopeTemplates/CreateProjectScopeTemplateFromJson.graphql new file mode 100644 index 0000000..50207e8 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/CreateProjectScopeTemplateFromJson.graphql @@ -0,0 +1,20 @@ +mutation CreateProjectScopeTemplateFromJson($payload: JSON!, $replace: Boolean = false) { + createProjectScopeTemplateFromJson(payload: $payload, replace: $replace) { + id + name + description + isActive + categoryTemplates { + id + name + order + taskTemplates { + id + description + checklistDescription + order + estimatedMinutes + } + } + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/CreateProjectTaskTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/CreateProjectTaskTemplate.graphql new file mode 100644 index 0000000..9672955 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/CreateProjectTaskTemplate.graphql @@ -0,0 +1,10 @@ +mutation CreateProjectTaskTemplate($input: ProjectTaskTemplateInput!) { + createProjectTaskTemplate(input: $input) { + id + description + checklistDescription + order + estimatedMinutes + areaTemplateId + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/CreateScopeTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/CreateScopeTemplate.graphql new file mode 100644 index 0000000..2aa4d10 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/CreateScopeTemplate.graphql @@ -0,0 +1,8 @@ +mutation CreateScopeTemplate($input: ScopeTemplateInput!) { + createScopeTemplate(input: $input) { + id + name + description + isActive + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/CreateScopeTemplateFromJson.graphql b/src/lib/graphql/mutations/scopeTemplates/CreateScopeTemplateFromJson.graphql new file mode 100644 index 0000000..34ac7c1 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/CreateScopeTemplateFromJson.graphql @@ -0,0 +1,22 @@ +mutation CreateScopeTemplateFromJson($payload: JSON!, $replace: Boolean = false) { + createScopeTemplateFromJson(payload: $payload, replace: $replace) { + id + name + description + isActive + areaTemplates { + id + name + order + taskTemplates { + id + description + checklistDescription + frequency + isConditional + order + estimatedMinutes + } + } + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/CreateTaskTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/CreateTaskTemplate.graphql new file mode 100644 index 0000000..ab8ff33 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/CreateTaskTemplate.graphql @@ -0,0 +1,12 @@ +mutation CreateTaskTemplate($input: TaskTemplateInput!) { + createTaskTemplate(input: $input) { + id + description + checklistDescription + frequency + isConditional + order + estimatedMinutes + areaTemplateId + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/DeleteAreaTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/DeleteAreaTemplate.graphql new file mode 100644 index 0000000..a405c63 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/DeleteAreaTemplate.graphql @@ -0,0 +1,3 @@ +mutation DeleteAreaTemplate($id: ID!) { + deleteAreaTemplate(id: $id) +} diff --git a/src/lib/graphql/mutations/scopeTemplates/DeleteProjectAreaTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/DeleteProjectAreaTemplate.graphql new file mode 100644 index 0000000..907d0dc --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/DeleteProjectAreaTemplate.graphql @@ -0,0 +1,3 @@ +mutation DeleteProjectAreaTemplate($id: ID!) { + deleteProjectAreaTemplate(id: $id) +} diff --git a/src/lib/graphql/mutations/scopeTemplates/DeleteProjectScopeTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/DeleteProjectScopeTemplate.graphql new file mode 100644 index 0000000..74d8193 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/DeleteProjectScopeTemplate.graphql @@ -0,0 +1,3 @@ +mutation DeleteProjectScopeTemplate($id: ID!) { + deleteProjectScopeTemplate(id: $id) +} diff --git a/src/lib/graphql/mutations/scopeTemplates/DeleteProjectTaskTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/DeleteProjectTaskTemplate.graphql new file mode 100644 index 0000000..cb1263b --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/DeleteProjectTaskTemplate.graphql @@ -0,0 +1,3 @@ +mutation DeleteProjectTaskTemplate($id: ID!) { + deleteProjectTaskTemplate(id: $id) +} diff --git a/src/lib/graphql/mutations/scopeTemplates/DeleteScopeTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/DeleteScopeTemplate.graphql new file mode 100644 index 0000000..b2f341e --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/DeleteScopeTemplate.graphql @@ -0,0 +1,3 @@ +mutation DeleteScopeTemplate($id: ID!) { + deleteScopeTemplate(id: $id) +} diff --git a/src/lib/graphql/mutations/scopeTemplates/DeleteTaskTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/DeleteTaskTemplate.graphql new file mode 100644 index 0000000..42ca12a --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/DeleteTaskTemplate.graphql @@ -0,0 +1,3 @@ +mutation DeleteTaskTemplate($id: ID!) { + deleteTaskTemplate(id: $id) +} diff --git a/src/lib/graphql/mutations/scopeTemplates/UpdateAreaTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/UpdateAreaTemplate.graphql new file mode 100644 index 0000000..d31d66e --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/UpdateAreaTemplate.graphql @@ -0,0 +1,8 @@ +mutation UpdateAreaTemplate($input: AreaTemplateUpdateInput!) { + updateAreaTemplate(input: $input) { + id + name + order + scopeTemplateId + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/UpdateProjectAreaTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/UpdateProjectAreaTemplate.graphql new file mode 100644 index 0000000..7b49480 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/UpdateProjectAreaTemplate.graphql @@ -0,0 +1,7 @@ +mutation UpdateProjectAreaTemplate($input: ProjectAreaTemplateUpdateInput!) { + updateProjectAreaTemplate(input: $input) { + id + name + order + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/UpdateProjectScopeTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/UpdateProjectScopeTemplate.graphql new file mode 100644 index 0000000..301f6d4 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/UpdateProjectScopeTemplate.graphql @@ -0,0 +1,8 @@ +mutation UpdateProjectScopeTemplate($input: ProjectScopeTemplateUpdateInput!) { + updateProjectScopeTemplate(input: $input) { + id + name + description + isActive + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/UpdateProjectTaskTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/UpdateProjectTaskTemplate.graphql new file mode 100644 index 0000000..3c3b0d0 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/UpdateProjectTaskTemplate.graphql @@ -0,0 +1,10 @@ +mutation UpdateProjectTaskTemplate($input: ProjectTaskTemplateUpdateInput!) { + updateProjectTaskTemplate(input: $input) { + id + description + checklistDescription + order + estimatedMinutes + areaTemplateId + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/UpdateScopeTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/UpdateScopeTemplate.graphql new file mode 100644 index 0000000..003b617 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/UpdateScopeTemplate.graphql @@ -0,0 +1,8 @@ +mutation UpdateScopeTemplate($input: ScopeTemplateUpdateInput!) { + updateScopeTemplate(input: $input) { + id + name + description + isActive + } +} diff --git a/src/lib/graphql/mutations/scopeTemplates/UpdateTaskTemplate.graphql b/src/lib/graphql/mutations/scopeTemplates/UpdateTaskTemplate.graphql new file mode 100644 index 0000000..8faeef5 --- /dev/null +++ b/src/lib/graphql/mutations/scopeTemplates/UpdateTaskTemplate.graphql @@ -0,0 +1,12 @@ +mutation UpdateTaskTemplate($input: TaskTemplateUpdateInput!) { + updateTaskTemplate(input: $input) { + id + description + checklistDescription + frequency + isConditional + order + estimatedMinutes + areaTemplateId + } +} diff --git a/src/lib/graphql/mutations/scopes/CreateArea.graphql b/src/lib/graphql/mutations/scopes/CreateArea.graphql new file mode 100644 index 0000000..c88cfb5 --- /dev/null +++ b/src/lib/graphql/mutations/scopes/CreateArea.graphql @@ -0,0 +1,16 @@ +mutation CreateArea($input: AreaInput!) { + createArea(input: $input) { + id + name + order + scopeId + tasks { + id + description + checklistDescription + frequency + isConditional + order + } + } +} diff --git a/src/lib/graphql/mutations/scopes/CreateScope.graphql b/src/lib/graphql/mutations/scopes/CreateScope.graphql new file mode 100644 index 0000000..2d515c0 --- /dev/null +++ b/src/lib/graphql/mutations/scopes/CreateScope.graphql @@ -0,0 +1,23 @@ +mutation CreateScope($input: ScopeInput!) { + createScope(input: $input) { + id + accountId + accountAddressId + name + description + isActive + areas { + id + name + order + tasks { + id + description + checklistDescription + frequency + isConditional + order + } + } + } +} diff --git a/src/lib/graphql/mutations/scopes/CreateScopeFromTemplate.graphql b/src/lib/graphql/mutations/scopes/CreateScopeFromTemplate.graphql new file mode 100644 index 0000000..16780e1 --- /dev/null +++ b/src/lib/graphql/mutations/scopes/CreateScopeFromTemplate.graphql @@ -0,0 +1,23 @@ +mutation CreateScopeFromTemplate($input: CreateScopeFromTemplateInput!) { + createScopeFromTemplate(input: $input) { + id + accountId + accountAddressId + name + description + isActive + areas { + id + name + order + tasks { + id + description + checklistDescription + frequency + isConditional + order + } + } + } +} diff --git a/src/lib/graphql/mutations/scopes/CreateTask.graphql b/src/lib/graphql/mutations/scopes/CreateTask.graphql new file mode 100644 index 0000000..e47b8f6 --- /dev/null +++ b/src/lib/graphql/mutations/scopes/CreateTask.graphql @@ -0,0 +1,12 @@ +mutation CreateTask($input: TaskInput!) { + createTask(input: $input) { + id + areaId + description + checklistDescription + estimatedMinutes + frequency + isConditional + order + } +} diff --git a/src/lib/graphql/mutations/scopes/DeleteArea.graphql b/src/lib/graphql/mutations/scopes/DeleteArea.graphql new file mode 100644 index 0000000..1023f75 --- /dev/null +++ b/src/lib/graphql/mutations/scopes/DeleteArea.graphql @@ -0,0 +1,3 @@ +mutation DeleteArea($id: ID!) { + deleteArea(id: $id) +} diff --git a/src/lib/graphql/mutations/scopes/DeleteScope.graphql b/src/lib/graphql/mutations/scopes/DeleteScope.graphql new file mode 100644 index 0000000..ea7fe59 --- /dev/null +++ b/src/lib/graphql/mutations/scopes/DeleteScope.graphql @@ -0,0 +1,3 @@ +mutation DeleteScope($id: ID!) { + deleteScope(id: $id) +} diff --git a/src/lib/graphql/mutations/scopes/DeleteTask.graphql b/src/lib/graphql/mutations/scopes/DeleteTask.graphql new file mode 100644 index 0000000..36c3328 --- /dev/null +++ b/src/lib/graphql/mutations/scopes/DeleteTask.graphql @@ -0,0 +1,3 @@ +mutation DeleteTask($id: ID!) { + deleteTask(id: $id) +} diff --git a/src/lib/graphql/mutations/scopes/UpdateArea.graphql b/src/lib/graphql/mutations/scopes/UpdateArea.graphql new file mode 100644 index 0000000..8e1b284 --- /dev/null +++ b/src/lib/graphql/mutations/scopes/UpdateArea.graphql @@ -0,0 +1,16 @@ +mutation UpdateArea($input: AreaUpdateInput!) { + updateArea(input: $input) { + id + name + order + scopeId + tasks { + id + description + checklistDescription + frequency + isConditional + order + } + } +} diff --git a/src/lib/graphql/mutations/scopes/UpdateScope.graphql b/src/lib/graphql/mutations/scopes/UpdateScope.graphql new file mode 100644 index 0000000..54150a5 --- /dev/null +++ b/src/lib/graphql/mutations/scopes/UpdateScope.graphql @@ -0,0 +1,23 @@ +mutation UpdateScope($input: ScopeUpdateInput!) { + updateScope(input: $input) { + id + accountId + accountAddressId + name + description + isActive + areas { + id + name + order + tasks { + id + description + checklistDescription + frequency + isConditional + order + } + } + } +} diff --git a/src/lib/graphql/mutations/scopes/UpdateTask.graphql b/src/lib/graphql/mutations/scopes/UpdateTask.graphql new file mode 100644 index 0000000..4ed2d39 --- /dev/null +++ b/src/lib/graphql/mutations/scopes/UpdateTask.graphql @@ -0,0 +1,12 @@ +mutation UpdateTask($input: TaskUpdateInput!) { + updateTask(input: $input) { + id + areaId + description + checklistDescription + estimatedMinutes + frequency + isConditional + order + } +} diff --git a/src/lib/graphql/mutations/services/CreateService.graphql b/src/lib/graphql/mutations/services/CreateService.graphql new file mode 100644 index 0000000..1e32a7c --- /dev/null +++ b/src/lib/graphql/mutations/services/CreateService.graphql @@ -0,0 +1,13 @@ +mutation CreateService($input: ServiceInput!) { + createService(input: $input) { + id + accountId + accountAddressId + date + status + notes + teamMembers { + pk + } + } +} diff --git a/src/lib/graphql/mutations/services/DeleteService.graphql b/src/lib/graphql/mutations/services/DeleteService.graphql new file mode 100644 index 0000000..e3d5d77 --- /dev/null +++ b/src/lib/graphql/mutations/services/DeleteService.graphql @@ -0,0 +1,3 @@ +mutation DeleteService($id: ID!) { + deleteService(id: $id) +} diff --git a/src/lib/graphql/mutations/services/GenerateServicesByMonth.graphql b/src/lib/graphql/mutations/services/GenerateServicesByMonth.graphql new file mode 100644 index 0000000..bebc287 --- /dev/null +++ b/src/lib/graphql/mutations/services/GenerateServicesByMonth.graphql @@ -0,0 +1,9 @@ +mutation GenerateServicesByMonth($input: ServiceGenerationInput!) { + generateServicesByMonth(input: $input) { + id + accountAddressId + date + status + notes + } +} diff --git a/src/lib/graphql/mutations/services/UpdateService.graphql b/src/lib/graphql/mutations/services/UpdateService.graphql new file mode 100644 index 0000000..2f89139 --- /dev/null +++ b/src/lib/graphql/mutations/services/UpdateService.graphql @@ -0,0 +1,12 @@ +mutation UpdateService($input: ServiceUpdateInput!) { + updateService(input: $input) { + id + date + status + notes + calendarEventId + teamMembers { + pk + } + } +} diff --git a/src/lib/graphql/mutations/sessions/CloseProjectSession.graphql b/src/lib/graphql/mutations/sessions/CloseProjectSession.graphql new file mode 100644 index 0000000..d3ef3cc --- /dev/null +++ b/src/lib/graphql/mutations/sessions/CloseProjectSession.graphql @@ -0,0 +1,23 @@ +mutation CloseProjectSession($input: ProjectSessionCloseInput!) { + closeProjectSession(input: $input) { + id + projectId + accountId + accountAddressId + customerId + scopeId + start + end + isActive + durationSeconds + completedTasks { + id + taskId + projectId + completedAt + completedById + notes + accountAddressId + } + } +} diff --git a/src/lib/graphql/mutations/sessions/CloseServiceSession.graphql b/src/lib/graphql/mutations/sessions/CloseServiceSession.graphql new file mode 100644 index 0000000..c259a5d --- /dev/null +++ b/src/lib/graphql/mutations/sessions/CloseServiceSession.graphql @@ -0,0 +1,25 @@ +mutation CloseServiceSession($input: CloseServiceSessionInput!) { + closeServiceSession(input: $input) { + id + serviceId + accountId + accountAddressId + customerId + scopeId + start + end + isActive + durationSeconds + completedTasks { + id + taskId + serviceId + completedAt + completedById + notes + accountAddressId + month + year + } + } +} diff --git a/src/lib/graphql/mutations/sessions/OpenProjectSession.graphql b/src/lib/graphql/mutations/sessions/OpenProjectSession.graphql new file mode 100644 index 0000000..ebd90f2 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/OpenProjectSession.graphql @@ -0,0 +1,10 @@ +mutation OpenProjectSession($input: ProjectSessionStartInput!) { + openProjectSession(input: $input) { + id + projectId + scopeId + customerId + start + isActive + } +} diff --git a/src/lib/graphql/mutations/sessions/OpenServiceSession.graphql b/src/lib/graphql/mutations/sessions/OpenServiceSession.graphql new file mode 100644 index 0000000..44cb4f9 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/OpenServiceSession.graphql @@ -0,0 +1,10 @@ +mutation OpenServiceSession($input: OpenServiceSessionInput!) { + openServiceSession(input: $input) { + id + serviceId + scopeId + customerId + start + isActive + } +} diff --git a/src/lib/graphql/mutations/sessions/RevertProjectSession.graphql b/src/lib/graphql/mutations/sessions/RevertProjectSession.graphql new file mode 100644 index 0000000..6cd6736 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/RevertProjectSession.graphql @@ -0,0 +1,3 @@ +mutation RevertProjectSession($input: ProjectSessionRevertInput!) { + revertProjectSession(input: $input) +} diff --git a/src/lib/graphql/mutations/sessions/RevertServiceSession.graphql b/src/lib/graphql/mutations/sessions/RevertServiceSession.graphql new file mode 100644 index 0000000..049b9a0 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/RevertServiceSession.graphql @@ -0,0 +1,3 @@ +mutation RevertServiceSession($input: RevertServiceSessionInput!) { + revertServiceSession(input: $input) +} diff --git a/src/lib/graphql/mutations/sessions/media/RemoveProjectPhoto.graphql b/src/lib/graphql/mutations/sessions/media/RemoveProjectPhoto.graphql new file mode 100644 index 0000000..09feb53 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/media/RemoveProjectPhoto.graphql @@ -0,0 +1,3 @@ +mutation RemoveProjectPhoto($id: ID!) { + deleteProjectSessionImage(id: $id) +} diff --git a/src/lib/graphql/mutations/sessions/media/RemoveProjectVideo.graphql b/src/lib/graphql/mutations/sessions/media/RemoveProjectVideo.graphql new file mode 100644 index 0000000..cb27942 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/media/RemoveProjectVideo.graphql @@ -0,0 +1,3 @@ +mutation RemoveProjectVideo($id: ID!) { + deleteProjectSessionVideo(id: $id) +} diff --git a/src/lib/graphql/mutations/sessions/media/RemoveServicePhoto.graphql b/src/lib/graphql/mutations/sessions/media/RemoveServicePhoto.graphql new file mode 100644 index 0000000..fcb61d6 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/media/RemoveServicePhoto.graphql @@ -0,0 +1,3 @@ +mutation RemoveServicePhoto($id: ID!) { + deleteServiceSessionImage(id: $id) +} diff --git a/src/lib/graphql/mutations/sessions/media/RemoveServiceVideo.graphql b/src/lib/graphql/mutations/sessions/media/RemoveServiceVideo.graphql new file mode 100644 index 0000000..f2947fb --- /dev/null +++ b/src/lib/graphql/mutations/sessions/media/RemoveServiceVideo.graphql @@ -0,0 +1,3 @@ +mutation RemoveServiceVideo($id: ID!) { + deleteServiceSessionVideo(id: $id) +} diff --git a/src/lib/graphql/mutations/sessions/media/UpdateProjectPhoto.graphql b/src/lib/graphql/mutations/sessions/media/UpdateProjectPhoto.graphql new file mode 100644 index 0000000..9488514 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/media/UpdateProjectPhoto.graphql @@ -0,0 +1,8 @@ +mutation UpdateProjectPhoto($input: ProjectSessionImageUpdateInput!) { + updateProjectSessionImage(input: $input) { + id + title + notes + internal + } +} diff --git a/src/lib/graphql/mutations/sessions/media/UpdateProjectVideo.graphql b/src/lib/graphql/mutations/sessions/media/UpdateProjectVideo.graphql new file mode 100644 index 0000000..9b419f6 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/media/UpdateProjectVideo.graphql @@ -0,0 +1,8 @@ +mutation UpdateProjectVideo($input: ProjectSessionVideoUpdateInput!) { + updateProjectSessionVideo(input: $input) { + id + title + notes + internal + } +} diff --git a/src/lib/graphql/mutations/sessions/media/UpdateServicePhoto.graphql b/src/lib/graphql/mutations/sessions/media/UpdateServicePhoto.graphql new file mode 100644 index 0000000..7b624f3 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/media/UpdateServicePhoto.graphql @@ -0,0 +1,8 @@ +mutation UpdateServicePhoto($input: ServiceSessionImageUpdateInput!) { + updateServiceSessionImage(input: $input) { + id + title + notes + internal + } +} diff --git a/src/lib/graphql/mutations/sessions/media/UpdateServiceVideo.graphql b/src/lib/graphql/mutations/sessions/media/UpdateServiceVideo.graphql new file mode 100644 index 0000000..8886d77 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/media/UpdateServiceVideo.graphql @@ -0,0 +1,8 @@ +mutation UpdateServiceVideo($input: ServiceSessionVideoUpdateInput!) { + updateServiceSessionVideo(input: $input) { + id + title + notes + internal + } +} diff --git a/src/lib/graphql/mutations/sessions/notes/CreateProjectNote.graphql b/src/lib/graphql/mutations/sessions/notes/CreateProjectNote.graphql new file mode 100644 index 0000000..2bef26a --- /dev/null +++ b/src/lib/graphql/mutations/sessions/notes/CreateProjectNote.graphql @@ -0,0 +1,11 @@ +mutation CreateProjectNote($input: ProjectSessionNoteInput!) { + createProjectSessionNote(input: $input) { + id + sessionId + content + authorId + internal + createdAt + updatedAt + } +} diff --git a/src/lib/graphql/mutations/sessions/notes/CreateServiceNote.graphql b/src/lib/graphql/mutations/sessions/notes/CreateServiceNote.graphql new file mode 100644 index 0000000..a4c65f5 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/notes/CreateServiceNote.graphql @@ -0,0 +1,11 @@ +mutation CreateServiceNote($input: ServiceSessionNoteInput!) { + createServiceSessionNote(input: $input) { + id + sessionId + content + authorId + internal + createdAt + updatedAt + } +} diff --git a/src/lib/graphql/mutations/sessions/notes/RemoveProjectNote.graphql b/src/lib/graphql/mutations/sessions/notes/RemoveProjectNote.graphql new file mode 100644 index 0000000..d62b158 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/notes/RemoveProjectNote.graphql @@ -0,0 +1,3 @@ +mutation RemoveProjectNote($id: ID!) { + deleteProjectSessionNote(id: $id) +} diff --git a/src/lib/graphql/mutations/sessions/notes/RemoveServiceNote.graphql b/src/lib/graphql/mutations/sessions/notes/RemoveServiceNote.graphql new file mode 100644 index 0000000..b0c9bc4 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/notes/RemoveServiceNote.graphql @@ -0,0 +1,3 @@ +mutation RemoveServiceNote($id: ID!) { + deleteServiceSessionNote(id: $id) +} diff --git a/src/lib/graphql/mutations/sessions/notes/UpdateProjectNote.graphql b/src/lib/graphql/mutations/sessions/notes/UpdateProjectNote.graphql new file mode 100644 index 0000000..0556b9d --- /dev/null +++ b/src/lib/graphql/mutations/sessions/notes/UpdateProjectNote.graphql @@ -0,0 +1,11 @@ +mutation UpdateProjectNote($input: ProjectSessionNoteUpdateInput!) { + updateProjectSessionNote(input: $input) { + id + sessionId + content + authorId + internal + createdAt + updatedAt + } +} diff --git a/src/lib/graphql/mutations/sessions/notes/UpdateServiceNote.graphql b/src/lib/graphql/mutations/sessions/notes/UpdateServiceNote.graphql new file mode 100644 index 0000000..0adcd82 --- /dev/null +++ b/src/lib/graphql/mutations/sessions/notes/UpdateServiceNote.graphql @@ -0,0 +1,11 @@ +mutation UpdateServiceNote($input: ServiceSessionNoteUpdateInput!) { + updateServiceSessionNote(input: $input) { + id + sessionId + content + authorId + internal + createdAt + updatedAt + } +} diff --git a/src/lib/graphql/mutations/sessions/tasks/AddProjectTask.graphql b/src/lib/graphql/mutations/sessions/tasks/AddProjectTask.graphql new file mode 100644 index 0000000..2ddc64f --- /dev/null +++ b/src/lib/graphql/mutations/sessions/tasks/AddProjectTask.graphql @@ -0,0 +1,23 @@ +mutation AddProjectTask($projectId: ID!, $taskId: ID!, $notes: String) { + addProjectTaskCompletion(projectId: $projectId, taskId: $taskId, notes: $notes) { + id + projectId + accountId + accountAddressId + customerId + scopeId + start + end + isActive + durationSeconds + completedTasks { + id + taskId + projectId + completedAt + completedById + notes + accountAddressId + } + } +} diff --git a/src/lib/graphql/mutations/sessions/tasks/AddServiceTask.graphql b/src/lib/graphql/mutations/sessions/tasks/AddServiceTask.graphql new file mode 100644 index 0000000..c32782b --- /dev/null +++ b/src/lib/graphql/mutations/sessions/tasks/AddServiceTask.graphql @@ -0,0 +1,25 @@ +mutation AddServiceTask($serviceId: ID!, $taskId: ID!, $notes: String) { + addTaskCompletion(serviceId: $serviceId, taskId: $taskId, notes: $notes) { + id + serviceId + accountId + accountAddressId + customerId + scopeId + start + end + isActive + durationSeconds + completedTasks { + id + taskId + serviceId + completedAt + completedById + notes + accountAddressId + month + year + } + } +} diff --git a/src/lib/graphql/mutations/sessions/tasks/RemoveProjectTask.graphql b/src/lib/graphql/mutations/sessions/tasks/RemoveProjectTask.graphql new file mode 100644 index 0000000..f71ee3e --- /dev/null +++ b/src/lib/graphql/mutations/sessions/tasks/RemoveProjectTask.graphql @@ -0,0 +1,23 @@ +mutation RemoveProjectTask($projectId: ID!, $taskId: ID!) { + removeProjectTaskCompletion(projectId: $projectId, taskId: $taskId) { + id + projectId + accountId + accountAddressId + customerId + scopeId + start + end + isActive + durationSeconds + completedTasks { + id + taskId + projectId + completedAt + completedById + notes + accountAddressId + } + } +} diff --git a/src/lib/graphql/mutations/sessions/tasks/RemoveServiceTask.graphql b/src/lib/graphql/mutations/sessions/tasks/RemoveServiceTask.graphql new file mode 100644 index 0000000..b2dc8fa --- /dev/null +++ b/src/lib/graphql/mutations/sessions/tasks/RemoveServiceTask.graphql @@ -0,0 +1,25 @@ +mutation RemoveServiceTask($serviceId: ID!, $taskId: ID!) { + removeTaskCompletion(serviceId: $serviceId, taskId: $taskId) { + id + serviceId + accountId + accountAddressId + customerId + scopeId + start + end + isActive + durationSeconds + completedTasks { + id + taskId + serviceId + completedAt + completedById + notes + accountAddressId + month + year + } + } +} diff --git a/src/lib/graphql/queries/accounts/GetAccount.graphql b/src/lib/graphql/queries/accounts/GetAccount.graphql new file mode 100644 index 0000000..6a5b0bb --- /dev/null +++ b/src/lib/graphql/queries/accounts/GetAccount.graphql @@ -0,0 +1,97 @@ +query GetAccount($id: ID!) { + account(id: $id) { + id + customerId + name + startDate + endDate + status + isActive + revenues { + id + amount + startDate + endDate + waveServiceId + } + primaryAddress { + id + streetAddress + city + state + zipCode + isActive + isPrimary + notes + } + addresses { + id + streetAddress + name + city + state + zipCode + isActive + isPrimary + notes + schedules { + id + name + startDate + endDate + mondayService + tuesdayService + wednesdayService + thursdayService + fridayService + saturdayService + sundayService + weekendService + scheduleException + } + labors { + id + amount + startDate + endDate + } + scopes { + id + name + description + isActive + areas { + id + name + order + tasks { + id + description + checklistDescription + frequency + isConditional + order + } + } + } + services { + id + accountAddressId + status + date + notes + } + } + contacts { + id + firstName + lastName + fullName + email + phone + isActive + isPrimary + notes + } + } +} diff --git a/src/lib/graphql/queries/admin/AdminEvent.graphql b/src/lib/graphql/queries/admin/AdminEvent.graphql new file mode 100644 index 0000000..0b1108b --- /dev/null +++ b/src/lib/graphql/queries/admin/AdminEvent.graphql @@ -0,0 +1,13 @@ +query AdminEvent($id: ID!) { + event(id: $id) { + id + eventType + entityType + entityId + metadata + triggeredById + triggeredByType + createdAt + updatedAt + } +} diff --git a/src/lib/graphql/queries/admin/AdminEvents.graphql b/src/lib/graphql/queries/admin/AdminEvents.graphql new file mode 100644 index 0000000..583359a --- /dev/null +++ b/src/lib/graphql/queries/admin/AdminEvents.graphql @@ -0,0 +1,12 @@ +query AdminEvents($limit: Int, $offset: Int) { + events(limit: $limit, offset: $offset) { + id + eventType + entityType + entityId + metadata + triggeredById + triggeredByType + createdAt + } +} diff --git a/src/lib/graphql/queries/admin/AdminInvoices.graphql b/src/lib/graphql/queries/admin/AdminInvoices.graphql new file mode 100644 index 0000000..362a4bf --- /dev/null +++ b/src/lib/graphql/queries/admin/AdminInvoices.graphql @@ -0,0 +1,11 @@ +query AdminInvoices($filters: InvoiceFilter) { + invoices(filters: $filters) { + id + date + customerId + status + datePaid + paymentType + waveInvoiceId + } +} diff --git a/src/lib/graphql/queries/admin/AdminNotificationRule.graphql b/src/lib/graphql/queries/admin/AdminNotificationRule.graphql new file mode 100644 index 0000000..f772e8e --- /dev/null +++ b/src/lib/graphql/queries/admin/AdminNotificationRule.graphql @@ -0,0 +1,18 @@ +query AdminNotificationRule($id: ID!) { + notificationRule(id: $id) { + id + name + description + eventTypes + channels + targetRoles + targetTeamProfileIds + targetCustomerProfileIds + isActive + templateSubject + templateBody + conditions + createdAt + updatedAt + } +} diff --git a/src/lib/graphql/queries/admin/AdminNotificationRules.graphql b/src/lib/graphql/queries/admin/AdminNotificationRules.graphql new file mode 100644 index 0000000..8b7c25c --- /dev/null +++ b/src/lib/graphql/queries/admin/AdminNotificationRules.graphql @@ -0,0 +1,18 @@ +query AdminNotificationRules($isActive: Boolean) { + notificationRules(isActive: $isActive) { + id + name + description + eventTypes + channels + targetRoles + targetTeamProfileIds + targetCustomerProfileIds + isActive + templateSubject + templateBody + conditions + createdAt + updatedAt + } +} diff --git a/src/lib/graphql/queries/admin/AdminProfiles.graphql b/src/lib/graphql/queries/admin/AdminProfiles.graphql new file mode 100644 index 0000000..17dacb4 --- /dev/null +++ b/src/lib/graphql/queries/admin/AdminProfiles.graphql @@ -0,0 +1,27 @@ +query AdminProfiles { + teamProfiles { + id + firstName + lastName + fullName + email + phone + status + notes + role + } + customerProfiles { + id + firstName + lastName + fullName + email + phone + status + notes + customers { + id + name + } + } +} diff --git a/src/lib/graphql/queries/admin/AdminProjects.graphql b/src/lib/graphql/queries/admin/AdminProjects.graphql new file mode 100644 index 0000000..be2a3e5 --- /dev/null +++ b/src/lib/graphql/queries/admin/AdminProjects.graphql @@ -0,0 +1,22 @@ +query AdminProjects($filters: ProjectFilter) { + projects(filters: $filters) { + id + name + date + status + amount + labor + customerId + accountAddressId + city + state + streetAddress + zipCode + notes + calendarEventId + waveServiceId + teamMembers { + pk + } + } +} diff --git a/src/lib/graphql/queries/admin/AdminReports.graphql b/src/lib/graphql/queries/admin/AdminReports.graphql new file mode 100644 index 0000000..32270b9 --- /dev/null +++ b/src/lib/graphql/queries/admin/AdminReports.graphql @@ -0,0 +1,10 @@ +query AdminReports($filters: ReportFilter) { + reports(filters: $filters) { + id + date + teamMemberId + servicesLaborTotal + projectsLaborTotal + totalLaborValue + } +} diff --git a/src/lib/graphql/queries/admin/AdminServices.graphql b/src/lib/graphql/queries/admin/AdminServices.graphql new file mode 100644 index 0000000..d1a275d --- /dev/null +++ b/src/lib/graphql/queries/admin/AdminServices.graphql @@ -0,0 +1,14 @@ +query AdminServices($filters: ServiceFilter) { + services(filters: $filters) { + id + accountId + accountAddressId + date + status + notes + calendarEventId + teamMembers { + pk + } + } +} diff --git a/src/lib/graphql/queries/admin/GetInvoice.graphql b/src/lib/graphql/queries/admin/GetInvoice.graphql new file mode 100644 index 0000000..555b42f --- /dev/null +++ b/src/lib/graphql/queries/admin/GetInvoice.graphql @@ -0,0 +1,28 @@ +query GetInvoice($id: ID!) { + invoice(id: $id) { + id + date + customerId + status + datePaid + paymentType + waveInvoiceId + projects { + id + name + date + amount + customerId + accountAddressId + waveServiceId + } + revenues { + id + accountId + amount + startDate + endDate + waveServiceId + } + } +} diff --git a/src/lib/graphql/queries/admin/GetProject.graphql b/src/lib/graphql/queries/admin/GetProject.graphql new file mode 100644 index 0000000..f07c1c1 --- /dev/null +++ b/src/lib/graphql/queries/admin/GetProject.graphql @@ -0,0 +1,23 @@ +query GetProject($id: ID!) { + project(id: $id) { + id + name + date + status + amount + labor + customerId + accountAddressId + city + state + streetAddress + zipCode + notes + scopeId + calendarEventId + waveServiceId + teamMembers { + pk + } + } +} diff --git a/src/lib/graphql/queries/admin/GetReport.graphql b/src/lib/graphql/queries/admin/GetReport.graphql new file mode 100644 index 0000000..10e1cc2 --- /dev/null +++ b/src/lib/graphql/queries/admin/GetReport.graphql @@ -0,0 +1,44 @@ +query GetReport($id: ID!) { + report(id: $id) { + id + date + teamMemberId + services { + id + date + accountAddressId + } + projects { + id + name + date + accountAddressId + customerId + } + servicesLaborTotal + projectsLaborTotal + totalLaborValue + laborBreakdown { + teamMemberId + teamMemberName + services { + serviceId + totalLaborRate + teamMemberCount + isTeamMemberAssigned + laborShare + } + projects { + projectId + projectName + totalLaborAmount + teamMemberCount + isTeamMemberAssigned + laborShare + } + servicesTotal + projectsTotal + grandTotal + } + } +} diff --git a/src/lib/graphql/queries/admin/GetService.graphql b/src/lib/graphql/queries/admin/GetService.graphql new file mode 100644 index 0000000..ac2dcf2 --- /dev/null +++ b/src/lib/graphql/queries/admin/GetService.graphql @@ -0,0 +1,14 @@ +query GetService($id: ID!) { + service(id: $id) { + id + accountId + accountAddressId + date + status + notes + calendarEventId + teamMembers { + pk + } + } +} diff --git a/src/lib/graphql/queries/customer/CustomerAccounts.graphql b/src/lib/graphql/queries/customer/CustomerAccounts.graphql new file mode 100644 index 0000000..0e193d9 --- /dev/null +++ b/src/lib/graphql/queries/customer/CustomerAccounts.graphql @@ -0,0 +1,44 @@ +query CustomerAccounts($filters: AccountFilter) { + accounts(filters: $filters) { + id + name + customerId + primaryAddress { + id + name + streetAddress + city + state + zipCode + } + addresses { + id + name + streetAddress + city + state + zipCode + isPrimary + schedules { + id + name + mondayService + tuesdayService + wednesdayService + thursdayService + fridayService + saturdayService + sundayService + weekendService + } + } + contacts { + id + fullName + email + phone + isActive + isPrimary + } + } +} diff --git a/src/lib/graphql/queries/customer/CustomerInvoices.graphql b/src/lib/graphql/queries/customer/CustomerInvoices.graphql new file mode 100644 index 0000000..ec35552 --- /dev/null +++ b/src/lib/graphql/queries/customer/CustomerInvoices.graphql @@ -0,0 +1,10 @@ +query CustomerInvoices($filters: InvoiceFilter) { + invoices(filters: $filters) { + id + date + customerId + status + datePaid + paymentType + } +} diff --git a/src/lib/graphql/queries/customer/CustomerProjects.graphql b/src/lib/graphql/queries/customer/CustomerProjects.graphql new file mode 100644 index 0000000..7759ca6 --- /dev/null +++ b/src/lib/graphql/queries/customer/CustomerProjects.graphql @@ -0,0 +1,16 @@ +query CustomerProjects($filters: ProjectFilter) { + projects(filters: $filters) { + id + name + date + status + amount + customerId + accountAddressId + streetAddress + city + state + zipCode + notes + } +} diff --git a/src/lib/graphql/queries/customer/CustomerServices.graphql b/src/lib/graphql/queries/customer/CustomerServices.graphql new file mode 100644 index 0000000..362a59a --- /dev/null +++ b/src/lib/graphql/queries/customer/CustomerServices.graphql @@ -0,0 +1,10 @@ +query CustomerServices($filters: ServiceFilter) { + services(filters: $filters) { + id + accountId + accountAddressId + date + status + notes + } +} diff --git a/src/lib/graphql/queries/customer/GetCustomerInvoice.graphql b/src/lib/graphql/queries/customer/GetCustomerInvoice.graphql new file mode 100644 index 0000000..e01a6ae --- /dev/null +++ b/src/lib/graphql/queries/customer/GetCustomerInvoice.graphql @@ -0,0 +1,24 @@ +query GetCustomerInvoice($id: ID!) { + invoice(id: $id) { + id + date + customerId + status + datePaid + paymentType + projects { + id + name + date + amount + accountAddressId + } + revenues { + id + accountId + amount + startDate + endDate + } + } +} diff --git a/src/lib/graphql/queries/customer/TeamProfiles.graphql b/src/lib/graphql/queries/customer/TeamProfiles.graphql new file mode 100644 index 0000000..fb92997 --- /dev/null +++ b/src/lib/graphql/queries/customer/TeamProfiles.graphql @@ -0,0 +1,6 @@ +query TeamProfiles { + teamProfiles { + id + fullName + } +} diff --git a/src/lib/graphql/queries/customers/GetCustomer.graphql b/src/lib/graphql/queries/customers/GetCustomer.graphql new file mode 100644 index 0000000..5a59bd5 --- /dev/null +++ b/src/lib/graphql/queries/customers/GetCustomer.graphql @@ -0,0 +1,38 @@ +query GetCustomer($id: ID!) { + customer(id: $id) { + id + name + billingEmail + billingTerms + startDate + endDate + status + isActive + waveCustomerId + addresses { + id + streetAddress + city + state + zipCode + addressType + isActive + isPrimary + } + contacts { + id + firstName + lastName + fullName + email + phone + isActive + isPrimary + notes + } + accounts { + id + name + } + } +} diff --git a/src/lib/graphql/queries/dashboard/AdminDashboard.graphql b/src/lib/graphql/queries/dashboard/AdminDashboard.graphql new file mode 100644 index 0000000..87a2083 --- /dev/null +++ b/src/lib/graphql/queries/dashboard/AdminDashboard.graphql @@ -0,0 +1,91 @@ +query AdminDashboard($month: String!, $invoiceStatus: String) { + adminDashboard(month: $month, invoiceStatus: $invoiceStatus) { + services { + id + accountId + accountAddressId + date + status + notes + calendarEventId + teamMembers { + pk + } + } + projects { + id + name + date + status + amount + labor + customerId + accountAddressId + city + state + streetAddress + zipCode + notes + calendarEventId + waveServiceId + teamMembers { + pk + } + } + invoices { + id + date + customerId + status + datePaid + paymentType + waveInvoiceId + } + reports { + id + date + teamMemberId + servicesLaborTotal + projectsLaborTotal + totalLaborValue + } + serviceScopeTemplates { + id + name + description + isActive + areaTemplates { + id + name + order + taskTemplates { + id + description + checklistDescription + frequency + isConditional + order + estimatedMinutes + } + } + } + projectScopeTemplates { + id + name + description + isActive + categoryTemplates { + id + name + order + taskTemplates { + id + description + checklistDescription + order + estimatedMinutes + } + } + } + } +} diff --git a/src/lib/graphql/queries/dashboard/CustomerDashboard.graphql b/src/lib/graphql/queries/dashboard/CustomerDashboard.graphql new file mode 100644 index 0000000..ecb80eb --- /dev/null +++ b/src/lib/graphql/queries/dashboard/CustomerDashboard.graphql @@ -0,0 +1,42 @@ +query CustomerDashboard($customerId: ID!) { + customerDashboard(customerId: $customerId) { + services { + id + accountId + accountAddressId + date + status + notes + teamMembers { + pk + } + } + projects { + id + name + date + status + amount + customerId + accountAddressId + city + state + streetAddress + zipCode + notes + scopeId + teamMembers { + pk + } + } + invoices { + id + date + customerId + status + datePaid + paymentType + waveInvoiceId + } + } +} diff --git a/src/lib/graphql/queries/dashboard/TeamDashboard.graphql b/src/lib/graphql/queries/dashboard/TeamDashboard.graphql new file mode 100644 index 0000000..f3a3c7a --- /dev/null +++ b/src/lib/graphql/queries/dashboard/TeamDashboard.graphql @@ -0,0 +1,44 @@ +query TeamDashboard($teamProfileId: ID!, $month: String!) { + teamDashboard(teamProfileId: $teamProfileId, month: $month) { + services { + id + accountId + accountAddressId + date + status + notes + calendarEventId + teamMembers { + pk + } + } + projects { + id + name + date + status + amount + labor + customerId + accountAddressId + city + state + streetAddress + zipCode + notes + calendarEventId + scopeId + teamMembers { + pk + } + } + reports { + id + date + teamMemberId + servicesLaborTotal + projectsLaborTotal + totalLaborValue + } + } +} diff --git a/src/lib/graphql/queries/layout/Accounts.graphql b/src/lib/graphql/queries/layout/Accounts.graphql new file mode 100644 index 0000000..557a7ea --- /dev/null +++ b/src/lib/graphql/queries/layout/Accounts.graphql @@ -0,0 +1,74 @@ +query Accounts($filters: AccountFilter) { + accounts(filters: $filters) { + id + name + customerId + primaryAddress { + id + streetAddress + city + state + zipCode + isActive + } + addresses { + id + name + notes + streetAddress + city + state + zipCode + isActive + isPrimary + schedules { + id + name + startDate + endDate + mondayService + tuesdayService + wednesdayService + thursdayService + fridayService + saturdayService + sundayService + weekendService + scheduleException + } + scopes { + id + name + description + areas { + id + name + order + tasks { + id + description + checklistDescription + frequency + order + } + } + isActive + } + } + contacts { + id + fullName + email + isActive + isPrimary + notes + } + revenues { + id + amount + startDate + endDate + waveServiceId + } + } +} diff --git a/src/lib/graphql/queries/layout/AccountsBasic.graphql b/src/lib/graphql/queries/layout/AccountsBasic.graphql new file mode 100644 index 0000000..69fc3e4 --- /dev/null +++ b/src/lib/graphql/queries/layout/AccountsBasic.graphql @@ -0,0 +1,22 @@ +query AccountsBasic($filters: AccountFilter) { + accounts(filters: $filters) { + id + name + customerId + primaryAddress { + id + streetAddress + city + state + zipCode + } + addresses { + id + name + streetAddress + city + state + zipCode + } + } +} diff --git a/src/lib/graphql/queries/layout/Customers.graphql b/src/lib/graphql/queries/layout/Customers.graphql new file mode 100644 index 0000000..8874940 --- /dev/null +++ b/src/lib/graphql/queries/layout/Customers.graphql @@ -0,0 +1,12 @@ +query Customers { + customers { + id + name + status + accounts { + id + name + } + waveCustomerId + } +} diff --git a/src/lib/graphql/queries/layout/Me.graphql b/src/lib/graphql/queries/layout/Me.graphql new file mode 100644 index 0000000..5e8e6e9 --- /dev/null +++ b/src/lib/graphql/queries/layout/Me.graphql @@ -0,0 +1,24 @@ +query Me { + me { + __typename + ... on TeamProfileType { + id + oryKratosId + fullName + email + phone + role + } + ... on CustomerProfileType { + id + oryKratosId + fullName + email + phone + customers { + id + name + } + } + } +} diff --git a/src/lib/graphql/queries/layout/Team.graphql b/src/lib/graphql/queries/layout/Team.graphql new file mode 100644 index 0000000..64135f2 --- /dev/null +++ b/src/lib/graphql/queries/layout/Team.graphql @@ -0,0 +1,10 @@ +query Team { + teamProfiles { + id + role + fullName + lastName + email + status + } +} diff --git a/src/lib/graphql/queries/messages/GetConversation.graphql b/src/lib/graphql/queries/messages/GetConversation.graphql new file mode 100644 index 0000000..04b79ee --- /dev/null +++ b/src/lib/graphql/queries/messages/GetConversation.graphql @@ -0,0 +1,101 @@ +query GetConversation($id: ID!, $messageLimit: Int = 50, $messageOffset: Int = 0) { + conversation(id: $id) { + id + subject + conversationType + lastMessageAt + isArchived + metadata + createdAt + updatedAt + unreadCount + messageCount + canDelete + createdBy { + teamProfile { + id + fullName + } + customerProfile { + id + fullName + } + } + entity { + entityType + entityId + project { + id + name + date + customerId + accountAddressId + } + service { + id + date + accountAddressId + } + account { + id + name + } + customer { + id + name + } + } + participants { + id + unreadCount + isMuted + isArchived + lastReadAt + joinedAt + participant { + teamProfile { + id + fullName + } + customerProfile { + id + fullName + } + } + } + messages(limit: $messageLimit, offset: $messageOffset) { + id + body + isSystemMessage + attachments + metadata + createdAt + updatedAt + canDelete + sender { + teamProfile { + id + fullName + } + customerProfile { + id + fullName + } + } + replyTo { + id + body + sender { + teamProfile { + id + fullName + } + customerProfile { + id + fullName + } + } + } + } + } +} diff --git a/src/lib/graphql/queries/messages/GetConversationsByEntity.graphql b/src/lib/graphql/queries/messages/GetConversationsByEntity.graphql new file mode 100644 index 0000000..e54bae3 --- /dev/null +++ b/src/lib/graphql/queries/messages/GetConversationsByEntity.graphql @@ -0,0 +1,54 @@ +query GetConversationsByEntity( + $entityType: String! + $entityId: ID! + $first: Int + $after: String + $last: Int + $before: String +) { + getConversationsByEntity( + entityType: $entityType + entityId: $entityId + first: $first + after: $after + last: $last + before: $before + ) { + edges { + node { + id + subject + conversationType + lastMessageAt + isArchived + createdAt + unreadCount + participants { + id + participant { + teamProfile { + id + fullName + } + customerProfile { + id + fullName + } + } + } + messages(limit: 1, offset: 0) { + id + body + createdAt + } + } + cursor + } + pageInfo { + hasNextPage + endCursor + hasPreviousPage + startCursor + } + } +} diff --git a/src/lib/graphql/queries/messages/GetMyConversations.graphql b/src/lib/graphql/queries/messages/GetMyConversations.graphql new file mode 100644 index 0000000..345bcd7 --- /dev/null +++ b/src/lib/graphql/queries/messages/GetMyConversations.graphql @@ -0,0 +1,102 @@ +query GetMyConversations( + $includeArchived: Boolean = false + $first: Int + $after: String + $last: Int + $before: String +) { + getMyConversations( + includeArchived: $includeArchived + first: $first + after: $after + last: $last + before: $before + ) { + edges { + node { + id + subject + conversationType + lastMessageAt + isArchived + metadata + createdAt + updatedAt + unreadCount + canDelete + createdBy { + teamProfile { + id + fullName + } + customerProfile { + id + fullName + } + } + entity { + entityType + entityId + project { + id + name + date + customerId + accountAddressId + } + service { + id + date + accountAddressId + } + account { + id + name + } + customer { + id + name + } + } + participants { + id + unreadCount + isMuted + isArchived + participant { + teamProfile { + id + fullName + } + customerProfile { + id + fullName + } + } + } + messages(limit: 1, offset: 0) { + id + body + createdAt + sender { + teamProfile { + id + fullName + } + customerProfile { + id + fullName + } + } + } + } + cursor + } + pageInfo { + hasNextPage + endCursor + hasPreviousPage + startCursor + } + } +} diff --git a/src/lib/graphql/queries/messages/GetUnreadMessageCount.graphql b/src/lib/graphql/queries/messages/GetUnreadMessageCount.graphql new file mode 100644 index 0000000..28288a8 --- /dev/null +++ b/src/lib/graphql/queries/messages/GetUnreadMessageCount.graphql @@ -0,0 +1,3 @@ +query GetUnreadMessageCount { + unreadMessageCount +} diff --git a/src/lib/graphql/queries/notifications/GetMyNotifications.graphql b/src/lib/graphql/queries/notifications/GetMyNotifications.graphql new file mode 100644 index 0000000..4cfaa52 --- /dev/null +++ b/src/lib/graphql/queries/notifications/GetMyNotifications.graphql @@ -0,0 +1,29 @@ +query GetMyNotifications($unreadOnly: Boolean = false, $limit: Int = 50, $offset: Int = 0) { + myNotifications(unreadOnly: $unreadOnly, limit: $limit, offset: $offset) { + id + status + subject + body + actionUrl + readAt + metadata + createdAt + updatedAt + isRead + recipientId + event { + id + eventType + entityType + entityId + metadata + createdAt + triggeredById + triggeredByType + } + rule { + id + name + } + } +} diff --git a/src/lib/graphql/queries/notifications/GetNotification.graphql b/src/lib/graphql/queries/notifications/GetNotification.graphql new file mode 100644 index 0000000..81e8470 --- /dev/null +++ b/src/lib/graphql/queries/notifications/GetNotification.graphql @@ -0,0 +1,30 @@ +query GetNotification($id: ID!) { + notification(id: $id) { + id + status + subject + body + actionUrl + readAt + metadata + createdAt + updatedAt + isRead + recipientId + event { + id + eventType + entityType + entityId + metadata + createdAt + triggeredById + triggeredByType + } + rule { + id + name + description + } + } +} diff --git a/src/lib/graphql/queries/notifications/GetUnreadNotificationCount.graphql b/src/lib/graphql/queries/notifications/GetUnreadNotificationCount.graphql new file mode 100644 index 0000000..a10f968 --- /dev/null +++ b/src/lib/graphql/queries/notifications/GetUnreadNotificationCount.graphql @@ -0,0 +1,3 @@ +query GetUnreadNotificationCount { + myUnreadNotificationCount +} diff --git a/src/lib/graphql/queries/scopeTemplates/GetProjectScopeTemplates.graphql b/src/lib/graphql/queries/scopeTemplates/GetProjectScopeTemplates.graphql new file mode 100644 index 0000000..c9a9983 --- /dev/null +++ b/src/lib/graphql/queries/scopeTemplates/GetProjectScopeTemplates.graphql @@ -0,0 +1,20 @@ +query GetProjectScopeTemplates { + projectScopeTemplates { + id + name + description + isActive + categoryTemplates { + id + name + order + taskTemplates { + id + description + checklistDescription + order + estimatedMinutes + } + } + } +} diff --git a/src/lib/graphql/queries/scopeTemplates/GetScopeTemplates.graphql b/src/lib/graphql/queries/scopeTemplates/GetScopeTemplates.graphql new file mode 100644 index 0000000..bc0d8fe --- /dev/null +++ b/src/lib/graphql/queries/scopeTemplates/GetScopeTemplates.graphql @@ -0,0 +1,22 @@ +query GetScopeTemplates { + scopeTemplates { + id + name + description + isActive + areaTemplates { + id + name + order + taskTemplates { + id + description + checklistDescription + frequency + isConditional + order + estimatedMinutes + } + } + } +} diff --git a/src/lib/graphql/queries/scopes/ProjectScope.graphql b/src/lib/graphql/queries/scopes/ProjectScope.graphql new file mode 100644 index 0000000..77d2cee --- /dev/null +++ b/src/lib/graphql/queries/scopes/ProjectScope.graphql @@ -0,0 +1,23 @@ +query ProjectScope($id: ID!) { + projectScope(id: $id) { + id + projectId + name + description + isActive + projectAreas { + id + name + order + scopeId + projectTasks { + id + categoryId + description + checklistDescription + estimatedMinutes + order + } + } + } +} diff --git a/src/lib/graphql/queries/scopes/Scope.graphql b/src/lib/graphql/queries/scopes/Scope.graphql new file mode 100644 index 0000000..c0f28b2 --- /dev/null +++ b/src/lib/graphql/queries/scopes/Scope.graphql @@ -0,0 +1,26 @@ +query Scope($id: ID!) { + scope(id: $id) { + id + accountId + accountAddressId + name + description + isActive + areas { + id + name + order + scopeId + tasks { + id + areaId + description + checklistDescription + estimatedMinutes + frequency + isConditional + order + } + } + } +} diff --git a/src/lib/graphql/queries/scopes/ScopeByAddress.graphql b/src/lib/graphql/queries/scopes/ScopeByAddress.graphql new file mode 100644 index 0000000..1d177f6 --- /dev/null +++ b/src/lib/graphql/queries/scopes/ScopeByAddress.graphql @@ -0,0 +1,26 @@ +query ScopeByAddress($accountAddressId: UUID!) { + scopes(filters: { accountAddressId: $accountAddressId, isActive: true }) { + id + accountId + accountAddressId + name + description + isActive + areas { + id + name + order + scopeId + tasks { + id + areaId + description + checklistDescription + estimatedMinutes + frequency + isConditional + order + } + } + } +} diff --git a/src/lib/graphql/queries/sessions/ProjectSession.graphql b/src/lib/graphql/queries/sessions/ProjectSession.graphql new file mode 100644 index 0000000..19a154e --- /dev/null +++ b/src/lib/graphql/queries/sessions/ProjectSession.graphql @@ -0,0 +1,74 @@ +query ProjectSession($id: ID!) { + projectSession(id: $id) { + id + projectId + accountId + accountAddressId + customerId + scopeId + start + end + isActive + durationSeconds + completedTasks { + id + taskId + projectId + completedAt + completedById + notes + accountAddressId + } + photos { + id + projectSessionId + title + notes + createdAt + contentType + width + height + uploadedByTeamProfileId + internal + image { + url + name + } + thumbnail { + url + name + } + } + videos { + id + title + notes + internal + createdAt + contentType + width + height + durationSeconds + fileSizeBytes + uploadedByTeamProfileId + projectSessionId + video { + url + name + } + thumbnail { + url + name + } + } + notes { + id + sessionId + createdAt + updatedAt + content + authorId + internal + } + } +} diff --git a/src/lib/graphql/queries/sessions/ProjectSessionByProject.graphql b/src/lib/graphql/queries/sessions/ProjectSessionByProject.graphql new file mode 100644 index 0000000..f542d20 --- /dev/null +++ b/src/lib/graphql/queries/sessions/ProjectSessionByProject.graphql @@ -0,0 +1,32 @@ +query ProjectSessionByProject($projectId: UUID!) { + projectSessions(filters: { projectId: { exact: $projectId } }) { + id + projectId + accountId + accountAddressId + customerId + scopeId + start + end + isActive + durationSeconds + completedTasks { + id + taskId + projectId + completedAt + completedById + notes + accountAddressId + } + notes { + id + sessionId + createdAt + updatedAt + content + authorId + internal + } + } +} diff --git a/src/lib/graphql/queries/sessions/ProjectSessionPhotos.graphql b/src/lib/graphql/queries/sessions/ProjectSessionPhotos.graphql new file mode 100644 index 0000000..d62ba58 --- /dev/null +++ b/src/lib/graphql/queries/sessions/ProjectSessionPhotos.graphql @@ -0,0 +1,22 @@ +query ProjectSessionPhotos($sessionId: UUID!) { + projectSessionImages(filters: { projectSessionId: $sessionId }) { + id + projectSessionId + title + notes + createdAt + contentType + width + height + uploadedByTeamProfileId + internal + image { + url + name + } + thumbnail { + url + name + } + } +} diff --git a/src/lib/graphql/queries/sessions/ProjectSessionVideos.graphql b/src/lib/graphql/queries/sessions/ProjectSessionVideos.graphql new file mode 100644 index 0000000..9f4cc32 --- /dev/null +++ b/src/lib/graphql/queries/sessions/ProjectSessionVideos.graphql @@ -0,0 +1,24 @@ +query ProjectSessionVideos($sessionId: UUID!) { + projectSessionVideos(filters: { projectSessionId: $sessionId }) { + id + title + notes + internal + createdAt + contentType + width + height + durationSeconds + fileSizeBytes + uploadedByTeamProfileId + projectSessionId + video { + url + name + } + thumbnail { + url + name + } + } +} diff --git a/src/lib/graphql/queries/sessions/ProjectSessions.graphql b/src/lib/graphql/queries/sessions/ProjectSessions.graphql new file mode 100644 index 0000000..caea48d --- /dev/null +++ b/src/lib/graphql/queries/sessions/ProjectSessions.graphql @@ -0,0 +1,8 @@ +query ProjectSessions($filters: ProjectSessionFilter) { + projectSessions(filters: $filters) { + id + projectId + start + end + } +} diff --git a/src/lib/graphql/queries/sessions/ServiceSession.graphql b/src/lib/graphql/queries/sessions/ServiceSession.graphql new file mode 100644 index 0000000..68ff953 --- /dev/null +++ b/src/lib/graphql/queries/sessions/ServiceSession.graphql @@ -0,0 +1,76 @@ +query ServiceSession($id: ID!) { + serviceSession(id: $id) { + id + serviceId + accountId + accountAddressId + customerId + scopeId + start + end + isActive + durationSeconds + completedTasks { + id + taskId + serviceId + completedAt + completedById + notes + accountAddressId + month + year + } + photos { + id + serviceSessionId + title + notes + createdAt + contentType + width + height + uploadedByTeamProfileId + internal + image { + url + name + } + thumbnail { + url + name + } + } + videos { + id + title + notes + internal + createdAt + contentType + width + height + durationSeconds + fileSizeBytes + uploadedByTeamProfileId + serviceSessionId + video { + url + name + } + thumbnail { + url + name + } + } + notes { + id + sessionId + createdAt + updatedAt + content + authorId + internal + } + } +} diff --git a/src/lib/graphql/queries/sessions/ServiceSessionByService.graphql b/src/lib/graphql/queries/sessions/ServiceSessionByService.graphql new file mode 100644 index 0000000..dff937e --- /dev/null +++ b/src/lib/graphql/queries/sessions/ServiceSessionByService.graphql @@ -0,0 +1,34 @@ +query ServiceSessionByService($serviceId: UUID!) { + serviceSessions(filters: { serviceId: { exact: $serviceId } }) { + id + serviceId + accountId + accountAddressId + customerId + scopeId + start + end + isActive + durationSeconds + completedTasks { + id + taskId + serviceId + completedAt + completedById + notes + accountAddressId + month + year + } + notes { + id + sessionId + createdAt + updatedAt + content + authorId + internal + } + } +} diff --git a/src/lib/graphql/queries/sessions/ServiceSessionPhotos.graphql b/src/lib/graphql/queries/sessions/ServiceSessionPhotos.graphql new file mode 100644 index 0000000..a61a528 --- /dev/null +++ b/src/lib/graphql/queries/sessions/ServiceSessionPhotos.graphql @@ -0,0 +1,22 @@ +query ServiceSessionPhotos($sessionId: UUID!) { + serviceSessionImages(filters: { serviceSessionId: $sessionId }) { + id + serviceSessionId + title + notes + createdAt + contentType + width + height + uploadedByTeamProfileId + internal + image { + url + name + } + thumbnail { + url + name + } + } +} diff --git a/src/lib/graphql/queries/sessions/ServiceSessionVideos.graphql b/src/lib/graphql/queries/sessions/ServiceSessionVideos.graphql new file mode 100644 index 0000000..32892ed --- /dev/null +++ b/src/lib/graphql/queries/sessions/ServiceSessionVideos.graphql @@ -0,0 +1,24 @@ +query ServiceSessionVideos($sessionId: UUID!) { + serviceSessionVideos(filters: { serviceSessionId: $sessionId }) { + id + title + notes + internal + createdAt + contentType + width + height + durationSeconds + fileSizeBytes + uploadedByTeamProfileId + serviceSessionId + video { + url + name + } + thumbnail { + url + name + } + } +} diff --git a/src/lib/graphql/queries/sessions/ServiceSessions.graphql b/src/lib/graphql/queries/sessions/ServiceSessions.graphql new file mode 100644 index 0000000..1ba2171 --- /dev/null +++ b/src/lib/graphql/queries/sessions/ServiceSessions.graphql @@ -0,0 +1,8 @@ +query ServiceSessions($filters: ServiceSessionFilter) { + serviceSessions(filters: $filters) { + id + serviceId + start + end + } +} diff --git a/src/lib/graphql/queries/team/GetTeamReport.graphql b/src/lib/graphql/queries/team/GetTeamReport.graphql new file mode 100644 index 0000000..ad487d9 --- /dev/null +++ b/src/lib/graphql/queries/team/GetTeamReport.graphql @@ -0,0 +1,44 @@ +query GetTeamReport($id: ID!) { + report(id: $id) { + id + date + teamMemberId + services { + id + date + accountAddressId + } + projects { + id + name + date + accountAddressId + customerId + } + servicesLaborTotal + projectsLaborTotal + totalLaborValue + laborBreakdown { + teamMemberId + teamMemberName + services { + serviceId + totalLaborRate + teamMemberCount + isTeamMemberAssigned + laborShare + } + projects { + projectId + projectName + totalLaborAmount + teamMemberCount + isTeamMemberAssigned + laborShare + } + servicesTotal + projectsTotal + grandTotal + } + } +} diff --git a/src/lib/graphql/queries/team/Projects.graphql b/src/lib/graphql/queries/team/Projects.graphql new file mode 100644 index 0000000..2ba8f51 --- /dev/null +++ b/src/lib/graphql/queries/team/Projects.graphql @@ -0,0 +1,46 @@ +query ProjectsForTeam( + $teamProfileId: ID! + $first: Int + $after: String + $last: Int + $before: String + $filters: ProjectFilter + $ordering: DateOrdering +) { + getProjectsByTeamMember( + teamProfileId: $teamProfileId + first: $first + after: $after + last: $last + before: $before + filters: $filters + ordering: $ordering + ) { + edges { + node { + id + name + date + status + accountAddressId + customerId + scopeId + city + state + zipCode + streetAddress + notes + teamMembers { + pk + } + } + cursor + } + pageInfo { + hasNextPage + endCursor + hasPreviousPage + startCursor + } + } +} diff --git a/src/lib/graphql/queries/team/Reports.graphql b/src/lib/graphql/queries/team/Reports.graphql new file mode 100644 index 0000000..dc1faa5 --- /dev/null +++ b/src/lib/graphql/queries/team/Reports.graphql @@ -0,0 +1,10 @@ +query ReportsForTeam($filters: ReportFilter) { + reports(filters: $filters) { + id + date + teamMemberId + projectsLaborTotal + servicesLaborTotal + totalLaborValue + } +} diff --git a/src/lib/graphql/queries/team/Services.graphql b/src/lib/graphql/queries/team/Services.graphql new file mode 100644 index 0000000..73388b6 --- /dev/null +++ b/src/lib/graphql/queries/team/Services.graphql @@ -0,0 +1,40 @@ +query ServicesForTeam( + $teamProfileId: ID! + $first: Int + $after: String + $last: Int + $before: String + $filters: ServiceFilter + $ordering: DateOrdering +) { + getServicesByTeamMember( + teamProfileId: $teamProfileId + first: $first + after: $after + last: $last + before: $before + filters: $filters + ordering: $ordering + ) { + edges { + node { + id + accountId + accountAddressId + date + status + notes + teamMembers { + pk + } + } + cursor + } + pageInfo { + hasNextPage + endCursor + hasPreviousPage + startCursor + } + } +} diff --git a/src/lib/graphql/wave/.graphqlrc.yaml b/src/lib/graphql/wave/.graphqlrc.yaml new file mode 100644 index 0000000..7f54965 --- /dev/null +++ b/src/lib/graphql/wave/.graphqlrc.yaml @@ -0,0 +1,2 @@ +schema: ./schema.graphql +documents: ./**/*.gql diff --git a/src/lib/graphql/wave/client.ts b/src/lib/graphql/wave/client.ts new file mode 100644 index 0000000..4e44033 --- /dev/null +++ b/src/lib/graphql/wave/client.ts @@ -0,0 +1,17 @@ +import { createClient, cacheExchange, fetchExchange } from '@urql/svelte'; + +/** + * Create a Wave client for server-side use with a provided access token. + * Use this in +page.server.ts or +server.ts files with WAVE_ACCESS_TOKEN from private env. + */ +export function createWaveServerClient(accessToken: string) { + return createClient({ + url: 'https://gql.waveapps.com/graphql/public', + exchanges: [cacheExchange, fetchExchange], + fetchOptions: () => ({ + headers: { + Authorization: `Bearer ${accessToken}` + } + }) + }); +} diff --git a/src/lib/graphql/wave/generated.ts b/src/lib/graphql/wave/generated.ts new file mode 100644 index 0000000..fabb3ad --- /dev/null +++ b/src/lib/graphql/wave/generated.ts @@ -0,0 +1,4659 @@ +import gql from 'graphql-tag'; +export type Maybe = T | null; +export type InputMaybe = Maybe; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type MakeEmpty = { [_ in K]?: never }; +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +export type Omit = Pick>; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } + Date: { input: any; output: any; } + DateTime: { input: any; output: any; } + Decimal: { input: any; output: any; } + HexColorCode: { input: any; output: any; } + JSON: { input: any; output: any; } + URL: { input: any; output: any; } +}; + +/** A unique record for each type of asset, liability, equity, income and expense. Used as part of a Chart of Accounts. */ +export type Account = BusinessNode & Node & { + __typename?: 'Account'; + /** The balance of the account as of the current date. */ + balance?: Maybe; + /** The balance of the account as of the current date in the business currency. */ + balanceInBusinessCurrency?: Maybe; + /** Business that the account belongs to. */ + business: Business; + /** The classic primary key used internally at Wave. */ + classicId?: Maybe; + /** Currency of the account. */ + currency: Currency; + /** User defined description for the account. */ + description?: Maybe; + /** User defined id for the account. */ + displayId?: Maybe; + /** Unique identifier for the account. */ + id: Scalars['ID']['output']; + /** Indicates whether the account is hidden from view by default. */ + isArchived: Scalars['Boolean']['output']; + /** Name of the account. */ + name: Scalars['String']['output']; + /** Credit or Debit. */ + normalBalanceType: AccountNormalBalanceType; + /** + * Numerically increasing version, each representing a revision of account data. + * As soon as something modifies an account, its sequence is incremented. + */ + sequence: Scalars['Int']['output']; + /** The account subtype classification based on type. */ + subtype: AccountSubtype; + /** Account type. */ + type: AccountType; +}; + +/** Input to the `accountArchive` mutation. */ +export type AccountArchiveInput = { + /** The unique identifier for the account. */ + id: Scalars['ID']['input']; +}; + +/** Output of the accountArchive mutation. */ +export type AccountArchiveOutput = { + __typename?: 'AccountArchiveOutput'; + /** Indicates whether the account was successfully archived. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; +}; + +/** Account connection. */ +export type AccountConnection = { + __typename?: 'AccountConnection'; + /** List of accounts from the Chart of Accounts. */ + edges: Array; + /** Information about pagination. */ + pageInfo: OffsetPageInfo; +}; + +/** Input to the `accountCreate` mutation. */ +export type AccountCreateInput = { + /** The unique identifier for the business. */ + businessId: Scalars['ID']['input']; + /** Currency of the account. Will default to business's currency. */ + currency?: InputMaybe; + /** User defined description for the account. */ + description?: InputMaybe; + /** User defined id for the account. */ + displayId?: InputMaybe; + /** Name of the account. */ + name: Scalars['String']['input']; + /** The account subtype classification. */ + subtype: AccountSubtypeValue; +}; + +/** Output of the `accountCreate` mutation. */ +export type AccountCreateOutput = { + __typename?: 'AccountCreateOutput'; + /** Account that was created. */ + account?: Maybe; + /** Indicates whether the account was successfully created. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; +}; + +/** Account edge. */ +export type AccountEdge = { + __typename?: 'AccountEdge'; + /** An account from the Chart of Accounts. */ + node?: Maybe; +}; + +/** Account balance type. */ +export enum AccountNormalBalanceType { + /** Credit */ + Credit = 'CREDIT', + /** Debit */ + Debit = 'DEBIT' +} + +/** Input to the `accountPatch` mutation. */ +export type AccountPatchInput = { + /** User defined description for the account. Use null to unset the current value. */ + description?: InputMaybe; + /** User defined id for the account. Use null to unset the current value. */ + displayId?: InputMaybe; + /** The unique identifier for the account. */ + id: Scalars['ID']['input']; + /** Name of the account. */ + name?: InputMaybe; + /** The most recent reversion you are aware of. As soon as something modifies an account, its sequence is incremented. */ + sequence: Scalars['Int']['input']; +}; + +/** Output of the accountPatch mutation. */ +export type AccountPatchOutput = { + __typename?: 'AccountPatchOutput'; + /** Account that was patched. */ + account?: Maybe; + /** Indicates whether the account was successfully patched. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; +}; + +/** Account subtype. */ +export type AccountSubtype = { + __typename?: 'AccountSubtype'; + /** Indicates if accounts of this subtype can be archived. */ + archivable: Scalars['Boolean']['output']; + /** Account subtype description. */ + description?: Maybe; + /** Account subtype name. */ + name: Scalars['String']['output']; + /** Indicates if accounts of this subtype is system created accounts. */ + systemCreated: Scalars['Boolean']['output']; + /** Account type for the subtype. */ + type: AccountType; + /** Account subtype value. */ + value: AccountSubtypeValue; +}; + +/** Subtypes of accounts, as used in the Chart of Accounts. */ +export enum AccountSubtypeValue { + /** Cash & Bank */ + CashAndBank = 'CASH_AND_BANK', + /** Cost of Goods Sold */ + CostOfGoodsSold = 'COST_OF_GOODS_SOLD', + /** Credit Card */ + CreditCard = 'CREDIT_CARD', + /** Customer Prepayments and Customer Credits */ + CustomerPrepaymentsAndCredits = 'CUSTOMER_PREPAYMENTS_AND_CREDITS', + /** Depreciation and Amortization */ + DepreciationAndAmortization = 'DEPRECIATION_AND_AMORTIZATION', + /** Discount */ + Discounts = 'DISCOUNTS', + /** Due For Payroll */ + DueForPayroll = 'DUE_FOR_PAYROLL', + /** Due to You and Other Business Owners */ + DueToYouAndOtherOwners = 'DUE_TO_YOU_AND_OTHER_OWNERS', + /** Expense */ + Expense = 'EXPENSE', + /** Gain on Foreign Exchange */ + GainOnForeignExchange = 'GAIN_ON_FOREIGN_EXCHANGE', + /** Income */ + Income = 'INCOME', + /** Inventory */ + Inventory = 'INVENTORY', + /** Loan and Line of Credit */ + Loans = 'LOANS', + /** Loss on Foreign Exchange */ + LossOnForeignExchange = 'LOSS_ON_FOREIGN_EXCHANGE', + /** Money in Transit */ + MoneyInTransit = 'MONEY_IN_TRANSIT', + /** Business Owner Contribution */ + NonRetainedEarnings = 'NON_RETAINED_EARNINGS', + /** Other Short-Term Asset */ + OtherCurrentAssets = 'OTHER_CURRENT_ASSETS', + /** Other Short-Term Liability */ + OtherCurrentLiability = 'OTHER_CURRENT_LIABILITY', + /** Other Income */ + OtherIncome = 'OTHER_INCOME', + /** Other Long-Term Asset */ + OtherLongTermAssets = 'OTHER_LONG_TERM_ASSETS', + /** Other Long-Term Liability */ + OtherLongTermLiability = 'OTHER_LONG_TERM_LIABILITY', + /** Payable */ + Payable = 'PAYABLE', + /** System Payable Bill */ + PayableBills = 'PAYABLE_BILLS', + /** System Payable Non-Bill */ + PayableOther = 'PAYABLE_OTHER', + /** Payment Processing Fee */ + PaymentProcessingFees = 'PAYMENT_PROCESSING_FEES', + /** Payroll Expense */ + PayrollExpenses = 'PAYROLL_EXPENSES', + /** Property, Plant, Equipment */ + PropertyPlantEquipment = 'PROPERTY_PLANT_EQUIPMENT', + /** Receivable */ + Receivable = 'RECEIVABLE', + /** System Receivable Invoice */ + ReceivableInvoices = 'RECEIVABLE_INVOICES', + /** System Receivable Non-Invoice */ + ReceivableOther = 'RECEIVABLE_OTHER', + /** Retained Earnings: Profit and Business Owner Drawing */ + RetainedEarnings = 'RETAINED_EARNINGS', + /** Sales Tax on Sales and Purchases */ + SalesTax = 'SALES_TAX', + /** System Customer Credits */ + SystemCustomerCredits = 'SYSTEM_CUSTOMER_CREDITS', + /** Transfers */ + Transfers = 'TRANSFERS', + /** Uncategorized Expense */ + UncategorizedExpense = 'UNCATEGORIZED_EXPENSE', + /** Uncategorized Income */ + UncategorizedIncome = 'UNCATEGORIZED_INCOME', + /** Unknown Account */ + UnknownAccount = 'UNKNOWN_ACCOUNT', + /** Vendor Prepayments and Vendor Credits */ + VendorPrepaymentsAndCredits = 'VENDOR_PREPAYMENTS_AND_CREDITS' +} + +/** Account type. */ +export type AccountType = { + __typename?: 'AccountType'; + /** Account type name. */ + name: Scalars['String']['output']; + /** Normal balance type of the account type */ + normalBalanceType: AccountNormalBalanceType; + /** Account type value. */ + value: AccountTypeValue; +}; + +/** Types of accounts, as used in the Chart of Accounts. */ +export enum AccountTypeValue { + /** Represents the different types of economic resources owned or controlled by an entity. */ + Asset = 'ASSET', + /** Represents the residual equity of an entity. */ + Equity = 'EQUITY', + /** Represents the business's expenditures. */ + Expense = 'EXPENSE', + /** Represents the business's earnings. */ + Income = 'INCOME', + /** Represents the different types of economic obligations of an entity. */ + Liability = 'LIABILITY' +} + +/** An address. */ +export type Address = { + __typename?: 'Address'; + /** Address line 1 (Street address/PO Box/Company name). */ + addressLine1?: Maybe; + /** Address line 2 (Apartment/Suite/Unit/Building). */ + addressLine2?: Maybe; + /** City/District/Suburb/Town/Village. */ + city?: Maybe; + /** Country. */ + country?: Maybe; + /** Zip/Postal Code. */ + postalCode?: Maybe; + /** State/County/Province/Region. */ + province?: Maybe; +}; + +/** An address. */ +export type AddressInput = { + /** Address line 1 (Street address/PO Box/Company name). */ + addressLine1?: InputMaybe; + /** Address line 2 (Apartment/Suite/Unit/Building). */ + addressLine2?: InputMaybe; + /** City/District/Suburb/Town/Village. */ + city?: InputMaybe; + /** Country Code. */ + countryCode?: InputMaybe; + /** Zip/Postal Code. */ + postalCode?: InputMaybe; + /** State/County/Province/Region Code ([ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2)). */ + provinceCode?: InputMaybe; +}; + +/** Balance type that expresses how to change an account. */ +export enum BalanceType { + /** Credit. */ + Credit = 'CREDIT', + /** Debit. */ + Debit = 'DEBIT', + /** Decrease using the inverse of the account's normal balance type. For contra accounts whose subtype is `DISCOUNTS` or `DEPRECIATION_AND_AMORTIZATION`, apply the amount in the account's normal balance type. */ + Decrease = 'DECREASE', + /** Increase using the account's normal balance type. For contra accounts whose subtype is `DISCOUNTS` or `DEPRECIATION_AND_AMORTIZATION`, apply the amount in the inverse of the account's normal balance type. */ + Increase = 'INCREASE' +} + +/** An organization and legal entity made up of an association of people. */ +export type Business = Node & { + __typename?: 'Business'; + /** Get an account of the business. */ + account?: Maybe; + /** Chart of Accounts for the business. */ + accounts?: Maybe; + /** The address of the business. */ + address?: Maybe
; + /** When the business was created. */ + createdAt: Scalars['DateTime']['output']; + /** The currency of the business. */ + currency: Currency; + /** Get a customer of the business. */ + customer?: Maybe; + /** List of customers for the business. */ + customers?: Maybe; + /** Indicates whether Wave email sending features, such as sending invoices, are enabled for this business */ + emailSendEnabled: Scalars['Boolean']['output']; + /** The fax number of the business. */ + fax?: Maybe; + /** The unique identifier for the business. */ + id: Scalars['ID']['output']; + /** Get an invoice of the business. */ + invoice?: Maybe; + /** Invoice and estimate settings for the business. */ + invoiceEstimateSettings: InvoiceEstimateSettings; + /** List of invoices for the business. */ + invoices?: Maybe; + /** Is the business hidden from view by default. */ + isArchived: Scalars['Boolean']['output']; + /** + * Does business use classic accounting system. + * @deprecated Classic Accounting is deprecated + */ + isClassicAccounting: Scalars['Boolean']['output']; + /** + * Does business use classic invoicing system. + * @deprecated Classic Invoicing is deprecated + */ + isClassicInvoicing: Scalars['Boolean']['output']; + /** Is the business a personal one with limited functionality compared to regular businesses. */ + isPersonal: Scalars['Boolean']['output']; + /** The mobile/cell number of the business. */ + mobile?: Maybe; + /** When the business was last modified. */ + modifiedAt: Scalars['DateTime']['output']; + /** The name of the business. */ + name: Scalars['String']['output']; + /** The organization type of the business. */ + organizationalType?: Maybe; + /** The phone number of the business. */ + phone?: Maybe; + /** Get a product (or service) of the business. */ + product?: Maybe; + /** List of products (and services) for the business. */ + products?: Maybe; + /** Get a sales tax of the business. */ + salesTax?: Maybe; + /** List of sales taxes for the business. */ + salesTaxes?: Maybe; + /** The subtype of the business. */ + subtype?: Maybe; + /** The timezone of the business. */ + timezone?: Maybe; + /** The toll free number of the business. */ + tollFree?: Maybe; + /** The type of the business. */ + type?: Maybe; + /** Get a vendor of the business. */ + vendor?: Maybe; + /** List of vendors for the business. */ + vendors?: Maybe; + /** The website of the business. */ + website?: Maybe; +}; + + +/** An organization and legal entity made up of an association of people. */ +export type BusinessAccountArgs = { + id: Scalars['ID']['input']; +}; + + +/** An organization and legal entity made up of an association of people. */ +export type BusinessAccountsArgs = { + excludedSubtypes?: InputMaybe>; + isArchived?: InputMaybe; + page?: InputMaybe; + pageSize?: InputMaybe; + subtypes?: InputMaybe>; + types?: InputMaybe>; +}; + + +/** An organization and legal entity made up of an association of people. */ +export type BusinessCustomerArgs = { + id: Scalars['ID']['input']; +}; + + +/** An organization and legal entity made up of an association of people. */ +export type BusinessCustomersArgs = { + email?: InputMaybe; + modifiedAtAfter?: InputMaybe; + modifiedAtBefore?: InputMaybe; + page?: InputMaybe; + pageSize?: InputMaybe; + sort?: Array; +}; + + +/** An organization and legal entity made up of an association of people. */ +export type BusinessInvoiceArgs = { + id: Scalars['ID']['input']; +}; + + +/** An organization and legal entity made up of an association of people. */ +export type BusinessInvoicesArgs = { + amountDue?: InputMaybe; + currency?: InputMaybe; + customerId?: InputMaybe; + invoiceDateEnd?: InputMaybe; + invoiceDateStart?: InputMaybe; + invoiceNumber?: InputMaybe; + modifiedAtAfter?: InputMaybe; + modifiedAtBefore?: InputMaybe; + page?: InputMaybe; + pageSize?: InputMaybe; + sort?: Array; + sourceId?: InputMaybe; + status?: InputMaybe; +}; + + +/** An organization and legal entity made up of an association of people. */ +export type BusinessProductArgs = { + id: Scalars['ID']['input']; +}; + + +/** An organization and legal entity made up of an association of people. */ +export type BusinessProductsArgs = { + isArchived?: InputMaybe; + isBought?: InputMaybe; + isSold?: InputMaybe; + modifiedAtAfter?: InputMaybe; + modifiedAtBefore?: InputMaybe; + page?: InputMaybe; + pageSize?: InputMaybe; + sort?: Array; +}; + + +/** An organization and legal entity made up of an association of people. */ +export type BusinessSalesTaxArgs = { + id: Scalars['ID']['input']; +}; + + +/** An organization and legal entity made up of an association of people. */ +export type BusinessSalesTaxesArgs = { + isArchived?: InputMaybe; + modifiedAtAfter?: InputMaybe; + modifiedAtBefore?: InputMaybe; + page?: InputMaybe; + pageSize?: InputMaybe; +}; + + +/** An organization and legal entity made up of an association of people. */ +export type BusinessVendorArgs = { + id: Scalars['ID']['input']; +}; + + +/** An organization and legal entity made up of an association of people. */ +export type BusinessVendorsArgs = { + email?: InputMaybe; + modifiedAtAfter?: InputMaybe; + modifiedAtBefore?: InputMaybe; + page?: InputMaybe; + pageSize?: InputMaybe; +}; + +/** Business connection. */ +export type BusinessConnection = { + __typename?: 'BusinessConnection'; + /** List of businesses. */ + edges: Array; + /** Information about pagination. */ + pageInfo: OffsetPageInfo; +}; + +/** Business edge. */ +export type BusinessEdge = { + __typename?: 'BusinessEdge'; + /** A business. */ + node?: Maybe; +}; + +/** An object belonging to a `Business` with an `ID`. */ +export type BusinessNode = { + /** Business that the node belongs to. */ + business: Business; + /** ID of the object. */ + id: Scalars['ID']['output']; +}; + +/** Granular area of focus of a business. */ +export type BusinessSubtype = { + __typename?: 'BusinessSubtype'; + /** The description of the business subtype in human-friendly form. */ + name: Scalars['String']['output']; + /** The enum value of the business subtype. */ + value: BusinessSubtypeValue; +}; + +/** Granular area of focus of a business. */ +export enum BusinessSubtypeValue { + /** Advertising, Public Relations */ + AdvertisingPublicRelations = 'ADVERTISING_PUBLIC_RELATIONS', + /** Agriculture, Ranching and Farming */ + AgricultureRanchingFarming = 'AGRICULTURE_RANCHING_FARMING', + /** Actor */ + ArtistsPhotographersCreativeActor = 'ARTISTS_PHOTOGRAPHERS_CREATIVE__ACTOR', + /** Audio/Visual Production */ + ArtistsPhotographersCreativeAudioVisualProduction = 'ARTISTS_PHOTOGRAPHERS_CREATIVE__AUDIO_VISUAL_PRODUCTION', + /** Craftsperson */ + ArtistsPhotographersCreativeCraftsperson = 'ARTISTS_PHOTOGRAPHERS_CREATIVE__CRAFTSPERSON', + /** Dancer, Choreographer */ + ArtistsPhotographersCreativeDancerChoreog = 'ARTISTS_PHOTOGRAPHERS_CREATIVE__DANCER_CHOREOG', + /** Musician */ + ArtistsPhotographersCreativeMusician = 'ARTISTS_PHOTOGRAPHERS_CREATIVE__MUSICIAN', + /** Other Creative */ + ArtistsPhotographersCreativeOther = 'ARTISTS_PHOTOGRAPHERS_CREATIVE__OTHER', + /** Performing Arts (acting, music, dance) */ + ArtistsPhotographersCreativePerformingArtsActingMusicDance = 'ARTISTS_PHOTOGRAPHERS_CREATIVE__PERFORMING_ARTS_ACTING_MUSIC_DANCE', + /** Photographer */ + ArtistsPhotographersCreativePhotographer = 'ARTISTS_PHOTOGRAPHERS_CREATIVE__PHOTOGRAPHER', + /** Visual Artist */ + ArtistsPhotographersCreativeVisualArtist = 'ARTISTS_PHOTOGRAPHERS_CREATIVE__VISUAL_ARTIST', + /** Automotive Repair & Sales */ + AutomotiveSalesAndRepair = 'AUTOMOTIVE_SALES_AND_REPAIR', + /** Church, Religious Organization */ + ChurchReligiousOrganization = 'CHURCH_RELIGIOUS_ORGANIZATION', + /** Contractor */ + ConstructionHomeImprovementContractor = 'CONSTRUCTION_HOME_IMPROVEMENT__CONTRACTOR', + /** Engineer */ + ConstructionHomeImprovementEngineer = 'CONSTRUCTION_HOME_IMPROVEMENT__ENGINEER', + /** Home Inspector */ + ConstructionHomeImprovementHomeInspector = 'CONSTRUCTION_HOME_IMPROVEMENT__HOME_INSPECTOR', + /** Trade */ + ConstructionHomeImprovementOtherTrades = 'CONSTRUCTION_HOME_IMPROVEMENT__OTHER_TRADES', + /** Accountant, Bookkeeper */ + ConsultantsProfessionalsAccountantsBookkeepers = 'CONSULTANTS_PROFESSIONALS__ACCOUNTANTS_BOOKKEEPERS', + /** Communications, Marketing, PR */ + ConsultantsProfessionalsCommunications = 'CONSULTANTS_PROFESSIONALS__COMMUNICATIONS', + /** Executive Coach */ + ConsultantsProfessionalsExecutiveCoach = 'CONSULTANTS_PROFESSIONALS__EXECUTIVE_COACH', + /** HR, Recruitment, Staffing */ + ConsultantsProfessionalsHrRecruitmentStaffing = 'CONSULTANTS_PROFESSIONALS__HR_RECRUITMENT_STAFFING', + /** IT, Technical */ + ConsultantsProfessionalsItTechnical = 'CONSULTANTS_PROFESSIONALS__IT_TECHNICAL', + /** Other Consultant */ + ConsultantsProfessionalsOther = 'CONSULTANTS_PROFESSIONALS__OTHER', + /** Sales */ + ConsultantsProfessionalsSales = 'CONSULTANTS_PROFESSIONALS__SALES', + /** Design, Architecture, Engineering */ + DesignArchitectureEngineering = 'DESIGN_ARCHITECTURE_ENGINEERING', + /** Other Financial Service */ + FinancialServices = 'FINANCIAL_SERVICES', + /** Salon, Spa */ + HairSpaAestheticsHairSalon = 'HAIR_SPA_AESTHETICS__HAIR_SALON', + /** Massage */ + HairSpaAestheticsMassage = 'HAIR_SPA_AESTHETICS__MASSAGE', + /** Nails, Skin, Aesthetics */ + HairSpaAestheticsNailSkinAesthetics = 'HAIR_SPA_AESTHETICS__NAIL_SKIN_AESTHETICS', + /** Other Aesthetics/Spa */ + HairSpaAestheticsOther = 'HAIR_SPA_AESTHETICS__OTHER', + /** Insurance Agency, Broker */ + InsuranceAgencyBroker = 'INSURANCE_AGENCY_BROKER', + /** Landlord */ + LandlordPropertyManagerLandlord = 'LANDLORD_PROPERTY_MANAGER__LANDLORD', + /** Property Manager */ + LandlordPropertyManagerPropertyManager = 'LANDLORD_PROPERTY_MANAGER__PROPERTY_MANAGER', + /** Lawn Care, Landscaping */ + LawnCareLandscaping = 'LAWN_CARE_LANDSCAPING', + /** Legal Services */ + LegalServices = 'LEGAL_SERVICES', + /** Lodging, Hotel, Motel */ + LodgingHotelMotel = 'LODGING_HOTEL_MOTEL', + /** Manufacturing Representative, Agent */ + ManufacturerRepresentativeAgent = 'MANUFACTURER_REPRESENTATIVE_AGENT', + /** Chiropractor */ + MedicalDentalHealthServiceChiropractor = 'MEDICAL_DENTAL_HEALTH_SERVICE__CHIROPRACTOR', + /** Dentist */ + MedicalDentalHealthServiceDentist = 'MEDICAL_DENTAL_HEALTH_SERVICE__DENTIST', + /** Fitness */ + MedicalDentalHealthServiceFitness = 'MEDICAL_DENTAL_HEALTH_SERVICE__FITNESS', + /** Massage Therapist */ + MedicalDentalHealthServiceMassageTherapist = 'MEDICAL_DENTAL_HEALTH_SERVICE__MASSAGE_THERAPIST', + /** Mental Health */ + MedicalDentalHealthServiceMentalHealth = 'MEDICAL_DENTAL_HEALTH_SERVICE__MENTAL_HEALTH', + /** Nutrition */ + MedicalDentalHealthServiceNutrition = 'MEDICAL_DENTAL_HEALTH_SERVICE__NUTRITION', + /** Occupational Therapist */ + MedicalDentalHealthServiceOccupTherapist = 'MEDICAL_DENTAL_HEALTH_SERVICE__OCCUP_THERAPIST', + /** Other Health */ + MedicalDentalHealthServiceOther = 'MEDICAL_DENTAL_HEALTH_SERVICE__OTHER', + /** Physical Therapist */ + MedicalDentalHealthServicePhysicalTherapist = 'MEDICAL_DENTAL_HEALTH_SERVICE__PHYSICAL_THERAPIST', + /** Association */ + NonprofitAssociationsGroupsAssociation = 'NONPROFIT_ASSOCIATIONS_GROUPS__ASSOCIATION', + /** Charity */ + NonprofitAssociationsGroupsCharitable = 'NONPROFIT_ASSOCIATIONS_GROUPS__CHARITABLE', + /** Club */ + NonprofitAssociationsGroupsClub = 'NONPROFIT_ASSOCIATIONS_GROUPS__CLUB', + /** Condo */ + NonprofitAssociationsGroupsCondo = 'NONPROFIT_ASSOCIATIONS_GROUPS__CONDO', + /** Other Non-Profit */ + NonprofitAssociationsGroupsOther = 'NONPROFIT_ASSOCIATIONS_GROUPS__OTHER', + /** Parent Booster USA */ + NonprofitAssociationsGroupsParentBooster = 'NONPROFIT_ASSOCIATIONS_GROUPS__PARENT_BOOSTER', + /** Other (please specify) */ + OtherOtherPleaseSpecify = 'OTHER__OTHER_PLEASE_SPECIFY', + /** Manufacturer */ + ProductProviderManufacturer = 'PRODUCT_PROVIDER__MANUFACTURER', + /** Manufacturer and Vendor */ + ProductProviderManufacturerAndVendor = 'PRODUCT_PROVIDER__MANUFACTURER_AND_VENDOR', + /** Other Product-based Business */ + ProductProviderOther = 'PRODUCT_PROVIDER__OTHER', + /** Vendor */ + ProductProviderVendor = 'PRODUCT_PROVIDER__VENDOR', + /** Real Estate Agent */ + RealEstateSalesAgent = 'REAL_ESTATE_SALES__AGENT', + /** Real Estate Broker */ + RealEstateSalesBroker = 'REAL_ESTATE_SALES__BROKER', + /** Other Real Estate */ + RealEstateSalesOther = 'REAL_ESTATE_SALES__OTHER', + /** Real Estate Rental */ + Rental = 'RENTAL', + /** Repairs/Maintenance */ + RepairAndMaintenance = 'REPAIR_AND_MAINTENANCE', + /** Restaurant, Caterer, Bar */ + RestaurantCatererBar = 'RESTAURANT_CATERER_BAR', + /** eBay Resellers */ + RetailersAndResellersEbay = 'RETAILERS_AND_RESELLERS__EBAY', + /** Etsy Vendors */ + RetailersAndResellersEtsy = 'RETAILERS_AND_RESELLERS__ETSY', + /** Non-Store Retailers */ + RetailersAndResellersNonStoreRetailer = 'RETAILERS_AND_RESELLERS__NON_STORE_RETAILER', + /** Other Retailers */ + RetailersAndResellersOther = 'RETAILERS_AND_RESELLERS__OTHER', + /** Store Retailers */ + RetailersAndResellersStoreRetailer = 'RETAILERS_AND_RESELLERS__STORE_RETAILER', + /** Sales: Independent Agent */ + SalesIndependentAgent = 'SALES_INDEPENDENT_AGENT', + /** Cleaning, Janitorial Services */ + ServiceProviderCleaningJanitorialServices = 'SERVICE_PROVIDER__CLEANING_JANITORIAL_SERVICES', + /** Customer Service/Support */ + ServiceProviderCustomerServiceSupport = 'SERVICE_PROVIDER__CUSTOMER_SERVICE_SUPPORT', + /** Household Employer */ + ServiceProviderDomesticCaregiverEmployer = 'SERVICE_PROVIDER__DOMESTIC_CAREGIVER_EMPLOYER', + /** Fitness */ + ServiceProviderFitness = 'SERVICE_PROVIDER__FITNESS', + /** Office Admin/Support */ + ServiceProviderOfficeAdminSupport = 'SERVICE_PROVIDER__OFFICE_ADMIN_SUPPORT', + /** Other Service-based Business */ + ServiceProviderOther = 'SERVICE_PROVIDER__OTHER', + /** Personal Care */ + ServiceProviderPersonalCare = 'SERVICE_PROVIDER__PERSONAL_CARE', + /** Telemarketing */ + ServiceProviderTelemarketing = 'SERVICE_PROVIDER__TELEMARKETING', + /** Transcription */ + ServiceProviderTranscription = 'SERVICE_PROVIDER__TRANSCRIPTION', + /** Transportation, Trucking, Deliver */ + TransportationTruckingDelivery = 'TRANSPORTATION_TRUCKING_DELIVERY', + /** Designer */ + WebMediaFreelancerDesigner = 'WEB_MEDIA_FREELANCER__DESIGNER', + /** Marketing, Social Media */ + WebMediaFreelancerMarketingSocialMedia = 'WEB_MEDIA_FREELANCER__MARKETING_SOCIAL_MEDIA', + /** Other Media/Tech */ + WebMediaFreelancerOther = 'WEB_MEDIA_FREELANCER__OTHER', + /** Programmer */ + WebMediaFreelancerProgrammer = 'WEB_MEDIA_FREELANCER__PROGRAMMER', + /** SEO */ + WebMediaFreelancerSeo = 'WEB_MEDIA_FREELANCER__SEO', + /** Writer */ + WebMediaFreelancerWriter = 'WEB_MEDIA_FREELANCER__WRITER', + /** Wholesale Distribution and Sales */ + WholesaleDistributionSales = 'WHOLESALE_DISTRIBUTION_SALES' +} + +/** Area of focus of a business. */ +export type BusinessType = { + __typename?: 'BusinessType'; + /** The description of the business type in human-friendly form. */ + name: Scalars['String']['output']; + /** The enum value of the business type. */ + value: BusinessTypeValue; +}; + +/** Area of focus of a business. */ +export enum BusinessTypeValue { + /** Artists, Photographers & Creative Types */ + ArtistsPhotographersCreative = 'ARTISTS_PHOTOGRAPHERS_CREATIVE', + /** Consultants & Professionals */ + ConsultantsProfessionals = 'CONSULTANTS_PROFESSIONALS', + /** Financial Services */ + FinanceInsurance = 'FINANCE_INSURANCE', + /** Hair, Spa & Aesthetics */ + HairSpaAesthetics = 'HAIR_SPA_AESTHETICS', + /** Medical, Dental, Health */ + MedicalDentalHealthService = 'MEDICAL_DENTAL_HEALTH_SERVICE', + /** Non-profits, Associations & Groups */ + NonprofitAssociationsGroups = 'NONPROFIT_ASSOCIATIONS_GROUPS', + /** Other (please specify) */ + Other = 'OTHER', + /** General: I make or sell a PRODUCT */ + ProductProvider = 'PRODUCT_PROVIDER', + /** Real Estate, Construction & Home Improvement */ + RealestateHome = 'REALESTATE_HOME', + /** Retailers, Resellers & Sales */ + RetailersAndResellers = 'RETAILERS_AND_RESELLERS', + /** General: I provide a SERVICE */ + ServiceProvider = 'SERVICE_PROVIDER', + /** Web, Tech & Media */ + WebMediaFreelancer = 'WEB_MEDIA_FREELANCER' +} + +/** A country. */ +export type Country = { + __typename?: 'Country'; + /** Country code. */ + code: CountryCode; + /** Default currency of the country. */ + currency: Currency; + /** Plain-language representation. */ + name: Scalars['String']['output']; + /** Name of the country with the appropriate article. */ + nameWithArticle: Scalars['String']['output']; + /** List of principal subdivisions. */ + provinces: Array; +}; + +/** Country codes ([ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)). */ +export enum CountryCode { + /** Andorra */ + Ad = 'AD', + /** United Arab Emirates */ + Ae = 'AE', + /** Afghanistan */ + Af = 'AF', + /** Antigua and Barbuda */ + Ag = 'AG', + /** Anguilla */ + Ai = 'AI', + /** Albania */ + Al = 'AL', + /** Armenia */ + Am = 'AM', + /** Angola */ + Ao = 'AO', + /** Antarctica */ + Aq = 'AQ', + /** Argentina */ + Ar = 'AR', + /** American Samoa */ + As = 'AS', + /** Austria */ + At = 'AT', + /** Australia */ + Au = 'AU', + /** Aruba */ + Aw = 'AW', + /** Åland Islands */ + Ax = 'AX', + /** Azerbaijan */ + Az = 'AZ', + /** Bosnia and Herzegovina */ + Ba = 'BA', + /** Barbados */ + Bb = 'BB', + /** Bangladesh */ + Bd = 'BD', + /** Belgium */ + Be = 'BE', + /** Burkina Faso */ + Bf = 'BF', + /** Bulgaria */ + Bg = 'BG', + /** Bahrain */ + Bh = 'BH', + /** Burundi */ + Bi = 'BI', + /** Benin */ + Bj = 'BJ', + /** Saint Barthélemy */ + Bl = 'BL', + /** Bermuda */ + Bm = 'BM', + /** Brunei Darussalam */ + Bn = 'BN', + /** Bolivia, Plurinational State of */ + Bo = 'BO', + /** Bonaire, Sint Eustatius and Saba */ + Bq = 'BQ', + /** Brazil */ + Br = 'BR', + /** Bahamas */ + Bs = 'BS', + /** Bhutan */ + Bt = 'BT', + /** Bouvet Island */ + Bv = 'BV', + /** Botswana */ + Bw = 'BW', + /** Belarus */ + By = 'BY', + /** Belize */ + Bz = 'BZ', + /** Canada */ + Ca = 'CA', + /** Cocos (Keeling) Islands */ + Cc = 'CC', + /** Congo, The Democratic Republic of the */ + Cd = 'CD', + /** Central African Republic */ + Cf = 'CF', + /** Congo */ + Cg = 'CG', + /** Switzerland */ + Ch = 'CH', + /** Côte d'Ivoire */ + Ci = 'CI', + /** Cook Islands */ + Ck = 'CK', + /** Chile */ + Cl = 'CL', + /** Cameroon */ + Cm = 'CM', + /** China */ + Cn = 'CN', + /** Colombia */ + Co = 'CO', + /** Costa Rica */ + Cr = 'CR', + /** Cuba */ + Cu = 'CU', + /** Cape Verde */ + Cv = 'CV', + /** Curaçao */ + Cw = 'CW', + /** Christmas Island */ + Cx = 'CX', + /** Cyprus */ + Cy = 'CY', + /** Czech Republic */ + Cz = 'CZ', + /** Germany */ + De = 'DE', + /** Djibouti */ + Dj = 'DJ', + /** Denmark */ + Dk = 'DK', + /** Dominica */ + Dm = 'DM', + /** Dominican Republic */ + Do = 'DO', + /** Algeria */ + Dz = 'DZ', + /** Ecuador */ + Ec = 'EC', + /** Estonia */ + Ee = 'EE', + /** Egypt */ + Eg = 'EG', + /** Western Sahara */ + Eh = 'EH', + /** Eritrea */ + Er = 'ER', + /** Spain */ + Es = 'ES', + /** Ethiopia */ + Et = 'ET', + /** Finland */ + Fi = 'FI', + /** Fiji */ + Fj = 'FJ', + /** Falkland Islands */ + Fk = 'FK', + /** Micronesia, Federated States of */ + Fm = 'FM', + /** Faroe Islands */ + Fo = 'FO', + /** France */ + Fr = 'FR', + /** Gabon */ + Ga = 'GA', + /** United Kingdom */ + Gb = 'GB', + /** Grenada */ + Gd = 'GD', + /** Georgia */ + Ge = 'GE', + /** French Guiana */ + Gf = 'GF', + /** Guernsey */ + Gg = 'GG', + /** Ghana */ + Gh = 'GH', + /** Gibraltar */ + Gi = 'GI', + /** Greenland */ + Gl = 'GL', + /** Gambia */ + Gm = 'GM', + /** Guinea */ + Gn = 'GN', + /** Guadeloupe */ + Gp = 'GP', + /** Equatorial Guinea */ + Gq = 'GQ', + /** Greece */ + Gr = 'GR', + /** South Georgia and the South Sandwich Islands */ + Gs = 'GS', + /** Guatemala */ + Gt = 'GT', + /** Guam */ + Gu = 'GU', + /** Guinea-Bissau */ + Gw = 'GW', + /** Guyana */ + Gy = 'GY', + /** Hong Kong */ + Hk = 'HK', + /** Heard Island and McDonald Islands */ + Hm = 'HM', + /** Honduras */ + Hn = 'HN', + /** Croatia */ + Hr = 'HR', + /** Haiti */ + Ht = 'HT', + /** Hungary */ + Hu = 'HU', + /** Indonesia */ + Id = 'ID', + /** Ireland */ + Ie = 'IE', + /** Israel */ + Il = 'IL', + /** Isle of Man */ + Im = 'IM', + /** India */ + In = 'IN', + /** British Indian Ocean Territory */ + Io = 'IO', + /** Iraq */ + Iq = 'IQ', + /** Iran */ + Ir = 'IR', + /** Iceland */ + Is = 'IS', + /** Italy */ + It = 'IT', + /** Jersey */ + Je = 'JE', + /** Jamaica */ + Jm = 'JM', + /** Jordan */ + Jo = 'JO', + /** Japan */ + Jp = 'JP', + /** Kenya */ + Ke = 'KE', + /** Kyrgyzstan */ + Kg = 'KG', + /** Cambodia */ + Kh = 'KH', + /** Kiribati */ + Ki = 'KI', + /** Comoros */ + Km = 'KM', + /** Saint Kitts and Nevis */ + Kn = 'KN', + /** Korea, Democratic People's Republic of */ + Kp = 'KP', + /** Korea, Republic of */ + Kr = 'KR', + /** Kuwait */ + Kw = 'KW', + /** Cayman Islands */ + Ky = 'KY', + /** Kazakhstan */ + Kz = 'KZ', + /** Lao People's Democratic Republic */ + La = 'LA', + /** Lebanon */ + Lb = 'LB', + /** Saint Lucia */ + Lc = 'LC', + /** Liechtenstein */ + Li = 'LI', + /** Sri Lanka */ + Lk = 'LK', + /** Liberia */ + Lr = 'LR', + /** Lesotho */ + Ls = 'LS', + /** Lithuania */ + Lt = 'LT', + /** Luxembourg */ + Lu = 'LU', + /** Latvia */ + Lv = 'LV', + /** Libya */ + Ly = 'LY', + /** Morocco */ + Ma = 'MA', + /** Monaco */ + Mc = 'MC', + /** Moldova, Republic of */ + Md = 'MD', + /** Montenegro */ + Me = 'ME', + /** Saint Martin */ + Mf = 'MF', + /** Madagascar */ + Mg = 'MG', + /** Marshall Islands */ + Mh = 'MH', + /** North Macedonia */ + Mk = 'MK', + /** Mali */ + Ml = 'ML', + /** Myanmar */ + Mm = 'MM', + /** Mongolia */ + Mn = 'MN', + /** Macao */ + Mo = 'MO', + /** Northern Mariana Islands */ + Mp = 'MP', + /** Martinique */ + Mq = 'MQ', + /** Mauritania */ + Mr = 'MR', + /** Montserrat */ + Ms = 'MS', + /** Malta */ + Mt = 'MT', + /** Mauritius */ + Mu = 'MU', + /** Maldives */ + Mv = 'MV', + /** Malawi */ + Mw = 'MW', + /** Mexico */ + Mx = 'MX', + /** Malaysia */ + My = 'MY', + /** Mozambique */ + Mz = 'MZ', + /** Namibia */ + Na = 'NA', + /** New Caledonia */ + Nc = 'NC', + /** Niger */ + Ne = 'NE', + /** Norfolk Island */ + Nf = 'NF', + /** Nigeria */ + Ng = 'NG', + /** Nicaragua */ + Ni = 'NI', + /** Netherlands */ + Nl = 'NL', + /** Norway */ + No = 'NO', + /** Nepal */ + Np = 'NP', + /** Nauru */ + Nr = 'NR', + /** Niue */ + Nu = 'NU', + /** New Zealand */ + Nz = 'NZ', + /** Oman */ + Om = 'OM', + /** Panama */ + Pa = 'PA', + /** Peru */ + Pe = 'PE', + /** French Polynesia */ + Pf = 'PF', + /** Papua New Guinea */ + Pg = 'PG', + /** Philippines */ + Ph = 'PH', + /** Pakistan */ + Pk = 'PK', + /** Poland */ + Pl = 'PL', + /** Saint Pierre and Miquelon */ + Pm = 'PM', + /** Pitcairn */ + Pn = 'PN', + /** Puerto Rico */ + Pr = 'PR', + /** Palestine */ + Ps = 'PS', + /** Portugal */ + Pt = 'PT', + /** Palau */ + Pw = 'PW', + /** Paraguay */ + Py = 'PY', + /** Qatar */ + Qa = 'QA', + /** Réunion */ + Re = 'RE', + /** Romania */ + Ro = 'RO', + /** Serbia */ + Rs = 'RS', + /** Russian Federation */ + Ru = 'RU', + /** Rwanda */ + Rw = 'RW', + /** Saudi Arabia */ + Sa = 'SA', + /** Solomon Islands */ + Sb = 'SB', + /** Seychelles */ + Sc = 'SC', + /** Sudan */ + Sd = 'SD', + /** Sweden */ + Se = 'SE', + /** Singapore */ + Sg = 'SG', + /** Saint Helena, Ascension and Tristan da Cunha */ + Sh = 'SH', + /** Slovenia */ + Si = 'SI', + /** Svalbard and Jan Mayen */ + Sj = 'SJ', + /** Slovakia */ + Sk = 'SK', + /** Sierra Leone */ + Sl = 'SL', + /** San Marino */ + Sm = 'SM', + /** Senegal */ + Sn = 'SN', + /** Somalia */ + So = 'SO', + /** Suriname */ + Sr = 'SR', + /** South Sudan */ + Ss = 'SS', + /** Sao Tome and Principe */ + St = 'ST', + /** El Salvador */ + Sv = 'SV', + /** Sint Maarten */ + Sx = 'SX', + /** Syria */ + Sy = 'SY', + /** Eswatini */ + Sz = 'SZ', + /** Turks and Caicos Islands */ + Tc = 'TC', + /** Chad */ + Td = 'TD', + /** French Southern Territories */ + Tf = 'TF', + /** Togo */ + Tg = 'TG', + /** Thailand */ + Th = 'TH', + /** Tajikistan */ + Tj = 'TJ', + /** Tokelau */ + Tk = 'TK', + /** Timor-Leste */ + Tl = 'TL', + /** Turkmenistan */ + Tm = 'TM', + /** Tunisia */ + Tn = 'TN', + /** Tonga */ + To = 'TO', + /** Turkey */ + Tr = 'TR', + /** Trinidad and Tobago */ + Tt = 'TT', + /** Tuvalu */ + Tv = 'TV', + /** Taiwan */ + Tw = 'TW', + /** Tanzania, United Republic of */ + Tz = 'TZ', + /** Ukraine */ + Ua = 'UA', + /** Uganda */ + Ug = 'UG', + /** United States Minor Outlying Islands */ + Um = 'UM', + /** United States */ + Us = 'US', + /** Uruguay */ + Uy = 'UY', + /** Uzbekistan */ + Uz = 'UZ', + /** Holy See */ + Va = 'VA', + /** Saint Vincent and the Grenadines */ + Vc = 'VC', + /** Venezuela, Bolivarian Republic of */ + Ve = 'VE', + /** Virgin Islands (British) */ + Vg = 'VG', + /** Virgin Islands (U.S) */ + Vi = 'VI', + /** Viet Nam */ + Vn = 'VN', + /** Vanuatu */ + Vu = 'VU', + /** Wallis and Futuna */ + Wf = 'WF', + /** Samoa */ + Ws = 'WS', + /** Yemen */ + Ye = 'YE', + /** Mayotte */ + Yt = 'YT', + /** South Africa */ + Za = 'ZA', + /** Zambia */ + Zm = 'ZM', + /** Zimbabwe */ + Zw = 'ZW' +} + +/** A medium of exchange in common use. */ +export type Currency = { + __typename?: 'Currency'; + /** Currency code. */ + code: CurrencyCode; + /** Expresses the relationship between a major currency unit and its minor currency unit. The number of digits found to the right of the decimal place to represent the fractional part of this currency (assumes a base of 10). */ + exponent: Scalars['Int']['output']; + /** Plain-language representation. */ + name: Scalars['String']['output']; + /** Plural version of currency name. */ + plural: Scalars['String']['output']; + /** Symbol used to denote that a number is a monetary value. */ + symbol: Scalars['String']['output']; +}; + +/** Currency codes based on ISO 4217. */ +export enum CurrencyCode { + /** UAE dirham */ + Aed = 'AED', + /** Afghani */ + Afn = 'AFN', + /** Lek */ + All = 'ALL', + /** Armenian dram */ + Amd = 'AMD', + /** Netherlands Antillean Guilder */ + Ang = 'ANG', + /** Kwanza */ + Aoa = 'AOA', + /** Argentinian peso */ + Ars = 'ARS', + /** Australian dollar */ + Aud = 'AUD', + /** Aruban Guilder */ + Awg = 'AWG', + /** New Manat */ + Azn = 'AZN', + /** Convertible Marks */ + Bam = 'BAM', + /** Barbados dollar */ + Bbd = 'BBD', + /** Taka */ + Bdt = 'BDT', + /** Lev */ + Bgn = 'BGN', + /** Bahraini dinar */ + Bhd = 'BHD', + /** Burundi franc */ + Bif = 'BIF', + /** Bermuda dollar */ + Bmd = 'BMD', + /** Brunei dollar */ + Bnd = 'BND', + /** Boliviano */ + Bob = 'BOB', + /** Real */ + Brl = 'BRL', + /** Bahamian dollar */ + Bsd = 'BSD', + /** Ngultrum */ + Btn = 'BTN', + /** Pula */ + Bwp = 'BWP', + /** Belarussian rouble */ + Byr = 'BYR', + /** Belize dollar */ + Bzd = 'BZD', + /** Canadian dollar */ + Cad = 'CAD', + /** Franc congolais */ + Cdf = 'CDF', + /** Swiss franc */ + Chf = 'CHF', + /** Chilean peso */ + Clp = 'CLP', + /** Ren-Min-Bi yuan */ + Cny = 'CNY', + /** Colombian peso */ + Cop = 'COP', + /** Costa Rican colon */ + Crc = 'CRC', + /** Cuban peso */ + Cup = 'CUP', + /** Cape Verde escudo */ + Cve = 'CVE', + /** Czech koruna */ + Czk = 'CZK', + /** Djibouti franc */ + Djf = 'DJF', + /** Danish krone */ + Dkk = 'DKK', + /** Dominican peso */ + Dop = 'DOP', + /** Algerian dinar */ + Dzd = 'DZD', + /** Estonian kroon */ + Eek = 'EEK', + /** Egyptian pound */ + Egp = 'EGP', + /** Nakfa */ + Ern = 'ERN', + /** Ethiopian birr */ + Etb = 'ETB', + /** Euro */ + Eur = 'EUR', + /** Fiji dollar */ + Fjd = 'FJD', + /** Falkland Islands (Malvinas) Pound */ + Fkp = 'FKP', + /** Pound sterling */ + Gbp = 'GBP', + /** Lari */ + Gel = 'GEL', + /** Ghana Cedi */ + Ghs = 'GHS', + /** Gibraltar pound */ + Gip = 'GIP', + /** Dalasi */ + Gmd = 'GMD', + /** Guinean franc */ + Gnf = 'GNF', + /** Quetzal */ + Gtq = 'GTQ', + /** Guinean bissau Peso */ + Gwp = 'GWP', + /** Guyana dollar */ + Gyd = 'GYD', + /** Hong Kong dollar */ + Hkd = 'HKD', + /** Lempira */ + Hnl = 'HNL', + /** Kuna */ + Hrk = 'HRK', + /** Haitian gourde */ + Htg = 'HTG', + /** Forint */ + Huf = 'HUF', + /** Rupiah */ + Idr = 'IDR', + /** New Israeli sheqel */ + Ils = 'ILS', + /** Indian rupee */ + Inr = 'INR', + /** Iraqi dinar */ + Iqd = 'IQD', + /** Iranian rial */ + Irr = 'IRR', + /** Icelandic Krona */ + Isk = 'ISK', + /** Jamaican dollar */ + Jmd = 'JMD', + /** Jordanian dinar */ + Jod = 'JOD', + /** Yen */ + Jpy = 'JPY', + /** Kenyan shilling */ + Kes = 'KES', + /** Kyrgyz Som */ + Kgs = 'KGS', + /** Riel */ + Khr = 'KHR', + /** Comoro franc */ + Kmf = 'KMF', + /** Won */ + Krw = 'KRW', + /** Kuwaiti dinar */ + Kwd = 'KWD', + /** Cayman Islands dollar */ + Kyd = 'KYD', + /** Tenge */ + Kzt = 'KZT', + /** Kip */ + Lak = 'LAK', + /** Lebanese pound */ + Lbp = 'LBP', + /** Sri Lankan rupee */ + Lkr = 'LKR', + /** Liberian dollar */ + Lrd = 'LRD', + /** Loti */ + Lsl = 'LSL', + /** Lithuanian litus */ + Ltl = 'LTL', + /** Latvian lats */ + Lvl = 'LVL', + /** Libyan dinar */ + Lyd = 'LYD', + /** Moroccan dirham */ + Mad = 'MAD', + /** Moldovan leu */ + Mdl = 'MDL', + /** Malagasy Ariary */ + Mga = 'MGA', + /** Denar */ + Mkd = 'MKD', + /** Kyat */ + Mmk = 'MMK', + /** Tugrik */ + Mnt = 'MNT', + /** Pataca */ + Mop = 'MOP', + /** Ouguiya */ + Mro = 'MRO', + /** Ouguiya */ + Mru = 'MRU', + /** Mauritian rupee */ + Mur = 'MUR', + /** Rufiyaa */ + Mvr = 'MVR', + /** Kwacha */ + Mwk = 'MWK', + /** Mexican peso */ + Mxn = 'MXN', + /** Malaysian ringgit */ + Myr = 'MYR', + /** Metical */ + Mzn = 'MZN', + /** Namibian dollar */ + Nad = 'NAD', + /** Naira */ + Ngn = 'NGN', + /** Cordoba Oro */ + Nio = 'NIO', + /** Norwegian krone */ + Nok = 'NOK', + /** Nepalese rupee */ + Npr = 'NPR', + /** New Zealand dollar */ + Nzd = 'NZD', + /** Omani rial */ + Omr = 'OMR', + /** Balboa */ + Pab = 'PAB', + /** Nuevo Sol */ + Pen = 'PEN', + /** Kina */ + Pgk = 'PGK', + /** Philippine peso */ + Php = 'PHP', + /** Pakistani rupee */ + Pkr = 'PKR', + /** Zloty */ + Pln = 'PLN', + /** Guarani */ + Pyg = 'PYG', + /** Qatari riyal */ + Qar = 'QAR', + /** New Leu */ + Ron = 'RON', + /** Serbian Dinar */ + Rsd = 'RSD', + /** Russian rouble */ + Rub = 'RUB', + /** Rwanda franc */ + Rwf = 'RWF', + /** Saudi riyal */ + Sar = 'SAR', + /** Solomon Islands Dollar */ + Sbd = 'SBD', + /** Seychelles rupee */ + Scr = 'SCR', + /** Sudanese Pound */ + Sdg = 'SDG', + /** Swedish Krona */ + Sek = 'SEK', + /** Singapore dollar */ + Sgd = 'SGD', + /** Saint Helena Pound */ + Shp = 'SHP', + /** Leone */ + Sll = 'SLL', + /** Somali shilling */ + Sos = 'SOS', + /** Surinam dollar */ + Srd = 'SRD', + /** South Sudanese pound */ + Ssp = 'SSP', + /** Dobra */ + Std = 'STD', + /** El Salvador colon */ + Svc = 'SVC', + /** Syrian pound */ + Syp = 'SYP', + /** Lilangeni */ + Szl = 'SZL', + /** Baht */ + Thb = 'THB', + /** Somoni */ + Tjs = 'TJS', + /** Manat */ + Tmm = 'TMM', + /** Tunisian dinar */ + Tnd = 'TND', + /** Pa'anga */ + Top = 'TOP', + /** Turkish Lira */ + Try = 'TRY', + /** Trinidad and Tobago dollar */ + Ttd = 'TTD', + /** Taiwan New Dollar */ + Twd = 'TWD', + /** Tanzanian shilling */ + Tzs = 'TZS', + /** Hryvnia */ + Uah = 'UAH', + /** Ugandan shilling */ + Ugx = 'UGX', + /** United States dollar */ + Usd = 'USD', + /** Uruguayo peso */ + Uyu = 'UYU', + /** Uzbekistan sum */ + Uzs = 'UZS', + /** Bolivar Fuerte */ + Vef = 'VEF', + /** Dong */ + Vnd = 'VND', + /** Vatu */ + Vuv = 'VUV', + /** Samoan Tala */ + Wst = 'WST', + /** CFA Franc - BEAC */ + Xaf = 'XAF', + /** Eastern Caribbean dollar */ + Xcd = 'XCD', + /** CFA franc - BCEAO */ + Xof = 'XOF', + /** Comptoirs Francais du Pacifique Francs */ + Xpf = 'XPF', + /** Yemeni rial */ + Yer = 'YER', + /** Rand */ + Zar = 'ZAR', + /** Kwacha */ + Zmk = 'ZMK', + /** Kwacha */ + Zmw = 'ZMW', + /** Zimbabwean dollar */ + Zwd = 'ZWD' +} + +/** A customer of the business. */ +export type Customer = BusinessNode & Node & { + __typename?: 'Customer'; + /** Address of the customer. */ + address?: Maybe
; + /** Business that the customer belongs to. */ + business: Business; + /** When the customer was created. */ + createdAt: Scalars['DateTime']['output']; + /** Default currency used by the customer. */ + currency?: Maybe; + /** User defined id for the customer. Commonly referred to as Account Number. */ + displayId?: Maybe; + /** Email of the principal contact. */ + email?: Maybe; + /** Fax number of the customer. */ + fax?: Maybe; + /** First name of the principal contact. */ + firstName?: Maybe; + /** Unique identifier for the customer. */ + id: Scalars['ID']['output']; + /** + * The primary key used internally at Wave. + * @deprecated Exposed internal IDs will eventually be removed in favor of global ID. Use Node.id instead. + */ + internalId?: Maybe; + /** Internal notes about the customer. */ + internalNotes?: Maybe; + /** Whether or not the customer is archived. */ + isArchived?: Maybe; + /** Last name of the principal contact. */ + lastName?: Maybe; + /** Mobile telephone number of the principal contact. */ + mobile?: Maybe; + /** When the customer was last modified. */ + modifiedAt: Scalars['DateTime']['output']; + /** Name or business name of the customer. */ + name: Scalars['String']['output']; + /** Amount due on customer's invoices. */ + outstandingAmount: Money; + /** Amount due on customer's invoices with due date that have passed. */ + overdueAmount: Money; + /** Telephone number of the customer. */ + phone?: Maybe; + /** Details for shipping to the customer. */ + shippingDetails?: Maybe; + /** Toll-free number of the customer. */ + tollFree?: Maybe; + /** Website address of the customer. */ + website?: Maybe; +}; + +/** Customer connection. */ +export type CustomerConnection = { + __typename?: 'CustomerConnection'; + /** List of customers. */ + edges: Array; + /** Information about pagination. */ + pageInfo: OffsetPageInfo; +}; + +/** Input to the `customerCreate` mutation. */ +export type CustomerCreateInput = { + /** Address */ + address?: InputMaybe; + /** The unique identifier for the business. */ + businessId: Scalars['ID']['input']; + /** Default currency used by the customer. */ + currency?: InputMaybe; + /** User defined id for the customer. */ + displayId?: InputMaybe; + /** Email of the principal contact. */ + email?: InputMaybe; + /** Fax number of the customer. */ + fax?: InputMaybe; + /** First name of the principal contact. */ + firstName?: InputMaybe; + /** Internal notes about the customer. */ + internalNotes?: InputMaybe; + /** Last name of the principal contact. */ + lastName?: InputMaybe; + /** Mobile telephone number of the principal contact. */ + mobile?: InputMaybe; + /** Name or business name of the customer. */ + name: Scalars['String']['input']; + /** Telephone number of the customer. */ + phone?: InputMaybe; + /** Details for shipping to the customer. */ + shippingDetails?: InputMaybe; + /** Toll-free number of the customer. */ + tollFree?: InputMaybe; + /** Website address of the customer. */ + website?: InputMaybe; +}; + +/** Output of the `customerCreate` mutation. */ +export type CustomerCreateOutput = { + __typename?: 'CustomerCreateOutput'; + /** Customer that was created. */ + customer?: Maybe; + /** Indicates whether the customer was successfully created. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; +}; + +/** Input to the `customerDelete` mutation. */ +export type CustomerDeleteInput = { + /** The unique identifier for the customer. */ + id: Scalars['ID']['input']; +}; + +/** Output of the `customerDelete` mutation. */ +export type CustomerDeleteOutput = { + __typename?: 'CustomerDeleteOutput'; + /** Indicates whether the customer was successfully deleted. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; +}; + +/** Customer edge. */ +export type CustomerEdge = { + __typename?: 'CustomerEdge'; + /** A customer. */ + node?: Maybe; +}; + +/** Input to the `customerPatch` mutation. */ +export type CustomerPatchInput = { + /** Address */ + address?: InputMaybe; + /** Default currency used by the customer. */ + currency?: InputMaybe; + /** User defined id for the customer. */ + displayId?: InputMaybe; + /** Email of the principal contact. */ + email?: InputMaybe; + /** Fax number of the customer. */ + fax?: InputMaybe; + /** First name of the principal contact. */ + firstName?: InputMaybe; + /** The unique identifier for the customer. */ + id: Scalars['ID']['input']; + /** Internal notes about the customer. */ + internalNotes?: InputMaybe; + /** Last name of the principal contact. */ + lastName?: InputMaybe; + /** Mobile telephone number of the principal contact. */ + mobile?: InputMaybe; + /** Name or business name of the customer. */ + name?: InputMaybe; + /** Telephone number of the customer. */ + phone?: InputMaybe; + /** Details for shipping to the customer. */ + shippingDetails?: InputMaybe; + /** Toll-free number of the customer. */ + tollFree?: InputMaybe; + /** Website address of the customer. */ + website?: InputMaybe; +}; + +/** Output of the `customerPatch` mutation. */ +export type CustomerPatchOutput = { + __typename?: 'CustomerPatchOutput'; + /** Customer that was patched. */ + customer?: Maybe; + /** Indicates whether the customer was successfully patched. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; +}; + +/** Shipping details related to a customer. */ +export type CustomerPatchShippingDetailsInput = { + /** Address of the customer. */ + address?: InputMaybe; + /** Delivery instructions for handling. */ + instructions?: InputMaybe; + /** Name or business name of the customer. */ + name?: InputMaybe; + /** Telephone number of the customer. */ + phone?: InputMaybe; +}; + +/** Shipping details related to a customer. */ +export type CustomerShippingDetails = { + __typename?: 'CustomerShippingDetails'; + /** Address of the customer. */ + address?: Maybe
; + /** Delivery instructions for handling. */ + instructions?: Maybe; + /** Name or business name of the customer. */ + name?: Maybe; + /** Telephone number of the customer. */ + phone?: Maybe; +}; + +/** Shipping details related to a customer. */ +export type CustomerShippingDetailsInput = { + /** Address of the customer. */ + address?: InputMaybe; + /** Delivery instructions for handling. */ + instructions?: InputMaybe; + /** Name or business name of the customer. */ + name?: InputMaybe; + /** Telephone number of the customer. */ + phone?: InputMaybe; +}; + +/** Options by which customers can be ordered. */ +export enum CustomerSort { + /** Ascending by creation time. */ + CreatedAtAsc = 'CREATED_AT_ASC', + /** Descending by creation time. */ + CreatedAtDesc = 'CREATED_AT_DESC', + /** Ascending by modified time. */ + ModifiedAtAsc = 'MODIFIED_AT_ASC', + /** Descending by modified time. */ + ModifiedAtDesc = 'MODIFIED_AT_DESC', + /** Ascending by name. */ + NameAsc = 'NAME_ASC', + /** Descending by name. */ + NameDesc = 'NAME_DESC' +} + +/** An approximate bill given to a buyer indicating the products or services, quantities, and expected prices (not a request for payment). */ +export type Estimate = BusinessNode & Node & { + __typename?: 'Estimate'; + /** Business that the Estimate belongs to */ + business: Business; + /** Unique identifier for the estimate. */ + id: Scalars['ID']['output']; + /** + * The primary key used internally at Wave. + * @deprecated Exposed internal IDs will eventually be removed in favor of global ID. Use Node.id instead. + */ + internalId?: Maybe; +}; + +/** A fixed discount applied to an Invoice. */ +export type FixedInvoiceDiscount = InvoiceDiscount & { + __typename?: 'FixedInvoiceDiscount'; + /** The amount of the discount. */ + amount?: Maybe; + /** When the invoice discount was created. */ + createdAt: Scalars['DateTime']['output']; + /** When the invoice discount was last modified. */ + modifiedAt: Scalars['DateTime']['output']; + /** A description of the discount. */ + name?: Maybe; +}; + +/** General settings on an invoice and estimate. */ +export type GeneralSettings = { + __typename?: 'GeneralSettings'; + /** Color to represent the brand of the business. */ + accentColor?: Maybe; + /** Logo of the business. */ + logoUrl?: Maybe; +}; + +/** Mutation validation error. */ +export type InputError = { + __typename?: 'InputError'; + /** Error code. */ + code?: Maybe; + /** Error message. */ + message?: Maybe; + /** Path to the input value. */ + path?: Maybe>; +}; + +/** Document issued to a buyer for payment indicating the products or services, quantities, and agreed prices. */ +export type Invoice = BusinessNode & Node & { + __typename?: 'Invoice'; + /** Invoice total less amount already paid. */ + amountDue: Money; + /** Total of all payments so far made against this invoice. */ + amountPaid: Money; + /** The label for the 'Amount' (= unit x price) column in the listing of line items on the invoice. */ + amountTitle: Scalars['String']['output']; + /** Business that the invoice belongs to. */ + business: Business; + /** When the invoice was created. */ + createdAt: Scalars['DateTime']['output']; + /** Currency of the invoice. */ + currency: Currency; + /** Customer the invoice is for. */ + customer: Customer; + /** Within a business that is enabled to accept credit card payments, indicates if this individual invoice has been marked to not be payable by American Express. */ + disableAmexPayments: Scalars['Boolean']['output']; + /** Within a business that is enabled to accept bank payments, indicates if this individual invoice has been marked to not be payable by bank payment. */ + disableBankPayments: Scalars['Boolean']['output']; + /** Within a business that is enabled to accept credit card payments, indicates if this individual invoice has been marked to not be payable by card. */ + disableCreditCardPayments: Scalars['Boolean']['output']; + /** Total value of all discounts. */ + discountTotal: Money; + /** Invoice discounts. */ + discounts?: Maybe>; + /** Date when payment is due. */ + dueDate: Scalars['Date']['output']; + /** Exchange rate to business's currency from the invoice's currency. Used to value the invoice income within Wave's accounting transactions. */ + exchangeRate: Scalars['Decimal']['output']; + /** Invoice footer text. */ + footer?: Maybe; + /** Indicates whether item's amount is hidden in the line items listing. */ + hideAmount: Scalars['Boolean']['output']; + /** Indicates whether item's description in item column is hidden in the line items listing. */ + hideDescription: Scalars['Boolean']['output']; + /** Indicates whether item's product name in item column is hidden in the line items listing. */ + hideName: Scalars['Boolean']['output']; + /** Indicates whether item's price is hidden in the line items listing. */ + hidePrice: Scalars['Boolean']['output']; + /** Indicates whether item's unit is hidden in the line items listing. */ + hideUnit: Scalars['Boolean']['output']; + /** Unique identifier for the invoice. */ + id: Scalars['ID']['output']; + /** + * The primary key used internally at Wave. + * @deprecated Exposed internal IDs will eventually be removed in favor of global ID. Use Node.id instead. + */ + internalId?: Maybe; + /** Date when invoice is issued. */ + invoiceDate: Scalars['Date']['output']; + /** Unique number assigned to the invoice. */ + invoiceNumber: Scalars['String']['output']; + /** The label for the 'Item' column in the line items listing. */ + itemTitle: Scalars['String']['output']; + /** The line items (product, unit and price) that make up the invoiced sale. */ + items?: Maybe>; + /** When the invoice was last sent. */ + lastSentAt?: Maybe; + /** How the invoice was last sent. */ + lastSentVia?: Maybe; + /** When the invoice was last viewed by the customer. */ + lastViewedAt?: Maybe; + /** Invoice memo (notes) text. */ + memo?: Maybe; + /** When the invoice was last modified. */ + modifiedAt: Scalars['DateTime']['output']; + /** URL to access PDF representation of the invoice. */ + pdfUrl: Scalars['String']['output']; + /** Purchase order or sales order number for the invoice. */ + poNumber?: Maybe; + /** The label for the 'Price' column in the listing of line items on the invoice. */ + priceTitle: Scalars['String']['output']; + /** Indicates whether the customer is required to accept the terms of service. */ + requireTermsOfServiceAgreement: Scalars['Boolean']['output']; + /** Entity that was the precursor to the invoice. */ + source?: Maybe; + /** Status of the Invoice. */ + status: InvoiceStatus; + /** Invoice subheading text. */ + subhead?: Maybe; + /** Pretax total. */ + subtotal: Money; + /** Total of all sales taxes on all line items within the invoice. */ + taxTotal: Money; + /** Invoice title at the top of the document. */ + title: Scalars['String']['output']; + /** Total value of the invoice including sales taxes. */ + total: Money; + /** The label for the 'Unit' column in the listing of line items on the invoice. */ + unitTitle: Scalars['String']['output']; + /** URL to view the invoice online as seen by a customer. */ + viewUrl: Scalars['String']['output']; +}; + +/** Input to the `invoiceApprove` mutation. */ +export type InvoiceApproveInput = { + /** The unique identifier for the invoice. */ + invoiceId: Scalars['ID']['input']; +}; + +/** Output of the `invoiceApprove` mutation. */ +export type InvoiceApproveOutput = { + __typename?: 'InvoiceApproveOutput'; + /** Indicates whether the invoice was successfully approved. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Invoice that was approved. */ + invoice?: Maybe; +}; + +/** Input to the `invoiceClone` mutation. */ +export type InvoiceCloneInput = { + /** The unique identifier for the invoice. */ + invoiceId: Scalars['ID']['input']; +}; + +/** Output of the `invoiceClone` mutation. */ +export type InvoiceCloneOutput = { + __typename?: 'InvoiceCloneOutput'; + /** Indicates whether the invoice was successfully cloned. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Invoice that was cloned. */ + invoice?: Maybe; +}; + +/** Invoice connection. */ +export type InvoiceConnection = { + __typename?: 'InvoiceConnection'; + /** List of invoices. */ + edges: Array; + /** Information about pagination. */ + pageInfo: OffsetPageInfo; +}; + +/** Input to the `invoiceCreate` mutation. */ +export type InvoiceCreateInput = { + /** The label for the 'Amount' (= unit x price) column in the listing of line items on the invoice. If not provided, will use the business's default invoice column amount title. */ + amountTitle?: InputMaybe; + /** The unique identifier for the business. */ + businessId: Scalars['ID']['input']; + /** Currency of the invoice. If not provided, will use the business's default currency. */ + currency?: InputMaybe; + /** The customer identifier to associate with invoice. */ + customerId: Scalars['ID']['input']; + /** Within a business that is enabled to accept credit card payments, indicates if this individual invoice has been marked to not be payable by american express payment. If not provided, will use the business's default invoice settings american express payment state. */ + disableAmexPayments?: InputMaybe; + /** Within a business that is enabled to accept bank payments, indicates if this individual invoice has been marked to not be payable by bank payment. If not provided, will use the business's default invoice bank payment state. */ + disableBankPayments?: InputMaybe; + /** Within a business that is enabled to accept credit card payments, indicates if this individual invoice has been marked to not be payable by card. If not provided, will use the business's default invoice credit card payment state. */ + disableCreditCardPayments?: InputMaybe; + /** The discounts applied to the invoice (currently limited to max 1). */ + discounts?: InputMaybe>; + /** Date when payment is due. If not provided, will apply the business's default invoice payment terms to `invoiceDate` value. */ + dueDate?: InputMaybe; + /** Exchange rate to business's currency from the invoice's currency. Used to value the invoice income within Wave's accounting transactions. */ + exchangeRate?: InputMaybe; + /** Invoice footer text. If not provided, will use the business's default invoice footer. */ + footer?: InputMaybe; + /** Indicates whether item's amount is hidden in the line items listing. If not provided, will use the business's default invoice item amount visibility. */ + hideAmount?: InputMaybe; + /** Indicates whether item's description in item column is hidden in the line items listing. If not provided, will use the business's default invoice item description visibility. */ + hideDescription?: InputMaybe; + /** Indicates whether item's product name in item column is hidden in the line items listing. If not provided, will use the business's default invoice item name visibility. */ + hideName?: InputMaybe; + /** Indicates whether item's price is hidden in the line items listing. If not provided, will use the business's default invoice item price visibility. */ + hidePrice?: InputMaybe; + /** Indicates whether item's unit is hidden in the line items listing. If not provided, will use the business's default invoice item unit visibility. */ + hideUnit?: InputMaybe; + /** Date when invoice is issued. If not provided, will use today's date. */ + invoiceDate?: InputMaybe; + /** Unique number assigned to the invoice. If not provided, will find the current largest invoice number and add 1. */ + invoiceNumber?: InputMaybe; + /** The label for the 'Item' column in the listing of line items on the invoice. If not provided, will use the business's default invoice column item title. */ + itemTitle?: InputMaybe; + /** The line items (product, unit and price) that make up the invoiced sale. */ + items?: InputMaybe>; + /** Invoice memo (notes) text. If not provided, will use the business's default invoice memo. */ + memo?: InputMaybe; + /** Purchase order or sales order number for the invoice. */ + poNumber?: InputMaybe; + /** The label for the 'Price' column in the listing of line items on the invoice. If not provided, will use the business's default invoice column price title. */ + priceTitle?: InputMaybe; + /** Indicates whether the customer is required to accept the terms of service. */ + requireTermsOfServiceAgreement?: InputMaybe; + /** Status of the Invoice. */ + status?: InputMaybe; + /** Invoice subheading text. If not provided, will use the business's default invoice subheading. */ + subhead?: InputMaybe; + /** Invoice title at the top of the document. If not provided, will use the business's default invoice title. */ + title?: InputMaybe; + /** The label for the 'Unit' column in the listing of line items on the invoice. If not provided, will use the business's default invoice column unit title. */ + unitTitle?: InputMaybe; +}; + +/** Invoice line item. */ +export type InvoiceCreateItemInput = { + /** Override product's description. */ + description?: InputMaybe; + /** Associated product. */ + productId: Scalars['ID']['input']; + /** Number of units (rounded to nearest 8 decimal places with ties going away from zero). */ + quantity?: InputMaybe; + /** Taxes. To have the product's default sales taxes applied, provide `undefined` as the value. */ + taxes?: InputMaybe>; + /** Override product's unitPrice. Price per unit in the major currency unit (rounded to nearest 8 decimal places with ties going away from zero). */ + unitPrice?: InputMaybe; +}; + +/** Invoice line item's sales tax. */ +export type InvoiceCreateItemTaxInput = { + /** *DEPRECATED - DO NOT USE* Sales Tax Amount is calculated by Wave using your Sales Tax settings. */ + amount?: InputMaybe; + /** Sales tax. */ + salesTaxId: Scalars['ID']['input']; +}; + +/** Output of the `invoiceCreate` mutation. */ +export type InvoiceCreateOutput = { + __typename?: 'InvoiceCreateOutput'; + /** Indicates whether the invoice was successfully created. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Invoice that was created. */ + invoice?: Maybe; +}; + +/** Status of an invoice. */ +export enum InvoiceCreateStatus { + /** The invoice is still a draft. */ + Draft = 'DRAFT', + /** The invoice was saved. */ + Saved = 'SAVED' +} + +/** Input to the `invoiceDelete` mutation. */ +export type InvoiceDeleteInput = { + /** The unique identifier for the invoice. */ + invoiceId: Scalars['ID']['input']; +}; + +/** Output of the `invoiceDelete` mutation. */ +export type InvoiceDeleteOutput = { + __typename?: 'InvoiceDeleteOutput'; + /** Indicates whether the invoice was successfully deleted. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; +}; + +/** Common base properties of InvoiceDiscounts. */ +export type InvoiceDiscount = { + /** When the invoice discount was created. */ + createdAt: Scalars['DateTime']['output']; + /** When the invoice discount was last modified. */ + modifiedAt: Scalars['DateTime']['output']; + /** A description of the discount. */ + name?: Maybe; +}; + +/** Invoice Discount. */ +export type InvoiceDiscountInput = { + /** Discount amount (for FIXED-type discounts). */ + amount?: InputMaybe; + /** Discount type. */ + discountType: InvoiceDiscountType; + /** Discount name. */ + name?: InputMaybe; + /** Discount percentage (for PERCENTAGE-type discounts). */ + percentage?: InputMaybe; +}; + +/** Type of invoice discount. */ +export enum InvoiceDiscountType { + /** Fixed dollar amount discount. */ + Fixed = 'FIXED', + /** Type of invoice discount. */ + Percentage = 'PERCENTAGE' +} + +/** Invoice edge. */ +export type InvoiceEdge = { + __typename?: 'InvoiceEdge'; + /** An invoice. */ + node: Invoice; +}; + +/** Business invoice and estimates settings information. */ +export type InvoiceEstimateSettings = { + __typename?: 'InvoiceEstimateSettings'; + /** Settings applied to both invoices and estimates. */ + generalSettings: GeneralSettings; +}; + +/** Invoice line item. */ +export type InvoiceItem = { + __typename?: 'InvoiceItem'; + /** Income account. */ + account: Account; + /** Detailed description. */ + description?: Maybe; + /** + * Price per unit. + * @deprecated Use unitPrice to avoid ambiguity in how the value relates to quantity and the subtotal. + */ + price: Scalars['Decimal']['output']; + /** Associated product. */ + product: Product; + /** Number of units. */ + quantity: Scalars['Decimal']['output']; + /** Pretax total. */ + subtotal: Money; + /** Taxes. */ + taxes: Array; + /** Total including sales taxes. */ + total: Money; + /** Price per unit in the major currency unit. */ + unitPrice: Scalars['Decimal']['output']; +}; + +/** Invoice line item's sales tax. */ +export type InvoiceItemTax = { + __typename?: 'InvoiceItemTax'; + /** Sales tax amount. */ + amount?: Maybe; + /** + * Sales tax rate. + * @deprecated Use `salesTax.rate`. `rate` will be removed on Oct 20th 2022. + */ + rate?: Maybe; + /** Sales tax. */ + salesTax: SalesTax; +}; + +/** Input to the `invoiceMarkSent` mutation. */ +export type InvoiceMarkSentInput = { + /** The unique identifier for the invoice. */ + invoiceId: Scalars['ID']['input']; + /** How the invoice was sent. */ + sendMethod: InvoiceSendMethod; + /** When the invoice was sent. */ + sentAt?: InputMaybe; +}; + +/** Output of the `invoiceMarkSent` mutation. */ +export type InvoiceMarkSentOutput = { + __typename?: 'InvoiceMarkSentOutput'; + /** Indicates whether the invoice was successfully marked as sent. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Invoice that was marked as sent. */ + invoice?: Maybe; +}; + +/** Input to the `invoicePatch` mutation. For each value if it's not provided - do not update it. */ +export type InvoicePatchInput = { + /** The label for the 'Amount' (= unit x price) column in the listing of line items on the invoice. */ + amountTitle?: InputMaybe; + /** Currency of the invoice. */ + currency?: InputMaybe; + /** The customer identifier to associate with invoice. */ + customerId?: InputMaybe; + /** Within a business that is enabled to accept credit card payments, indicates if this individual invoice has been marked to not be payable by american express payment. */ + disableAmexPayments?: InputMaybe; + /** Within a business that is enabled to accept bank payments, indicates if this individual invoice has been marked to not be payable by bank payment. */ + disableBankPayments?: InputMaybe; + /** Within a business that is enabled to accept credit card payments, indicates if this individual invoice has been marked to not be payable by card. */ + disableCreditCardPayments?: InputMaybe; + /** The discounts applied to the invoice (currently limited to max 1). */ + discounts?: InputMaybe>; + /** Date when payment is due. */ + dueDate?: InputMaybe; + /** Exchange rate to business's currency from the invoice's currency. Used to value the invoice income within Wave's accounting transactions. */ + exchangeRate?: InputMaybe; + /** Invoice footer text. */ + footer?: InputMaybe; + /** Indicates whether item's amount is hidden in the line items listing. */ + hideAmount?: InputMaybe; + /** Indicates whether item's description in item column is hidden in the line items listing. */ + hideDescription?: InputMaybe; + /** Indicates whether item's product name in item column is hidden in the line items listing. */ + hideName?: InputMaybe; + /** Indicates whether item's price is hidden in the line items listing. */ + hidePrice?: InputMaybe; + /** Indicates whether item's unit is hidden in the line items listing. */ + hideUnit?: InputMaybe; + /** Unique identifier for the invoice. */ + id: Scalars['ID']['input']; + /** Date when invoice is issued. */ + invoiceDate?: InputMaybe; + /** Unique number assigned to the invoice. */ + invoiceNumber?: InputMaybe; + /** The label for the 'Item' column in the listing of line items on the invoice. */ + itemTitle?: InputMaybe; + /** The line items (product, unit and price) that make up the invoiced sale. If provided, it would replace all items with given ones. */ + items?: InputMaybe>; + /** Invoice memo (notes) text. */ + memo?: InputMaybe; + /** Purchase order or sales order number for the invoice. */ + poNumber?: InputMaybe; + /** The label for the 'Price' column in the listing of line items on the invoice. */ + priceTitle?: InputMaybe; + /** Indicates whether the customer is required to accept the terms of service. */ + requireTermsOfServiceAgreement?: InputMaybe; + /** Status of the Invoice. */ + status?: InputMaybe; + /** Invoice subheading text. */ + subhead?: InputMaybe; + /** Invoice title at the top of the document. */ + title?: InputMaybe; + /** The label for the 'Unit' column in the listing of line items on the invoice. */ + unitTitle?: InputMaybe; +}; + +/** Output of the `invoicePatch` mutation. */ +export type InvoicePatchOutput = { + __typename?: 'InvoicePatchOutput'; + /** Indicates whether the invoice was successfully created. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Invoice that was created. */ + invoice?: Maybe; +}; + +/** Input to the `invoiceSend` mutation. */ +export type InvoiceSendInput = { + /** Include a PDF of the invoice as an attachment. */ + attachPDF?: Scalars['Boolean']['input']; + /** Carbon copy email. */ + ccMyself?: InputMaybe; + /** Email address from */ + fromAddress?: InputMaybe; + /** The unique identifier for the invoice. */ + invoiceId: Scalars['ID']['input']; + /** Message body of the email. */ + message?: InputMaybe; + /** Subject line of the email. */ + subject?: InputMaybe; + /** Email addresses to receive an email. */ + to: Array; +}; + +/** Invoice send method. */ +export enum InvoiceSendMethod { + /** Export PDF. */ + ExportPdf = 'EXPORT_PDF', + /** Gmail */ + Gmail = 'GMAIL', + /** Marked as sent. */ + MarkedSent = 'MARKED_SENT', + /** Not sent. */ + NotSent = 'NOT_SENT', + /** Outlook. */ + Outlook = 'OUTLOOK', + /** Shared link. */ + SharedLink = 'SHARED_LINK', + /** Skipped. */ + Skipped = 'SKIPPED', + /** Wave. */ + Wave = 'WAVE', + /** Yahoo. */ + Yahoo = 'YAHOO' +} + +/** Output of the `invoiceSend` mutation. */ +export type InvoiceSendOutput = { + __typename?: 'InvoiceSendOutput'; + /** Indicates whether the invoice was successfully queued for sending. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Invoice that was sent. */ + invoice?: Maybe; +}; + +/** Options by which invoices can be ordered. */ +export enum InvoiceSort { + /** Ascending by amount due. */ + AmountDueAsc = 'AMOUNT_DUE_ASC', + /** Descending by amount due. */ + AmountDueDesc = 'AMOUNT_DUE_DESC', + /** Ascending by amount paid. */ + AmountPaidAsc = 'AMOUNT_PAID_ASC', + /** Descending by amount paid. */ + AmountPaidDesc = 'AMOUNT_PAID_DESC', + /** Ascending by creation time. */ + CreatedAtAsc = 'CREATED_AT_ASC', + /** Descending by creation time. */ + CreatedAtDesc = 'CREATED_AT_DESC', + /** Ascending by customer's name. */ + CustomerNameAsc = 'CUSTOMER_NAME_ASC', + /** Descending by customer's name. */ + CustomerNameDesc = 'CUSTOMER_NAME_DESC', + /** Ascending by due date. */ + DueAtAsc = 'DUE_AT_ASC', + /** Descending by due date. */ + DueAtDesc = 'DUE_AT_DESC', + /** Ascending by invoice date. */ + InvoiceDateAsc = 'INVOICE_DATE_ASC', + /** Descending by invoice date. */ + InvoiceDateDesc = 'INVOICE_DATE_DESC', + /** Ascending by invoice number. */ + InvoiceNumberAsc = 'INVOICE_NUMBER_ASC', + /** Descending by invoice number. */ + InvoiceNumberDesc = 'INVOICE_NUMBER_DESC', + /** Ascending by modified date. */ + ModifiedAtAsc = 'MODIFIED_AT_ASC', + /** Descending by modified date. */ + ModifiedAtDesc = 'MODIFIED_AT_DESC', + /** Ascending by status. */ + StatusAsc = 'STATUS_ASC', + /** Descending by status. */ + StatusDesc = 'STATUS_DESC', + /** Ascending by total amount. */ + TotalAsc = 'TOTAL_ASC', + /** Descending by total amount. */ + TotalDesc = 'TOTAL_DESC' +} + +/** Specifies either the invoice is made from estimate, is recurring, or created manually. */ +export type InvoiceSource = Estimate | NewEstimate | RecurringInvoice; + +/** Status of an invoice. */ +export enum InvoiceStatus { + /** The invoice is still a draft. */ + Draft = 'DRAFT', + /** The invoice is overdue. */ + Overdue = 'OVERDUE', + /** The invoice was overpaid. */ + Overpaid = 'OVERPAID', + /** The invoice was paid. */ + Paid = 'PAID', + /** The invoice was partially paid. */ + Partial = 'PARTIAL', + /** The invoice was saved. */ + Saved = 'SAVED', + /** The invoice was sent. */ + Sent = 'SENT', + /** The invoice is unpaid. */ + Unpaid = 'UNPAID', + /** The invoice was viewed. */ + Viewed = 'VIEWED' +} + +/** A medium of exchange in common use. */ +export type Money = { + __typename?: 'Money'; + /** Currency */ + currency: Currency; + /** Value represented in only the minor currency unit. */ + minorUnitValue: Scalars['Decimal']['output']; + /** + * Value represented in only the minor currency unit. + * @deprecated Use `minorUnitValue` instead, as `raw` can overflow for large numbers. + */ + raw: Scalars['Int']['output']; + /** Amount represented as a combination of the major and minor currency unit (uses a decimal separator). */ + value: Scalars['String']['output']; +}; + +/** Input representing a deposit. */ +export type MoneyDepositTransactionCreateDepositInput = { + /** Id of the account. */ + accountId: Scalars['ID']['input']; + /** Date of the transaction. */ + amount: Scalars['Float']['input']; +}; + +/** Fee input. */ +export type MoneyDepositTransactionCreateFeeInput = { + /** ID of the account associated with the fee. */ + accountId: Scalars['ID']['input']; + /** Amount. */ + amount: Scalars['Float']['input']; +}; + +/** Input of the moneyDepositTransactionCreate Mutation */ +export type MoneyDepositTransactionCreateInput = { + /** Id of the business. */ + businessId: Scalars['ID']['input']; + /** Transaction timestamp. */ + createdAt?: InputMaybe; + /** Date of the transaction. */ + date: Scalars['Date']['input']; + /** Deposit account and amount. */ + deposit: MoneyDepositTransactionCreateDepositInput; + /** Description for the transaction. */ + description: Scalars['String']['input']; + /** ID of the transaction in an external system. */ + externalId?: InputMaybe; + /** Fees. */ + fees?: InputMaybe>; + /** Line items. */ + lineItems: Array; + /** Extra notes about the transaction. */ + notes?: InputMaybe; + /** Origin of the transaction. */ + origin: TransactionOrigin; +}; + +/** Line item input. */ +export type MoneyDepositTransactionCreateLineItemInput = { + /** ID of the account associated with the line item. */ + accountId: Scalars['ID']['input']; + /** Amount. */ + amount: Scalars['Float']['input']; + /** ID of the customer associated with the line item. */ + customerId?: InputMaybe; + /** Taxes applied to the line item. */ + taxes: Array; +}; + +/** Output of the moneyDepositTransactionCreate Mutation */ +export type MoneyDepositTransactionCreateOutput = { + __typename?: 'MoneyDepositTransactionCreateOutput'; + /** Whether or not the transaction was successfully created. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; +}; + +/** Anchor input. */ +export type MoneyTransactionCreateAnchorInput = { + /** ID of the anchor account. */ + accountId: Scalars['ID']['input']; + /** Amount of the transaction (unsigned). */ + amount: Scalars['Decimal']['input']; + /** Direction of a transaction */ + direction: TransactionDirection; +}; + +/** Input of the `moneyTransactionCreate` Mutation */ +export type MoneyTransactionCreateInput = { + /** Anchor item. */ + anchor: MoneyTransactionCreateAnchorInput; + /** The unique identifier for the business. */ + businessId: Scalars['ID']['input']; + /** Date of the transaction. */ + date: Scalars['Date']['input']; + /** Description for the transaction. */ + description: Scalars['String']['input']; + /** ID of the transaction in an external system. If you don't have one, generate a UUID and provide it. */ + externalId: Scalars['String']['input']; + /** Line items. */ + lineItems: Array; + /** Extra notes about the transaction. */ + notes?: InputMaybe; +}; + +/** Line item input. */ +export type MoneyTransactionCreateLineItemInput = { + /** ID of the account associated with the line item. */ + accountId: Scalars['ID']['input']; + /** Amount of the line item (unsigned). */ + amount: Scalars['Decimal']['input']; + /** How the account should change in relation to the amount. */ + balance?: BalanceType; + /** ID of the customer associated with the line item. */ + customerId?: InputMaybe; + /** Optional description for line item. */ + description?: InputMaybe; + /** Taxes applied to the line item. */ + taxes?: InputMaybe>; +}; + +/** Output of the `moneyTransactionCreate` Mutation */ +export type MoneyTransactionCreateOutput = { + __typename?: 'MoneyTransactionCreateOutput'; + /** Whether or not the transaction was successfully created. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Created transaction. */ + transaction?: Maybe; +}; + +/** Sales tax input. */ +export type MoneyTransactionCreateSalesTaxInput = { + /** Override the amount of the tax (unsigned). */ + amount: Scalars['Decimal']['input']; + /** ID of the sales tax. */ + salesTaxId: Scalars['ID']['input']; +}; + +/** Input for creating a money transaction. */ +export type MoneyTransactionDetails = { + /** Anchor item. */ + anchor: MoneyTransactionCreateAnchorInput; + /** Date of the transaction. */ + date: Scalars['Date']['input']; + /** Description for the transaction. */ + description: Scalars['String']['input']; + /** ID of the transaction in an external system. If you don't have one, generate a UUID and provide it. */ + externalId: Scalars['String']['input']; + /** Line items. */ + lineItems: Array; + /** Extra notes about the transaction. */ + notes?: InputMaybe; +}; + +/** Input of the `moneyTransactionsCreate` Mutation */ +export type MoneyTransactionsCreateInput = { + /** The unique identifier for the business. */ + businessId: Scalars['ID']['input']; + /** Array of transactions to create. */ + transactions: Array; +}; + +/** Output of the `moneyTransactionsCreate` Mutation */ +export type MoneyTransactionsCreateOutput = { + __typename?: 'MoneyTransactionsCreateOutput'; + /** Whether or not all transactions were successfully created. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Created transactions. */ + transactions?: Maybe>>; +}; + +/** The schema’s entry point for mutations. */ +export type Mutation = { + __typename?: 'Mutation'; + /** No-op placeholder for code generation. */ + _?: Maybe; + /** Archive an account. */ + accountArchive?: Maybe; + /** Create an account. */ + accountCreate?: Maybe; + /** Patch an account. */ + accountPatch?: Maybe; + /** Create a customer. */ + customerCreate?: Maybe; + /** Delete customer. */ + customerDelete?: Maybe; + /** Patch a customer. */ + customerPatch?: Maybe; + /** Approve an invoice. */ + invoiceApprove?: Maybe; + /** Clones an invoice. */ + invoiceClone?: Maybe; + /** Create an invoice. */ + invoiceCreate?: Maybe; + /** Delete an invoice. */ + invoiceDelete?: Maybe; + /** Mark the invoice as sent. */ + invoiceMarkSent?: Maybe; + /** Patch an invoice. */ + invoicePatch?: Maybe; + /** Send an invoice. Requires `Business.emailSendEnabled` to be true. */ + invoiceSend?: Maybe; + /** + * Create a money transaction. + * @deprecated Not available for public use at this time. + */ + moneyDepositTransactionCreate?: Maybe; + /** **BETA**: Create money transaction. Requires `isClassicAccounting` to be `false`. */ + moneyTransactionCreate?: Maybe; + /** **BETA**: Bulk create money transactions. Requires `isClassicAccounting` to be `false`. */ + moneyTransactionsCreate?: Maybe; + /** Archive a product. */ + productArchive?: Maybe; + /** Create a product. */ + productCreate?: Maybe; + /** Patch a product. */ + productPatch?: Maybe; + /** Archive a sales tax. */ + salesTaxArchive: SalesTaxArchiveOutput; + /** Create a sales tax. */ + salesTaxCreate: SalesTaxCreateOutput; + /** Update a sales tax. */ + salesTaxPatch: SalesTaxPatchOutput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationAccountArchiveArgs = { + input: AccountArchiveInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationAccountCreateArgs = { + input: AccountCreateInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationAccountPatchArgs = { + input: AccountPatchInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationCustomerCreateArgs = { + input: CustomerCreateInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationCustomerDeleteArgs = { + input: CustomerDeleteInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationCustomerPatchArgs = { + input: CustomerPatchInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationInvoiceApproveArgs = { + input: InvoiceApproveInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationInvoiceCloneArgs = { + input: InvoiceCloneInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationInvoiceCreateArgs = { + input: InvoiceCreateInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationInvoiceDeleteArgs = { + input: InvoiceDeleteInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationInvoiceMarkSentArgs = { + input: InvoiceMarkSentInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationInvoicePatchArgs = { + input: InvoicePatchInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationInvoiceSendArgs = { + input: InvoiceSendInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationMoneyDepositTransactionCreateArgs = { + input: MoneyDepositTransactionCreateInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationMoneyTransactionCreateArgs = { + input: MoneyTransactionCreateInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationMoneyTransactionsCreateArgs = { + input: MoneyTransactionsCreateInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationProductArchiveArgs = { + input: ProductArchiveInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationProductCreateArgs = { + input: ProductCreateInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationProductPatchArgs = { + input: ProductPatchInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationSalesTaxArchiveArgs = { + input: SalesTaxArchiveInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationSalesTaxCreateArgs = { + input: SalesTaxCreateInput; +}; + + +/** The schema’s entry point for mutations. */ +export type MutationSalesTaxPatchArgs = { + input: SalesTaxPatchInput; +}; + +/** An estimate created in our new platform. */ +export type NewEstimate = BusinessNode & Node & { + __typename?: 'NewEstimate'; + /** Business that the Estimate belongs to */ + business: Business; + /** Unique identifier for the estimate. */ + id: Scalars['ID']['output']; +}; + +/** An object with an `ID`. */ +export type Node = { + /** ID of the object. */ + id: Scalars['ID']['output']; +}; + +/** An OAuth application. */ +export type OAuthApplication = Node & { + __typename?: 'OAuthApplication'; + /** The client identifier issued to the client during the registration process. */ + clientId: Scalars['String']['output']; + /** When the application was created. */ + createdAt: Scalars['DateTime']['output']; + /** A description of the application. */ + description?: Maybe; + /** + * Additional data for the application. + * - If the requested `clientId` does not match that of the current OAuth application, `extraData` will not be returned. + */ + extraData?: Maybe; + /** The unique identifier for the application. */ + id: Scalars['ID']['output']; + /** The URL to the application logo. */ + logoUrl?: Maybe; + /** When the application was last modified. */ + modifiedAt: Scalars['DateTime']['output']; + /** The name of the application. */ + name: Scalars['String']['output']; +}; + +/** Information about pagination in a connection. */ +export type OffsetPageInfo = { + __typename?: 'OffsetPageInfo'; + /** Current page number. */ + currentPage: Scalars['Int']['output']; + /** Total number of nodes in the connection. */ + totalCount?: Maybe; + /** Total number of pages in the connection. */ + totalPages?: Maybe; +}; + +/** Forms of business ownership. */ +export enum OrganizationalType { + /** Corporation */ + Corporation = 'CORPORATION', + /** Partnership */ + Partnership = 'PARTNERSHIP', + /** Sole Proprietorship */ + SoleProprietorship = 'SOLE_PROPRIETORSHIP' +} + +/** A percentage discount applied to an Invoice. */ +export type PercentageInvoiceDiscount = InvoiceDiscount & { + __typename?: 'PercentageInvoiceDiscount'; + /** When the invoice discount was created. */ + createdAt: Scalars['DateTime']['output']; + /** When the invoice discount was last modified. */ + modifiedAt: Scalars['DateTime']['output']; + /** A description of the discount. */ + name?: Maybe; + /** The percentage of the discount. */ + percentage?: Maybe; +}; + +/** Product (or service) that a business sells to a customer or purchases from a vendor. */ +export type Product = BusinessNode & Node & { + __typename?: 'Product'; + /** Business that the product belongs to. */ + business: Business; + /** When the product was created. */ + createdAt: Scalars['DateTime']['output']; + /** Default sales taxes to apply on product. */ + defaultSalesTaxes: Array; + /** Description of the product. */ + description?: Maybe; + /** The expense account to associate with this product, set when isBought. */ + expenseAccount?: Maybe; + /** Unique identifier for the product. */ + id: Scalars['ID']['output']; + /** The income account to associate with this product, set when isSold. */ + incomeAccount?: Maybe; + /** + * The primary key used internally at Wave. + * @deprecated Exposed internal IDs will eventually be removed in favor of global ID. Use Node.id instead. + */ + internalId?: Maybe; + /** Is the product hidden from view by default. */ + isArchived: Scalars['Boolean']['output']; + /** Is product bought by the business. Allow this product or service to be added to Bills. */ + isBought: Scalars['Boolean']['output']; + /** Is product sold by the business. Allow this product or service to be added to Invoices. */ + isSold: Scalars['Boolean']['output']; + /** When the product was last modified. */ + modifiedAt: Scalars['DateTime']['output']; + /** Name of the product. */ + name: Scalars['String']['output']; + /** Price per unit in the major currency unit. */ + unitPrice: Scalars['Decimal']['output']; +}; + +/** Input to the `productArchive` mutation. */ +export type ProductArchiveInput = { + /** The unique identifier for the product. */ + id: Scalars['ID']['input']; +}; + +/** Output of the `productArchive` mutation. */ +export type ProductArchiveOutput = { + __typename?: 'ProductArchiveOutput'; + /** Indicates whether the product was successfully deleted. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Product that was archived. */ + product?: Maybe; +}; + +/** Product connection. */ +export type ProductConnection = { + __typename?: 'ProductConnection'; + /** List of products. */ + edges: Array; + /** Information about pagination. */ + pageInfo: OffsetPageInfo; +}; + +/** Input to the `productCreate` mutation. */ +export type ProductCreateInput = { + /** The unique identifier for the business. */ + businessId: Scalars['ID']['input']; + /** Default sales taxes to apply on product. */ + defaultSalesTaxIds?: InputMaybe>; + /** Product description. */ + description?: InputMaybe; + /** Expense account to associate with this product. Account must be one of subtypes: `EXPENSE`, `COST_OF_GOODS_SOLD`, `PAYMENT_PROCESSING_FEES`, `PAYROLL_EXPENSES`. */ + expenseAccountId?: InputMaybe; + /** Income account to associate with this product. Account must be one of subtypes: `INCOME`, `DISCOUNTS`, `OTHER_INCOME`. */ + incomeAccountId?: InputMaybe; + /** Name of the product. */ + name: Scalars['String']['input']; + /** Price per unit in the major currency unit (rounded to nearest 5 decimal places with ties going away from zero). */ + unitPrice: Scalars['Decimal']['input']; +}; + +/** Output of the `productCreate` mutation. */ +export type ProductCreateOutput = { + __typename?: 'ProductCreateOutput'; + /** Indicates whether the product was successfully created. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Product that was created. */ + product?: Maybe; +}; + +/** Product edge. */ +export type ProductEdge = { + __typename?: 'ProductEdge'; + /** A product. */ + node: Product; +}; + +/** Input to the `productPatch` mutation. */ +export type ProductPatchInput = { + /** Default sales taxes to apply on product. */ + defaultSalesTaxIds?: InputMaybe>; + /** Description of the product. */ + description?: InputMaybe; + /** Expense account to associate with this product. Account must be one of subtypes: `EXPENSE`, `COST_OF_GOODS_SOLD`, `PAYMENT_PROCESSING_FEES`, `PAYROLL_EXPENSES`. */ + expenseAccountId?: InputMaybe; + /** The unique identifier for the product. */ + id: Scalars['ID']['input']; + /** Income account to associate with this product. Account must be one of subtypes: `INCOME`, `DISCOUNTS`, `OTHER_INCOME`. */ + incomeAccountId?: InputMaybe; + /** Name of the product. */ + name?: InputMaybe; + /** Price per unit in the major currency unit (rounded to nearest 5 decimal places with ties going away from zero). */ + unitPrice?: InputMaybe; +}; + +/** Output of the `productPatch` mutation. */ +export type ProductPatchOutput = { + __typename?: 'ProductPatchOutput'; + /** Indicates whether the product was successfully patched. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Product that was updated. */ + product?: Maybe; +}; + +/** Options by which products can be ordered. */ +export enum ProductSort { + /** Ascending by creation time. */ + CreatedAtAsc = 'CREATED_AT_ASC', + /** Descending by creation time. */ + CreatedAtDesc = 'CREATED_AT_DESC', + /** Ascending by modified time. */ + ModifiedAtAsc = 'MODIFIED_AT_ASC', + /** Descending by modified time. */ + ModifiedAtDesc = 'MODIFIED_AT_DESC', + /** Ascending by name. */ + NameAsc = 'NAME_ASC', + /** Descending by name. */ + NameDesc = 'NAME_DESC' +} + +/** A state/county/province/region. */ +export type Province = { + __typename?: 'Province'; + /** [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) identifier. */ + code: Scalars['String']['output']; + /** Plain-lanuage representaton. */ + name: Scalars['String']['output']; + /** + * Informal name for identification. + * @deprecated Nonstandard values. Use code instead. + */ + slug?: Maybe; +}; + +/** The schema’s entry point for queries. */ +export type Query = { + __typename?: 'Query'; + /** No-op placeholder for code generation. */ + _?: Maybe; + /** List subtypes of accounts. */ + accountSubtypes: Array; + /** List types of accounts. */ + accountTypes: Array; + /** Get a business. */ + business?: Maybe; + /** List businesses. */ + businesses?: Maybe; + /** List countries. */ + countries: Array; + /** Get a country. */ + country?: Maybe; + /** List currencies. */ + currencies: Array; + /** Get a currency. */ + currency?: Maybe; + /** Get the current OAuth application. */ + oAuthApplication?: Maybe; + /** Get a province. */ + province?: Maybe; + /** The currently authenticated user. */ + user?: Maybe; +}; + + +/** The schema’s entry point for queries. */ +export type QueryBusinessArgs = { + id?: InputMaybe; +}; + + +/** The schema’s entry point for queries. */ +export type QueryBusinessesArgs = { + isArchived?: InputMaybe; + page?: InputMaybe; + pageSize?: InputMaybe; +}; + + +/** The schema’s entry point for queries. */ +export type QueryCountryArgs = { + code: CountryCode; +}; + + +/** The schema’s entry point for queries. */ +export type QueryCurrencyArgs = { + code: CurrencyCode; +}; + + +/** The schema’s entry point for queries. */ +export type QueryProvinceArgs = { + code: Scalars['String']['input']; +}; + +/** A template that can be used to generate and possibly pay an invoice at regular intervals. */ +export type RecurringInvoice = BusinessNode & Node & { + __typename?: 'RecurringInvoice'; + /** Business that the RecurringInvoice belongs to */ + business: Business; + /** Unique identifier for the recurring invoice. */ + id: Scalars['ID']['output']; + /** + * The primary key used internally at Wave. + * @deprecated Exposed internal IDs will eventually be removed in favor of global ID. Use Node.id instead. + */ + internalId?: Maybe; +}; + +/** A tax paid to a taxing authority for the sales of certain goods and services. */ +export type SalesTax = BusinessNode & Node & { + __typename?: 'SalesTax'; + /** A short form or code representing the sales tax. */ + abbreviation: Scalars['String']['output']; + /** Business that the sales tax belongs to. */ + business: Business; + /** When the sales tax was created. */ + createdAt: Scalars['DateTime']['output']; + /** User defined description for the sales tax. */ + description?: Maybe; + /** The unique identifier for the sales tax. */ + id: Scalars['ID']['output']; + /** + * The primary key used internally at Wave. + * @deprecated Exposed internal IDs will eventually be removed in favor of global ID. Use Node.id instead. + */ + internalId?: Maybe; + /** Is the sales tax hidden from view by default. */ + isArchived: Scalars['Boolean']['output']; + /** Is a compound tax, or stacked tax. This tax is calculated on top of the subtotal and other tax amounts. */ + isCompound: Scalars['Boolean']['output']; + /** Is a recoverable tax. It is recoverable if you can deduct the tax that you as a business paid from the tax that you have collected. */ + isRecoverable: Scalars['Boolean']['output']; + /** When the sales tax was last modified. */ + modifiedAt: Scalars['DateTime']['output']; + /** Name of the tax. */ + name: Scalars['String']['output']; + /** Tax rate effective on 'for' date, or current date if no parameter, as a decimal (e.g. 0.15 represents 15%). */ + rate: Scalars['Decimal']['output']; + /** Tax rates with their effective dates of application */ + rates: Array; + /** Display tax number beside the tax name on an invoice. */ + showTaxNumberOnInvoices: Scalars['Boolean']['output']; + /** The tax's issued identification number from a taxing authority. */ + taxNumber?: Maybe; +}; + + +/** A tax paid to a taxing authority for the sales of certain goods and services. */ +export type SalesTaxRateArgs = { + for?: InputMaybe; +}; + +/** Input to the `salesTaxArchive` mutation. */ +export type SalesTaxArchiveInput = { + /** The unique identifier for the sales tax. */ + id: Scalars['ID']['input']; +}; + +/** Output of the `salesTaxArchive` mutation. */ +export type SalesTaxArchiveOutput = { + __typename?: 'SalesTaxArchiveOutput'; + /** Indicates whether the sales tax was successfully deleted. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Sales tax that was archived. */ + salesTax?: Maybe; +}; + +/** Sales tax connection. */ +export type SalesTaxConnection = { + __typename?: 'SalesTaxConnection'; + /** List of sales taxes. */ + edges: Array; + /** Information about pagination. */ + pageInfo: OffsetPageInfo; +}; + +/** Input to the `salesTaxCreate` mutation. */ +export type SalesTaxCreateInput = { + /** An short form or code representing the sales tax. Max 10 characters, and MUST BE UNIQUE within business. */ + abbreviation: Scalars['String']['input']; + /** The unique identifier for the business. */ + businessId: Scalars['ID']['input']; + /** User defined description for the sales tax. */ + description?: InputMaybe; + /** Is a compound tax, or stacked tax. This tax is calculated on top of the subtotal and other tax amounts. */ + isCompound?: InputMaybe; + /** Is a recoverable tax. It is recoverable if you can deduct the tax that you as a business paid from the tax that you have collected. */ + isRecoverable?: InputMaybe; + /** Name of the tax. */ + name: Scalars['String']['input']; + /** The current rate, as a decimal (e.g. 0.15 represents 15%; rounded to nearest 6 decimal places with ties going away from zero). */ + rate: Scalars['Decimal']['input']; + /** Display tax number beside the tax name on an invoice. */ + showTaxNumberOnInvoices?: InputMaybe; + /** The tax's issued identification number from a taxing authority. */ + taxNumber?: InputMaybe; +}; + +/** Output of the `salesTaxCreate` mutation. */ +export type SalesTaxCreateOutput = { + __typename?: 'SalesTaxCreateOutput'; + /** Indicates whether the sales tax was successfully created. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Sales tax that was created. */ + salesTax?: Maybe; +}; + +/** Sales tax edge. */ +export type SalesTaxEdge = { + __typename?: 'SalesTaxEdge'; + /** A sales tax. */ + node?: Maybe; +}; + +/** Input to the `salesTaxPatch` mutation. */ +export type SalesTaxPatchInput = { + /** An short form or code representing the sales tax */ + abbreviation?: InputMaybe; + /** User defined description for the sales tax. */ + description?: InputMaybe; + /** The unique identifier for the sales tax. */ + id: Scalars['ID']['input']; + /** Name of the tax. */ + name?: InputMaybe; + /** Tax rate information. */ + rates?: InputMaybe>; + /** Display tax number beside the tax name on an invoice. */ + showTaxNumberOnInvoices?: InputMaybe; + /** The tax's issued identification number from a taxing authority. */ + taxNumber?: InputMaybe; +}; + +/** Output of the `salesTaxPatch` mutation. */ +export type SalesTaxPatchOutput = { + __typename?: 'SalesTaxPatchOutput'; + /** Indicates whether the sales tax was successfully patched. */ + didSucceed: Scalars['Boolean']['output']; + /** Mutation validation errors. */ + inputErrors?: Maybe>; + /** Sales tax that was patched. */ + salesTax?: Maybe; +}; + +/** A Sales Tax rate with effective date. New entry for each change of rate. */ +export type SalesTaxRate = { + __typename?: 'SalesTaxRate'; + /** Date from which the sales tax rate applies. */ + effective: Scalars['Date']['output']; + /** Tax rate applying from the effective date as a decimal (e.g. 0.15 represents 15%). */ + rate: Scalars['Decimal']['output']; +}; + +/** Sales tax rate input for the `salesTaxPatch` mutation */ +export type SalesTaxRateInput = { + /** Date from which the sales tax rate applies. */ + effective: Scalars['Date']['input']; + /** Tax rate applying from the effective date as a decimal (e.g. 0.15 represents 15%). */ + rate: Scalars['Decimal']['input']; +}; + +/** Wave's schemas. */ +export enum Schema { + /** Available only to HR Block integration. */ + Hrblock = 'HRBLOCK', + /** Available only to Wave. */ + Internal = 'INTERNAL', + /** Available to all third parties. */ + Public = 'PUBLIC', + /** Available only to Wave staff. */ + Staff = 'STAFF' +} + +/** An created transaction. */ +export type Transaction = { + __typename?: 'Transaction'; + /** Unique identifier for the transaction. */ + id: Scalars['ID']['output']; +}; + +/** Sales tax input. */ +export type TransactionCreateSalesTaxInput = { + /** Tax Abbreviation. */ + abbreviation: Scalars['String']['input']; + /** Tax Amount. */ + amount: Scalars['Float']['input']; +}; + +/** Represents the direction of a transaction. */ +export enum TransactionDirection { + /** To put in. */ + Deposit = 'DEPOSIT', + /** To remove from. */ + Withdrawal = 'WITHDRAWAL' +} + +/** Represents the origin of a transaction. */ +export enum TransactionOrigin { + /** Manually created transaction. */ + Manual = 'MANUAL', + /** Transaction created through Zapier. */ + Zapier = 'ZAPIER' +} + +/** A user is an individual's account. */ +export type User = Node & { + __typename?: 'User'; + /** When the user was created. */ + createdAt: Scalars['DateTime']['output']; + /** The user's primary email address. */ + defaultEmail?: Maybe; + /** The user's first name. */ + firstName?: Maybe; + /** The unique identifier for the user. */ + id: Scalars['ID']['output']; + /** The user's last name. */ + lastName?: Maybe; + /** When the user was last modified. */ + modifiedAt: Scalars['DateTime']['output']; +}; + +/** A vendor of the business. */ +export type Vendor = BusinessNode & Node & { + __typename?: 'Vendor'; + /** The address of the vendor. */ + address?: Maybe
; + /** Business that the vendor belongs to. */ + business: Business; + /** When the vendor was created. */ + createdAt: Scalars['DateTime']['output']; + /** Default currency used by the vendor. */ + currency?: Maybe; + /** User defined id for the vendor. Commonly referred to as Account Number. */ + displayId?: Maybe; + /** Email of the principal vendor. */ + email?: Maybe; + /** Fax number of the vendor. */ + fax?: Maybe; + /** The first name of the principal contact. */ + firstName?: Maybe; + /** Unique identifier for the customer. */ + id: Scalars['ID']['output']; + /** Internal notes about the vendor. */ + internalNotes?: Maybe; + /** Whether or not the vendor is archived. */ + isArchived?: Maybe; + /** The last name of the principal contact. */ + lastName?: Maybe; + /** The mobile number of the vendor. */ + mobile?: Maybe; + /** When the vendor was last modified. */ + modifiedAt: Scalars['DateTime']['output']; + /** Name or business name of the vendor. */ + name: Scalars['String']['output']; + /** The phone number of the vendor. */ + phone?: Maybe; + /** Details for shipping to the vendor. */ + shippingDetails?: Maybe; + /** Toll-free number of the vendor. */ + tollFree?: Maybe; + /** Website address of the vendor. */ + website?: Maybe; +}; + +/** Vendor connection. */ +export type VendorConnection = { + __typename?: 'VendorConnection'; + /** List of vendors. */ + edges: Array; + /** Information about pagination. */ + pageInfo: OffsetPageInfo; +}; + +/** Vendor edge. */ +export type VendorEdge = { + __typename?: 'VendorEdge'; + /** A vendor. */ + node: Vendor; +}; + +/** Shipping details related to a vendor. */ +export type VendorShippingDetails = { + __typename?: 'VendorShippingDetails'; + /** Address of the vendor. */ + address?: Maybe
; + /** Delivery instructions for handling. */ + instructions?: Maybe; + /** Name or business name of the vendor. */ + name?: Maybe; + /** Telephone number of the vendor. */ + phone?: Maybe; +}; + +export type CreateAccountMutationVariables = Exact<{ + input: AccountCreateInput; +}>; + + +export type CreateAccountMutation = { __typename?: 'Mutation', accountCreate?: { __typename?: 'AccountCreateOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, account?: { __typename?: 'Account', id: string, name: string, description?: string | null, displayId?: string | null, type: { __typename?: 'AccountType', name: string, value: AccountTypeValue }, subtype: { __typename?: 'AccountSubtype', name: string, value: AccountSubtypeValue }, currency: { __typename?: 'Currency', code: CurrencyCode } } | null } | null }; + +export type UpdateAccountMutationVariables = Exact<{ + input: AccountPatchInput; +}>; + + +export type UpdateAccountMutation = { __typename?: 'Mutation', accountPatch?: { __typename?: 'AccountPatchOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, account?: { __typename?: 'Account', id: string, name: string, description?: string | null, displayId?: string | null, sequence: number } | null } | null }; + +export type ArchiveAccountMutationVariables = Exact<{ + input: AccountArchiveInput; +}>; + + +export type ArchiveAccountMutation = { __typename?: 'Mutation', accountArchive?: { __typename?: 'AccountArchiveOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null } | null }; + +export type CreateCustomerMutationVariables = Exact<{ + input: CustomerCreateInput; +}>; + + +export type CreateCustomerMutation = { __typename?: 'Mutation', customerCreate?: { __typename?: 'CustomerCreateOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, customer?: { __typename?: 'Customer', id: string, name: string, firstName?: string | null, lastName?: string | null, email?: string | null, phone?: string | null, createdAt: any, currency?: { __typename?: 'Currency', code: CurrencyCode } | null } | null } | null }; + +export type UpdateCustomerMutationVariables = Exact<{ + input: CustomerPatchInput; +}>; + + +export type UpdateCustomerMutation = { __typename?: 'Mutation', customerPatch?: { __typename?: 'CustomerPatchOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, customer?: { __typename?: 'Customer', id: string, name: string, firstName?: string | null, lastName?: string | null, email?: string | null, phone?: string | null, modifiedAt: any } | null } | null }; + +export type DeleteCustomerMutationVariables = Exact<{ + input: CustomerDeleteInput; +}>; + + +export type DeleteCustomerMutation = { __typename?: 'Mutation', customerDelete?: { __typename?: 'CustomerDeleteOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null } | null }; + +export type CreateInvoiceMutationVariables = Exact<{ + input: InvoiceCreateInput; +}>; + + +export type CreateInvoiceMutation = { __typename?: 'Mutation', invoiceCreate?: { __typename?: 'InvoiceCreateOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, invoice?: { __typename?: 'Invoice', id: string, invoiceNumber: string, invoiceDate: any, dueDate: any, status: InvoiceStatus, createdAt: any, total: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode } } } | null } | null }; + +export type UpdateInvoiceMutationVariables = Exact<{ + input: InvoicePatchInput; +}>; + + +export type UpdateInvoiceMutation = { __typename?: 'Mutation', invoicePatch?: { __typename?: 'InvoicePatchOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, invoice?: { __typename?: 'Invoice', id: string, invoiceNumber: string, invoiceDate: any, dueDate: any, status: InvoiceStatus, modifiedAt: any, total: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode } } } | null } | null }; + +export type DeleteInvoiceMutationVariables = Exact<{ + input: InvoiceDeleteInput; +}>; + + +export type DeleteInvoiceMutation = { __typename?: 'Mutation', invoiceDelete?: { __typename?: 'InvoiceDeleteOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null } | null }; + +export type SendInvoiceMutationVariables = Exact<{ + input: InvoiceSendInput; +}>; + + +export type SendInvoiceMutation = { __typename?: 'Mutation', invoiceSend?: { __typename?: 'InvoiceSendOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, invoice?: { __typename?: 'Invoice', id: string, status: InvoiceStatus, lastSentAt?: any | null, lastSentVia?: InvoiceSendMethod | null } | null } | null }; + +export type ApproveInvoiceMutationVariables = Exact<{ + input: InvoiceApproveInput; +}>; + + +export type ApproveInvoiceMutation = { __typename?: 'Mutation', invoiceApprove?: { __typename?: 'InvoiceApproveOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, invoice?: { __typename?: 'Invoice', id: string, status: InvoiceStatus } | null } | null }; + +export type MarkInvoiceSentMutationVariables = Exact<{ + input: InvoiceMarkSentInput; +}>; + + +export type MarkInvoiceSentMutation = { __typename?: 'Mutation', invoiceMarkSent?: { __typename?: 'InvoiceMarkSentOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, invoice?: { __typename?: 'Invoice', id: string, status: InvoiceStatus, lastSentAt?: any | null, lastSentVia?: InvoiceSendMethod | null } | null } | null }; + +export type CloneInvoiceMutationVariables = Exact<{ + input: InvoiceCloneInput; +}>; + + +export type CloneInvoiceMutation = { __typename?: 'Mutation', invoiceClone?: { __typename?: 'InvoiceCloneOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, invoice?: { __typename?: 'Invoice', id: string, invoiceNumber: string, status: InvoiceStatus } | null } | null }; + +export type CreateProductMutationVariables = Exact<{ + input: ProductCreateInput; +}>; + + +export type CreateProductMutation = { __typename?: 'Mutation', productCreate?: { __typename?: 'ProductCreateOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, product?: { __typename?: 'Product', id: string, name: string, description?: string | null, unitPrice: any, isSold: boolean, isBought: boolean, createdAt: any } | null } | null }; + +export type UpdateProductMutationVariables = Exact<{ + input: ProductPatchInput; +}>; + + +export type UpdateProductMutation = { __typename?: 'Mutation', productPatch?: { __typename?: 'ProductPatchOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, product?: { __typename?: 'Product', id: string, name: string, description?: string | null, unitPrice: any, isSold: boolean, isBought: boolean, modifiedAt: any } | null } | null }; + +export type ArchiveProductMutationVariables = Exact<{ + input: ProductArchiveInput; +}>; + + +export type ArchiveProductMutation = { __typename?: 'Mutation', productArchive?: { __typename?: 'ProductArchiveOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, product?: { __typename?: 'Product', id: string, isArchived: boolean } | null } | null }; + +export type CreateSalesTaxMutationVariables = Exact<{ + input: SalesTaxCreateInput; +}>; + + +export type CreateSalesTaxMutation = { __typename?: 'Mutation', salesTaxCreate: { __typename?: 'SalesTaxCreateOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, salesTax?: { __typename?: 'SalesTax', id: string, name: string, abbreviation: string, rate: any, isCompound: boolean, isRecoverable: boolean, createdAt: any } | null } }; + +export type UpdateSalesTaxMutationVariables = Exact<{ + input: SalesTaxPatchInput; +}>; + + +export type UpdateSalesTaxMutation = { __typename?: 'Mutation', salesTaxPatch: { __typename?: 'SalesTaxPatchOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, salesTax?: { __typename?: 'SalesTax', id: string, name: string, abbreviation: string, rate: any, modifiedAt: any } | null } }; + +export type ArchiveSalesTaxMutationVariables = Exact<{ + input: SalesTaxArchiveInput; +}>; + + +export type ArchiveSalesTaxMutation = { __typename?: 'Mutation', salesTaxArchive: { __typename?: 'SalesTaxArchiveOutput', didSucceed: boolean, inputErrors?: Array<{ __typename?: 'InputError', path?: Array | null, message?: string | null, code?: string | null }> | null, salesTax?: { __typename?: 'SalesTax', id: string, isArchived: boolean } | null } }; + +export type GetAccountsQueryVariables = Exact<{ + businessId: Scalars['ID']['input']; + page?: InputMaybe; + pageSize?: InputMaybe; + types?: InputMaybe | AccountTypeValue>; + subtypes?: InputMaybe | AccountSubtypeValue>; + isArchived?: InputMaybe; +}>; + + +export type GetAccountsQuery = { __typename?: 'Query', business?: { __typename?: 'Business', id: string, accounts?: { __typename?: 'AccountConnection', edges: Array<{ __typename?: 'AccountEdge', node?: { __typename?: 'Account', id: string, name: string, description?: string | null, displayId?: string | null, normalBalanceType: AccountNormalBalanceType, balance?: any | null, balanceInBusinessCurrency?: any | null, isArchived: boolean, type: { __typename?: 'AccountType', name: string, value: AccountTypeValue }, subtype: { __typename?: 'AccountSubtype', name: string, value: AccountSubtypeValue }, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string } } | null }>, pageInfo: { __typename?: 'OffsetPageInfo', currentPage: number, totalPages?: number | null, totalCount?: number | null } } | null } | null }; + +export type GetAccountQueryVariables = Exact<{ + businessId: Scalars['ID']['input']; + accountId: Scalars['ID']['input']; +}>; + + +export type GetAccountQuery = { __typename?: 'Query', business?: { __typename?: 'Business', id: string, account?: { __typename?: 'Account', id: string, name: string, description?: string | null, displayId?: string | null, normalBalanceType: AccountNormalBalanceType, balance?: any | null, balanceInBusinessCurrency?: any | null, isArchived: boolean, sequence: number, type: { __typename?: 'AccountType', name: string, value: AccountTypeValue, normalBalanceType: AccountNormalBalanceType }, subtype: { __typename?: 'AccountSubtype', name: string, value: AccountSubtypeValue, archivable: boolean, systemCreated: boolean, description?: string | null }, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string, name: string } } | null } | null }; + +export type GetAccountTypesQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetAccountTypesQuery = { __typename?: 'Query', accountTypes: Array<{ __typename?: 'AccountType', name: string, value: AccountTypeValue, normalBalanceType: AccountNormalBalanceType }> }; + +export type GetAccountSubtypesQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetAccountSubtypesQuery = { __typename?: 'Query', accountSubtypes: Array<{ __typename?: 'AccountSubtype', name: string, value: AccountSubtypeValue, archivable: boolean, systemCreated: boolean, description?: string | null, type: { __typename?: 'AccountType', name: string, value: AccountTypeValue } }> }; + +export type GetBusinessQueryVariables = Exact<{ + id?: InputMaybe; +}>; + + +export type GetBusinessQuery = { __typename?: 'Query', business?: { __typename?: 'Business', id: string, name: string, isPersonal: boolean, timezone?: string | null, phone?: string | null, website?: string | null, isArchived: boolean, createdAt: any, modifiedAt: any, emailSendEnabled: boolean, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string, name: string }, address?: { __typename?: 'Address', addressLine1?: string | null, addressLine2?: string | null, city?: string | null, postalCode?: string | null, province?: { __typename?: 'Province', code: string, name: string } | null, country?: { __typename?: 'Country', code: CountryCode, name: string } | null } | null } | null }; + +export type ListBusinessesQueryVariables = Exact<{ + page?: InputMaybe; + pageSize?: InputMaybe; +}>; + + +export type ListBusinessesQuery = { __typename?: 'Query', businesses?: { __typename?: 'BusinessConnection', edges: Array<{ __typename?: 'BusinessEdge', node?: { __typename?: 'Business', id: string, name: string, isPersonal: boolean, isArchived: boolean, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string } } | null }>, pageInfo: { __typename?: 'OffsetPageInfo', currentPage: number, totalPages?: number | null, totalCount?: number | null } } | null }; + +export type GetCustomersQueryVariables = Exact<{ + businessId: Scalars['ID']['input']; + page?: InputMaybe; + pageSize?: InputMaybe; + sort?: Array | CustomerSort; +}>; + + +export type GetCustomersQuery = { __typename?: 'Query', business?: { __typename?: 'Business', id: string, customers?: { __typename?: 'CustomerConnection', edges: Array<{ __typename?: 'CustomerEdge', node?: { __typename?: 'Customer', id: string, name: string, firstName?: string | null, lastName?: string | null, email?: string | null, phone?: string | null, mobile?: string | null, createdAt: any, modifiedAt: any, isArchived?: boolean | null, currency?: { __typename?: 'Currency', code: CurrencyCode, symbol: string } | null, address?: { __typename?: 'Address', addressLine1?: string | null, addressLine2?: string | null, city?: string | null, postalCode?: string | null, province?: { __typename?: 'Province', code: string, name: string } | null, country?: { __typename?: 'Country', code: CountryCode, name: string } | null } | null, outstandingAmount: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode } }, overdueAmount: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode } } } | null }>, pageInfo: { __typename?: 'OffsetPageInfo', currentPage: number, totalPages?: number | null, totalCount?: number | null } } | null } | null }; + +export type GetCustomerQueryVariables = Exact<{ + businessId: Scalars['ID']['input']; + customerId: Scalars['ID']['input']; +}>; + + +export type GetCustomerQuery = { __typename?: 'Query', business?: { __typename?: 'Business', id: string, customer?: { __typename?: 'Customer', id: string, name: string, firstName?: string | null, lastName?: string | null, displayId?: string | null, email?: string | null, phone?: string | null, mobile?: string | null, fax?: string | null, tollFree?: string | null, website?: string | null, internalNotes?: string | null, createdAt: any, modifiedAt: any, isArchived?: boolean | null, currency?: { __typename?: 'Currency', code: CurrencyCode, symbol: string, name: string } | null, address?: { __typename?: 'Address', addressLine1?: string | null, addressLine2?: string | null, city?: string | null, postalCode?: string | null, province?: { __typename?: 'Province', code: string, name: string } | null, country?: { __typename?: 'Country', code: CountryCode, name: string } | null } | null, shippingDetails?: { __typename?: 'CustomerShippingDetails', name?: string | null, phone?: string | null, instructions?: string | null, address?: { __typename?: 'Address', addressLine1?: string | null, addressLine2?: string | null, city?: string | null, postalCode?: string | null, province?: { __typename?: 'Province', code: string, name: string } | null, country?: { __typename?: 'Country', code: CountryCode, name: string } | null } | null } | null, outstandingAmount: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode } }, overdueAmount: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode } } } | null } | null }; + +export type GetInvoicesQueryVariables = Exact<{ + businessId: Scalars['ID']['input']; + page?: InputMaybe; + pageSize?: InputMaybe; + sort?: Array | InvoiceSort; + status?: InputMaybe; + customerId?: InputMaybe; +}>; + + +export type GetInvoicesQuery = { __typename?: 'Query', business?: { __typename?: 'Business', id: string, invoices?: { __typename?: 'InvoiceConnection', edges: Array<{ __typename?: 'InvoiceEdge', node: { __typename?: 'Invoice', id: string, invoiceNumber: string, invoiceDate: any, dueDate: any, status: InvoiceStatus, title: string, createdAt: any, modifiedAt: any, lastSentAt?: any | null, lastViewedAt?: any | null, customer: { __typename?: 'Customer', id: string, name: string, email?: string | null }, amountDue: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string } }, amountPaid: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string } }, total: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string } }, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string } } }>, pageInfo: { __typename?: 'OffsetPageInfo', currentPage: number, totalPages?: number | null, totalCount?: number | null } } | null } | null }; + +export type GetInvoiceQueryVariables = Exact<{ + businessId: Scalars['ID']['input']; + invoiceId: Scalars['ID']['input']; +}>; + + +export type GetInvoiceQuery = { __typename?: 'Query', business?: { __typename?: 'Business', id: string, invoice?: { __typename?: 'Invoice', id: string, invoiceNumber: string, invoiceDate: any, dueDate: any, status: InvoiceStatus, title: string, subhead?: string | null, poNumber?: string | null, memo?: string | null, footer?: string | null, pdfUrl: string, viewUrl: string, exchangeRate: any, createdAt: any, modifiedAt: any, lastSentAt?: any | null, lastSentVia?: InvoiceSendMethod | null, lastViewedAt?: any | null, customer: { __typename?: 'Customer', id: string, name: string, firstName?: string | null, lastName?: string | null, email?: string | null, address?: { __typename?: 'Address', addressLine1?: string | null, addressLine2?: string | null, city?: string | null, postalCode?: string | null, province?: { __typename?: 'Province', code: string, name: string } | null, country?: { __typename?: 'Country', code: CountryCode, name: string } | null } | null }, items?: Array<{ __typename?: 'InvoiceItem', description?: string | null, quantity: any, unitPrice: any, product: { __typename?: 'Product', id: string, name: string }, subtotal: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode } }, total: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode } }, taxes: Array<{ __typename?: 'InvoiceItemTax', salesTax: { __typename?: 'SalesTax', id: string, name: string, abbreviation: string }, amount?: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode } } | null }> }> | null, discounts?: Array< + | { __typename?: 'FixedInvoiceDiscount', name?: string | null, amount?: any | null } + | { __typename?: 'PercentageInvoiceDiscount', name?: string | null, percentage?: any | null } + > | null, subtotal: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string } }, taxTotal: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string } }, discountTotal: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string } }, total: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string } }, amountDue: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string } }, amountPaid: { __typename?: 'Money', value: string, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string } }, currency: { __typename?: 'Currency', code: CurrencyCode, symbol: string, name: string } } | null } | null }; + +export type GetProductsQueryVariables = Exact<{ + businessId: Scalars['ID']['input']; + page?: InputMaybe; + pageSize?: InputMaybe; + sort?: Array | ProductSort; + isSold?: InputMaybe; + isBought?: InputMaybe; + isArchived?: InputMaybe; +}>; + + +export type GetProductsQuery = { __typename?: 'Query', business?: { __typename?: 'Business', id: string, products?: { __typename?: 'ProductConnection', edges: Array<{ __typename?: 'ProductEdge', node: { __typename?: 'Product', id: string, name: string, description?: string | null, unitPrice: any, isSold: boolean, isBought: boolean, isArchived: boolean, createdAt: any, modifiedAt: any, incomeAccount?: { __typename?: 'Account', id: string, name: string } | null, expenseAccount?: { __typename?: 'Account', id: string, name: string } | null, defaultSalesTaxes: Array<{ __typename?: 'SalesTax', id: string, name: string, abbreviation: string }> } }>, pageInfo: { __typename?: 'OffsetPageInfo', currentPage: number, totalPages?: number | null, totalCount?: number | null } } | null } | null }; + +export type GetProductQueryVariables = Exact<{ + businessId: Scalars['ID']['input']; + productId: Scalars['ID']['input']; +}>; + + +export type GetProductQuery = { __typename?: 'Query', business?: { __typename?: 'Business', id: string, product?: { __typename?: 'Product', id: string, name: string, description?: string | null, unitPrice: any, isSold: boolean, isBought: boolean, isArchived: boolean, createdAt: any, modifiedAt: any, incomeAccount?: { __typename?: 'Account', id: string, name: string, type: { __typename?: 'AccountType', name: string, value: AccountTypeValue }, subtype: { __typename?: 'AccountSubtype', name: string, value: AccountSubtypeValue } } | null, expenseAccount?: { __typename?: 'Account', id: string, name: string, type: { __typename?: 'AccountType', name: string, value: AccountTypeValue }, subtype: { __typename?: 'AccountSubtype', name: string, value: AccountSubtypeValue } } | null, defaultSalesTaxes: Array<{ __typename?: 'SalesTax', id: string, name: string, abbreviation: string, rate: any }> } | null } | null }; + +export type GetSalesTaxesQueryVariables = Exact<{ + businessId: Scalars['ID']['input']; + page?: InputMaybe; + pageSize?: InputMaybe; + isArchived?: InputMaybe; +}>; + + +export type GetSalesTaxesQuery = { __typename?: 'Query', business?: { __typename?: 'Business', id: string, salesTaxes?: { __typename?: 'SalesTaxConnection', edges: Array<{ __typename?: 'SalesTaxEdge', node?: { __typename?: 'SalesTax', id: string, name: string, abbreviation: string, description?: string | null, taxNumber?: string | null, showTaxNumberOnInvoices: boolean, rate: any, isCompound: boolean, isRecoverable: boolean, isArchived: boolean, createdAt: any, modifiedAt: any, rates: Array<{ __typename?: 'SalesTaxRate', effective: any, rate: any }> } | null }>, pageInfo: { __typename?: 'OffsetPageInfo', currentPage: number, totalPages?: number | null, totalCount?: number | null } } | null } | null }; + +export type GetSalesTaxQueryVariables = Exact<{ + businessId: Scalars['ID']['input']; + salesTaxId: Scalars['ID']['input']; +}>; + + +export type GetSalesTaxQuery = { __typename?: 'Query', business?: { __typename?: 'Business', id: string, salesTax?: { __typename?: 'SalesTax', id: string, name: string, abbreviation: string, description?: string | null, taxNumber?: string | null, showTaxNumberOnInvoices: boolean, rate: any, isCompound: boolean, isRecoverable: boolean, isArchived: boolean, createdAt: any, modifiedAt: any, rates: Array<{ __typename?: 'SalesTaxRate', effective: any, rate: any }> } | null } | null }; + + +export const CreateAccountDocument = gql` + mutation CreateAccount($input: AccountCreateInput!) { + accountCreate(input: $input) { + didSucceed + inputErrors { + path + message + code + } + account { + id + name + description + displayId + type { + name + value + } + subtype { + name + value + } + currency { + code + } + } + } +} + `; +export const UpdateAccountDocument = gql` + mutation UpdateAccount($input: AccountPatchInput!) { + accountPatch(input: $input) { + didSucceed + inputErrors { + path + message + code + } + account { + id + name + description + displayId + sequence + } + } +} + `; +export const ArchiveAccountDocument = gql` + mutation ArchiveAccount($input: AccountArchiveInput!) { + accountArchive(input: $input) { + didSucceed + inputErrors { + path + message + code + } + } +} + `; +export const CreateCustomerDocument = gql` + mutation CreateCustomer($input: CustomerCreateInput!) { + customerCreate(input: $input) { + didSucceed + inputErrors { + path + message + code + } + customer { + id + name + firstName + lastName + email + phone + currency { + code + } + createdAt + } + } +} + `; +export const UpdateCustomerDocument = gql` + mutation UpdateCustomer($input: CustomerPatchInput!) { + customerPatch(input: $input) { + didSucceed + inputErrors { + path + message + code + } + customer { + id + name + firstName + lastName + email + phone + modifiedAt + } + } +} + `; +export const DeleteCustomerDocument = gql` + mutation DeleteCustomer($input: CustomerDeleteInput!) { + customerDelete(input: $input) { + didSucceed + inputErrors { + path + message + code + } + } +} + `; +export const CreateInvoiceDocument = gql` + mutation CreateInvoice($input: InvoiceCreateInput!) { + invoiceCreate(input: $input) { + didSucceed + inputErrors { + path + message + code + } + invoice { + id + invoiceNumber + invoiceDate + dueDate + status + total { + value + currency { + code + } + } + createdAt + } + } +} + `; +export const UpdateInvoiceDocument = gql` + mutation UpdateInvoice($input: InvoicePatchInput!) { + invoicePatch(input: $input) { + didSucceed + inputErrors { + path + message + code + } + invoice { + id + invoiceNumber + invoiceDate + dueDate + status + total { + value + currency { + code + } + } + modifiedAt + } + } +} + `; +export const DeleteInvoiceDocument = gql` + mutation DeleteInvoice($input: InvoiceDeleteInput!) { + invoiceDelete(input: $input) { + didSucceed + inputErrors { + path + message + code + } + } +} + `; +export const SendInvoiceDocument = gql` + mutation SendInvoice($input: InvoiceSendInput!) { + invoiceSend(input: $input) { + didSucceed + inputErrors { + path + message + code + } + invoice { + id + status + lastSentAt + lastSentVia + } + } +} + `; +export const ApproveInvoiceDocument = gql` + mutation ApproveInvoice($input: InvoiceApproveInput!) { + invoiceApprove(input: $input) { + didSucceed + inputErrors { + path + message + code + } + invoice { + id + status + } + } +} + `; +export const MarkInvoiceSentDocument = gql` + mutation MarkInvoiceSent($input: InvoiceMarkSentInput!) { + invoiceMarkSent(input: $input) { + didSucceed + inputErrors { + path + message + code + } + invoice { + id + status + lastSentAt + lastSentVia + } + } +} + `; +export const CloneInvoiceDocument = gql` + mutation CloneInvoice($input: InvoiceCloneInput!) { + invoiceClone(input: $input) { + didSucceed + inputErrors { + path + message + code + } + invoice { + id + invoiceNumber + status + } + } +} + `; +export const CreateProductDocument = gql` + mutation CreateProduct($input: ProductCreateInput!) { + productCreate(input: $input) { + didSucceed + inputErrors { + path + message + code + } + product { + id + name + description + unitPrice + isSold + isBought + createdAt + } + } +} + `; +export const UpdateProductDocument = gql` + mutation UpdateProduct($input: ProductPatchInput!) { + productPatch(input: $input) { + didSucceed + inputErrors { + path + message + code + } + product { + id + name + description + unitPrice + isSold + isBought + modifiedAt + } + } +} + `; +export const ArchiveProductDocument = gql` + mutation ArchiveProduct($input: ProductArchiveInput!) { + productArchive(input: $input) { + didSucceed + inputErrors { + path + message + code + } + product { + id + isArchived + } + } +} + `; +export const CreateSalesTaxDocument = gql` + mutation CreateSalesTax($input: SalesTaxCreateInput!) { + salesTaxCreate(input: $input) { + didSucceed + inputErrors { + path + message + code + } + salesTax { + id + name + abbreviation + rate + isCompound + isRecoverable + createdAt + } + } +} + `; +export const UpdateSalesTaxDocument = gql` + mutation UpdateSalesTax($input: SalesTaxPatchInput!) { + salesTaxPatch(input: $input) { + didSucceed + inputErrors { + path + message + code + } + salesTax { + id + name + abbreviation + rate + modifiedAt + } + } +} + `; +export const ArchiveSalesTaxDocument = gql` + mutation ArchiveSalesTax($input: SalesTaxArchiveInput!) { + salesTaxArchive(input: $input) { + didSucceed + inputErrors { + path + message + code + } + salesTax { + id + isArchived + } + } +} + `; +export const GetAccountsDocument = gql` + query GetAccounts($businessId: ID!, $page: Int, $pageSize: Int, $types: [AccountTypeValue!], $subtypes: [AccountSubtypeValue!], $isArchived: Boolean) { + business(id: $businessId) { + id + accounts( + page: $page + pageSize: $pageSize + types: $types + subtypes: $subtypes + isArchived: $isArchived + ) { + edges { + node { + id + name + description + displayId + type { + name + value + } + subtype { + name + value + } + normalBalanceType + currency { + code + symbol + } + balance + balanceInBusinessCurrency + isArchived + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + } +} + `; +export const GetAccountDocument = gql` + query GetAccount($businessId: ID!, $accountId: ID!) { + business(id: $businessId) { + id + account(id: $accountId) { + id + name + description + displayId + type { + name + value + normalBalanceType + } + subtype { + name + value + archivable + systemCreated + description + } + normalBalanceType + currency { + code + symbol + name + } + balance + balanceInBusinessCurrency + isArchived + sequence + } + } +} + `; +export const GetAccountTypesDocument = gql` + query GetAccountTypes { + accountTypes { + name + value + normalBalanceType + } +} + `; +export const GetAccountSubtypesDocument = gql` + query GetAccountSubtypes { + accountSubtypes { + name + value + type { + name + value + } + archivable + systemCreated + description + } +} + `; +export const GetBusinessDocument = gql` + query GetBusiness($id: ID) { + business(id: $id) { + id + name + isPersonal + currency { + code + symbol + name + } + timezone + address { + addressLine1 + addressLine2 + city + province { + code + name + } + country { + code + name + } + postalCode + } + phone + website + isArchived + createdAt + modifiedAt + emailSendEnabled + } +} + `; +export const ListBusinessesDocument = gql` + query ListBusinesses($page: Int, $pageSize: Int) { + businesses(page: $page, pageSize: $pageSize) { + edges { + node { + id + name + isPersonal + currency { + code + symbol + } + isArchived + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } +} + `; +export const GetCustomersDocument = gql` + query GetCustomers($businessId: ID!, $page: Int, $pageSize: Int, $sort: [CustomerSort!]! = [NAME_ASC]) { + business(id: $businessId) { + id + customers(page: $page, pageSize: $pageSize, sort: $sort) { + edges { + node { + id + name + firstName + lastName + email + phone + mobile + currency { + code + symbol + } + address { + addressLine1 + addressLine2 + city + province { + code + name + } + country { + code + name + } + postalCode + } + outstandingAmount { + value + currency { + code + } + } + overdueAmount { + value + currency { + code + } + } + createdAt + modifiedAt + isArchived + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + } +} + `; +export const GetCustomerDocument = gql` + query GetCustomer($businessId: ID!, $customerId: ID!) { + business(id: $businessId) { + id + customer(id: $customerId) { + id + name + firstName + lastName + displayId + email + phone + mobile + fax + tollFree + website + internalNotes + currency { + code + symbol + name + } + address { + addressLine1 + addressLine2 + city + province { + code + name + } + country { + code + name + } + postalCode + } + shippingDetails { + name + phone + instructions + address { + addressLine1 + addressLine2 + city + province { + code + name + } + country { + code + name + } + postalCode + } + } + outstandingAmount { + value + currency { + code + } + } + overdueAmount { + value + currency { + code + } + } + createdAt + modifiedAt + isArchived + } + } +} + `; +export const GetInvoicesDocument = gql` + query GetInvoices($businessId: ID!, $page: Int, $pageSize: Int, $sort: [InvoiceSort!]! = [INVOICE_DATE_DESC], $status: InvoiceStatus, $customerId: ID) { + business(id: $businessId) { + id + invoices( + page: $page + pageSize: $pageSize + sort: $sort + status: $status + customerId: $customerId + ) { + edges { + node { + id + invoiceNumber + invoiceDate + dueDate + status + title + customer { + id + name + email + } + amountDue { + value + currency { + code + symbol + } + } + amountPaid { + value + currency { + code + symbol + } + } + total { + value + currency { + code + symbol + } + } + currency { + code + symbol + } + createdAt + modifiedAt + lastSentAt + lastViewedAt + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + } +} + `; +export const GetInvoiceDocument = gql` + query GetInvoice($businessId: ID!, $invoiceId: ID!) { + business(id: $businessId) { + id + invoice(id: $invoiceId) { + id + invoiceNumber + invoiceDate + dueDate + status + title + subhead + poNumber + memo + footer + pdfUrl + viewUrl + customer { + id + name + firstName + lastName + email + address { + addressLine1 + addressLine2 + city + province { + code + name + } + country { + code + name + } + postalCode + } + } + items { + product { + id + name + } + description + quantity + unitPrice + subtotal { + value + currency { + code + } + } + total { + value + currency { + code + } + } + taxes { + salesTax { + id + name + abbreviation + } + amount { + value + currency { + code + } + } + } + } + discounts { + ... on FixedInvoiceDiscount { + name + amount + } + ... on PercentageInvoiceDiscount { + name + percentage + } + } + subtotal { + value + currency { + code + symbol + } + } + taxTotal { + value + currency { + code + symbol + } + } + discountTotal { + value + currency { + code + symbol + } + } + total { + value + currency { + code + symbol + } + } + amountDue { + value + currency { + code + symbol + } + } + amountPaid { + value + currency { + code + symbol + } + } + currency { + code + symbol + name + } + exchangeRate + createdAt + modifiedAt + lastSentAt + lastSentVia + lastViewedAt + } + } +} + `; +export const GetProductsDocument = gql` + query GetProducts($businessId: ID!, $page: Int, $pageSize: Int, $sort: [ProductSort!]! = [NAME_ASC], $isSold: Boolean, $isBought: Boolean, $isArchived: Boolean) { + business(id: $businessId) { + id + products( + page: $page + pageSize: $pageSize + sort: $sort + isSold: $isSold + isBought: $isBought + isArchived: $isArchived + ) { + edges { + node { + id + name + description + unitPrice + isSold + isBought + isArchived + incomeAccount { + id + name + } + expenseAccount { + id + name + } + defaultSalesTaxes { + id + name + abbreviation + } + createdAt + modifiedAt + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + } +} + `; +export const GetProductDocument = gql` + query GetProduct($businessId: ID!, $productId: ID!) { + business(id: $businessId) { + id + product(id: $productId) { + id + name + description + unitPrice + isSold + isBought + isArchived + incomeAccount { + id + name + type { + name + value + } + subtype { + name + value + } + } + expenseAccount { + id + name + type { + name + value + } + subtype { + name + value + } + } + defaultSalesTaxes { + id + name + abbreviation + rate + } + createdAt + modifiedAt + } + } +} + `; +export const GetSalesTaxesDocument = gql` + query GetSalesTaxes($businessId: ID!, $page: Int, $pageSize: Int, $isArchived: Boolean) { + business(id: $businessId) { + id + salesTaxes(page: $page, pageSize: $pageSize, isArchived: $isArchived) { + edges { + node { + id + name + abbreviation + description + taxNumber + showTaxNumberOnInvoices + rate + rates { + effective + rate + } + isCompound + isRecoverable + isArchived + createdAt + modifiedAt + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + } +} + `; +export const GetSalesTaxDocument = gql` + query GetSalesTax($businessId: ID!, $salesTaxId: ID!) { + business(id: $businessId) { + id + salesTax(id: $salesTaxId) { + id + name + abbreviation + description + taxNumber + showTaxNumberOnInvoices + rate + rates { + effective + rate + } + isCompound + isRecoverable + isArchived + createdAt + modifiedAt + } + } +} + `; \ No newline at end of file diff --git a/src/lib/graphql/wave/index.ts b/src/lib/graphql/wave/index.ts new file mode 100644 index 0000000..02d2e75 --- /dev/null +++ b/src/lib/graphql/wave/index.ts @@ -0,0 +1,5 @@ +// Wave GraphQL Client +export { createWaveServerClient } from './client'; + +// Re-export all generated types and documents +export * from './generated'; diff --git a/src/lib/graphql/wave/mutations.ts b/src/lib/graphql/wave/mutations.ts new file mode 100644 index 0000000..76be8fa --- /dev/null +++ b/src/lib/graphql/wave/mutations.ts @@ -0,0 +1,594 @@ +import { WAVE_ACCESS_TOKEN } from '$env/static/private'; +import { PUBLIC_WAVE_BUSINESS_ID } from '$env/static/public'; +import { createWaveServerClient } from './client'; +import { + CreateInvoiceDocument, + DeleteInvoiceDocument, + SendInvoiceDocument, + UpdateInvoiceDocument, + ApproveInvoiceDocument, + CreateProductDocument, + UpdateProductDocument, + ArchiveProductDocument, + CreateCustomerDocument, + UpdateCustomerDocument, + DeleteCustomerDocument +} from './generated'; + +export interface WaveInvoiceItem { + productId: string; + description?: string; + quantity: string; + unitPrice: string; +} + +export interface WaveInvoiceDiscount { + discountType: 'PERCENTAGE' | 'FIXED'; + name?: string; + percentage?: string; + amount?: string; +} + +export interface CreateWaveInvoiceInput { + businessId: string; + customerId: string; + status?: 'DRAFT' | 'SAVED' | 'SENT'; + invoiceDate?: string; + dueDate?: string; + items: WaveInvoiceItem[]; + discounts?: WaveInvoiceDiscount[]; + memo?: string; + footer?: string; +} + +export interface WaveInvoiceResult { + success: boolean; + invoice?: { + id: string; + invoiceNumber: string; + status: string; + pdfUrl: string; + viewUrl: string; + }; + error?: string; +} + +export async function createWaveInvoice(input: CreateWaveInvoiceInput): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.mutation(CreateInvoiceDocument, { + input: { + businessId: input.businessId, + customerId: input.customerId, + status: input.status || 'DRAFT', + invoiceDate: input.invoiceDate, + dueDate: input.dueDate, + items: input.items, + discounts: input.discounts, + memo: input.memo, + footer: input.footer + } + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return { success: false, error: result.error.message || 'Failed to create Wave invoice' }; + } + + const invoiceCreate = result.data?.invoiceCreate; + + if (!invoiceCreate?.didSucceed) { + const errorMessages = invoiceCreate?.inputErrors + ?.map( + (e: { path?: string[] | null; message?: string | null }) => + `${e.path?.join('.')}: ${e.message}` + ) + .join(', '); + return { success: false, error: errorMessages || 'Failed to create Wave invoice' }; + } + + return { + success: true, + invoice: invoiceCreate.invoice + ? { + id: invoiceCreate.invoice.id, + invoiceNumber: invoiceCreate.invoice.invoiceNumber, + status: invoiceCreate.invoice.status, + pdfUrl: invoiceCreate.invoice.pdfUrl, + viewUrl: invoiceCreate.invoice.viewUrl + } + : undefined + }; +} + +export interface UpdateWaveInvoiceInput { + id: string; + invoiceDate?: string; + dueDate?: string; + items?: WaveInvoiceItem[]; + discounts?: WaveInvoiceDiscount[]; + memo?: string; + footer?: string; +} + +export async function updateWaveInvoice(input: UpdateWaveInvoiceInput): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.mutation(UpdateInvoiceDocument, { + input: { + id: input.id, + invoiceDate: input.invoiceDate, + dueDate: input.dueDate, + items: input.items, + discounts: input.discounts, + memo: input.memo, + footer: input.footer + } + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return { success: false, error: result.error.message || 'Failed to update Wave invoice' }; + } + + const invoicePatch = result.data?.invoicePatch; + + if (!invoicePatch?.didSucceed) { + const errorMessages = invoicePatch?.inputErrors + ?.map( + (e: { path?: string[] | null; message?: string | null }) => + `${e.path?.join('.')}: ${e.message}` + ) + .join(', '); + return { success: false, error: errorMessages || 'Failed to update Wave invoice' }; + } + + return { + success: true, + invoice: invoicePatch.invoice + ? { + id: invoicePatch.invoice.id, + invoiceNumber: invoicePatch.invoice.invoiceNumber, + status: invoicePatch.invoice.status, + pdfUrl: '', + viewUrl: '' + } + : undefined + }; +} + +export interface DeleteWaveInvoiceResult { + success: boolean; + error?: string; +} + +export async function deleteWaveInvoice(invoiceId: string): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.mutation(DeleteInvoiceDocument, { + input: { invoiceId } + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return { success: false, error: result.error.message || 'Failed to delete Wave invoice' }; + } + + const invoiceDelete = result.data?.invoiceDelete; + + if (!invoiceDelete?.didSucceed) { + const errorMessages = invoiceDelete?.inputErrors + ?.map( + (e: { path?: string[] | null; message?: string | null }) => + `${e.path?.join('.')}: ${e.message}` + ) + .join(', '); + return { success: false, error: errorMessages || 'Failed to delete Wave invoice' }; + } + + return { success: true }; +} + +export interface ApproveWaveInvoiceResult { + success: boolean; + error?: string; +} + +export async function approveWaveInvoice(invoiceId: string): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.mutation(ApproveInvoiceDocument, { + input: { invoiceId } + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return { success: false, error: result.error.message || 'Failed to approve Wave invoice' }; + } + + const invoiceApprove = result.data?.invoiceApprove; + + if (!invoiceApprove?.didSucceed) { + const errorMessages = invoiceApprove?.inputErrors + ?.map( + (e: { path?: string[] | null; message?: string | null }) => + `${e.path?.join('.')}: ${e.message}` + ) + .join(', '); + return { success: false, error: errorMessages || 'Failed to approve Wave invoice' }; + } + + return { success: true }; +} + +export async function sendWaveInvoice(invoiceId: string, to: string[]): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.mutation(SendInvoiceDocument, { + input: { invoiceId, to, attachPDF: true } + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return { success: false, error: result.error.message || 'Failed to send Wave invoice' }; + } + + const invoiceSend = result.data?.invoiceSend; + + if (!invoiceSend?.didSucceed) { + const errorMessages = invoiceSend?.inputErrors + ?.map( + (e: { path?: string[] | null; message?: string | null }) => + `${e.path?.join('.')}: ${e.message}` + ) + .join(', '); + return { success: false, error: errorMessages || 'Failed to send Wave invoice' }; + } + + return { + success: true, + invoice: invoiceSend.invoice + ? { + id: invoiceSend.invoice.id, + invoiceNumber: invoiceSend.invoice.invoiceNumber, + status: invoiceSend.invoice.status, + pdfUrl: invoiceSend.invoice.pdfUrl, + viewUrl: invoiceSend.invoice.viewUrl + } + : undefined + }; +} + +// ===================== +// Product Mutations +// ===================== + +export interface CreateWaveProductInput { + name: string; + unitPrice: number; + description?: string; + incomeAccountId?: string; + expenseAccountId?: string; + defaultSalesTaxIds?: string[]; +} + +export interface WaveProductResult { + success: boolean; + product?: { + id: string; + name: string; + description: string | null; + unitPrice: number; + }; + error?: string; +} + +export async function createWaveProduct(input: CreateWaveProductInput): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.mutation(CreateProductDocument, { + input: { + businessId: PUBLIC_WAVE_BUSINESS_ID, + name: input.name, + unitPrice: input.unitPrice, + description: input.description, + incomeAccountId: input.incomeAccountId, + expenseAccountId: input.expenseAccountId, + defaultSalesTaxIds: input.defaultSalesTaxIds + } + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return { success: false, error: result.error.message || 'Failed to create product' }; + } + + const productCreate = result.data?.productCreate; + + if (!productCreate?.didSucceed) { + const errorMessages = productCreate?.inputErrors + ?.map( + (e: { path?: string[] | null; message?: string | null }) => + `${e.path?.join('.')}: ${e.message}` + ) + .join(', '); + return { success: false, error: errorMessages || 'Failed to create product' }; + } + + return { + success: true, + product: productCreate.product + ? { + id: productCreate.product.id, + name: productCreate.product.name, + description: productCreate.product.description ?? null, + unitPrice: parseFloat(productCreate.product.unitPrice) + } + : undefined + }; +} + +export interface UpdateWaveProductInput { + id: string; + name?: string; + unitPrice?: number; + description?: string; + incomeAccountId?: string; + expenseAccountId?: string; + defaultSalesTaxIds?: string[]; +} + +export async function updateWaveProduct(input: UpdateWaveProductInput): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.mutation(UpdateProductDocument, { + input: { + id: input.id, + name: input.name, + unitPrice: input.unitPrice, + description: input.description, + incomeAccountId: input.incomeAccountId, + expenseAccountId: input.expenseAccountId, + defaultSalesTaxIds: input.defaultSalesTaxIds + } + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return { success: false, error: result.error.message || 'Failed to update product' }; + } + + const productPatch = result.data?.productPatch; + + if (!productPatch?.didSucceed) { + const errorMessages = productPatch?.inputErrors + ?.map( + (e: { path?: string[] | null; message?: string | null }) => + `${e.path?.join('.')}: ${e.message}` + ) + .join(', '); + return { success: false, error: errorMessages || 'Failed to update product' }; + } + + return { + success: true, + product: productPatch.product + ? { + id: productPatch.product.id, + name: productPatch.product.name, + description: productPatch.product.description ?? null, + unitPrice: parseFloat(productPatch.product.unitPrice) + } + : undefined + }; +} + +export interface ArchiveWaveProductResult { + success: boolean; + error?: string; +} + +export async function archiveWaveProduct(productId: string): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.mutation(ArchiveProductDocument, { + input: { id: productId } + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return { success: false, error: result.error.message || 'Failed to archive product' }; + } + + const productArchive = result.data?.productArchive; + + if (!productArchive?.didSucceed) { + const errorMessages = productArchive?.inputErrors + ?.map( + (e: { path?: string[] | null; message?: string | null }) => + `${e.path?.join('.')}: ${e.message}` + ) + .join(', '); + return { success: false, error: errorMessages || 'Failed to archive product' }; + } + + return { success: true }; +} + +// ===================== +// Customer Mutations +// ===================== + +export interface WaveAddressInput { + addressLine1?: string; + addressLine2?: string; + city?: string; + provinceCode?: string; + countryCode?: string; + postalCode?: string; +} + +export interface CreateWaveCustomerInput { + name: string; + firstName?: string; + lastName?: string; + email?: string; + phone?: string; + mobile?: string; + address?: WaveAddressInput; + internalNotes?: string; + currency?: string; +} + +export interface WaveCustomerResult { + success: boolean; + customer?: { + id: string; + name: string; + email: string | null; + }; + error?: string; +} + +export async function createWaveCustomer( + input: CreateWaveCustomerInput +): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.mutation(CreateCustomerDocument, { + input: { + businessId: PUBLIC_WAVE_BUSINESS_ID, + name: input.name, + firstName: input.firstName, + lastName: input.lastName, + email: input.email, + phone: input.phone, + mobile: input.mobile, + address: input.address, + internalNotes: input.internalNotes, + currency: input.currency + } + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return { success: false, error: result.error.message || 'Failed to create customer' }; + } + + const customerCreate = result.data?.customerCreate; + + if (!customerCreate?.didSucceed) { + const errorMessages = customerCreate?.inputErrors + ?.map( + (e: { path?: string[] | null; message?: string | null }) => + `${e.path?.join('.')}: ${e.message}` + ) + .join(', '); + return { success: false, error: errorMessages || 'Failed to create customer' }; + } + + return { + success: true, + customer: customerCreate.customer + ? { + id: customerCreate.customer.id, + name: customerCreate.customer.name, + email: customerCreate.customer.email ?? null + } + : undefined + }; +} + +export interface UpdateWaveCustomerInput { + id: string; + name?: string; + firstName?: string; + lastName?: string; + email?: string; + phone?: string; + mobile?: string; + address?: WaveAddressInput; + internalNotes?: string; + currency?: string; +} + +export async function updateWaveCustomer( + input: UpdateWaveCustomerInput +): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.mutation(UpdateCustomerDocument, { + input: { + id: input.id, + name: input.name, + firstName: input.firstName, + lastName: input.lastName, + email: input.email, + phone: input.phone, + mobile: input.mobile, + address: input.address, + internalNotes: input.internalNotes, + currency: input.currency + } + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return { success: false, error: result.error.message || 'Failed to update customer' }; + } + + const customerPatch = result.data?.customerPatch; + + if (!customerPatch?.didSucceed) { + const errorMessages = customerPatch?.inputErrors + ?.map( + (e: { path?: string[] | null; message?: string | null }) => + `${e.path?.join('.')}: ${e.message}` + ) + .join(', '); + return { success: false, error: errorMessages || 'Failed to update customer' }; + } + + return { + success: true, + customer: customerPatch.customer + ? { + id: customerPatch.customer.id, + name: customerPatch.customer.name, + email: customerPatch.customer.email ?? null + } + : undefined + }; +} + +export interface DeleteWaveCustomerResult { + success: boolean; + error?: string; +} + +export async function deleteWaveCustomer(customerId: string): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.mutation(DeleteCustomerDocument, { + input: { id: customerId } + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return { success: false, error: result.error.message || 'Failed to delete customer' }; + } + + const customerDelete = result.data?.customerDelete; + + if (!customerDelete?.didSucceed) { + const errorMessages = customerDelete?.inputErrors + ?.map( + (e: { path?: string[] | null; message?: string | null }) => + `${e.path?.join('.')}: ${e.message}` + ) + .join(', '); + return { success: false, error: errorMessages || 'Failed to delete customer' }; + } + + return { success: true }; +} diff --git a/src/lib/graphql/wave/mutations/accounts.gql b/src/lib/graphql/wave/mutations/accounts.gql new file mode 100644 index 0000000..44b5d53 --- /dev/null +++ b/src/lib/graphql/wave/mutations/accounts.gql @@ -0,0 +1,56 @@ +mutation CreateAccount($input: AccountCreateInput!) { + accountCreate(input: $input) { + didSucceed + inputErrors { + path + message + code + } + account { + id + name + description + displayId + type { + name + value + } + subtype { + name + value + } + currency { + code + } + } + } +} + +mutation UpdateAccount($input: AccountPatchInput!) { + accountPatch(input: $input) { + didSucceed + inputErrors { + path + message + code + } + account { + id + name + description + displayId + sequence + } + } +} + +mutation ArchiveAccount($input: AccountArchiveInput!) { + accountArchive(input: $input) { + didSucceed + inputErrors { + path + message + code + } + } +} diff --git a/src/lib/graphql/wave/mutations/customers.gql b/src/lib/graphql/wave/mutations/customers.gql new file mode 100644 index 0000000..1779e01 --- /dev/null +++ b/src/lib/graphql/wave/mutations/customers.gql @@ -0,0 +1,53 @@ +mutation CreateCustomer($input: CustomerCreateInput!) { + customerCreate(input: $input) { + didSucceed + inputErrors { + path + message + code + } + customer { + id + name + firstName + lastName + email + phone + currency { + code + } + createdAt + } + } +} + +mutation UpdateCustomer($input: CustomerPatchInput!) { + customerPatch(input: $input) { + didSucceed + inputErrors { + path + message + code + } + customer { + id + name + firstName + lastName + email + phone + modifiedAt + } + } +} + +mutation DeleteCustomer($input: CustomerDeleteInput!) { + customerDelete(input: $input) { + didSucceed + inputErrors { + path + message + code + } + } +} diff --git a/src/lib/graphql/wave/mutations/invoices.gql b/src/lib/graphql/wave/mutations/invoices.gql new file mode 100644 index 0000000..737df9a --- /dev/null +++ b/src/lib/graphql/wave/mutations/invoices.gql @@ -0,0 +1,125 @@ +mutation CreateInvoice($input: InvoiceCreateInput!) { + invoiceCreate(input: $input) { + didSucceed + inputErrors { + path + message + code + } + invoice { + id + invoiceNumber + invoiceDate + dueDate + status + total { + value + currency { + code + } + } + createdAt + } + } +} + +mutation UpdateInvoice($input: InvoicePatchInput!) { + invoicePatch(input: $input) { + didSucceed + inputErrors { + path + message + code + } + invoice { + id + invoiceNumber + invoiceDate + dueDate + status + total { + value + currency { + code + } + } + modifiedAt + } + } +} + +mutation DeleteInvoice($input: InvoiceDeleteInput!) { + invoiceDelete(input: $input) { + didSucceed + inputErrors { + path + message + code + } + } +} + +mutation SendInvoice($input: InvoiceSendInput!) { + invoiceSend(input: $input) { + didSucceed + inputErrors { + path + message + code + } + invoice { + id + status + lastSentAt + lastSentVia + } + } +} + +mutation ApproveInvoice($input: InvoiceApproveInput!) { + invoiceApprove(input: $input) { + didSucceed + inputErrors { + path + message + code + } + invoice { + id + status + } + } +} + +mutation MarkInvoiceSent($input: InvoiceMarkSentInput!) { + invoiceMarkSent(input: $input) { + didSucceed + inputErrors { + path + message + code + } + invoice { + id + status + lastSentAt + lastSentVia + } + } +} + +mutation CloneInvoice($input: InvoiceCloneInput!) { + invoiceClone(input: $input) { + didSucceed + inputErrors { + path + message + code + } + invoice { + id + invoiceNumber + status + } + } +} diff --git a/src/lib/graphql/wave/mutations/products.gql b/src/lib/graphql/wave/mutations/products.gql new file mode 100644 index 0000000..e0ab468 --- /dev/null +++ b/src/lib/graphql/wave/mutations/products.gql @@ -0,0 +1,54 @@ +mutation CreateProduct($input: ProductCreateInput!) { + productCreate(input: $input) { + didSucceed + inputErrors { + path + message + code + } + product { + id + name + description + unitPrice + isSold + isBought + createdAt + } + } +} + +mutation UpdateProduct($input: ProductPatchInput!) { + productPatch(input: $input) { + didSucceed + inputErrors { + path + message + code + } + product { + id + name + description + unitPrice + isSold + isBought + modifiedAt + } + } +} + +mutation ArchiveProduct($input: ProductArchiveInput!) { + productArchive(input: $input) { + didSucceed + inputErrors { + path + message + code + } + product { + id + isArchived + } + } +} diff --git a/src/lib/graphql/wave/mutations/salesTaxes.gql b/src/lib/graphql/wave/mutations/salesTaxes.gql new file mode 100644 index 0000000..3622b7b --- /dev/null +++ b/src/lib/graphql/wave/mutations/salesTaxes.gql @@ -0,0 +1,52 @@ +mutation CreateSalesTax($input: SalesTaxCreateInput!) { + salesTaxCreate(input: $input) { + didSucceed + inputErrors { + path + message + code + } + salesTax { + id + name + abbreviation + rate + isCompound + isRecoverable + createdAt + } + } +} + +mutation UpdateSalesTax($input: SalesTaxPatchInput!) { + salesTaxPatch(input: $input) { + didSucceed + inputErrors { + path + message + code + } + salesTax { + id + name + abbreviation + rate + modifiedAt + } + } +} + +mutation ArchiveSalesTax($input: SalesTaxArchiveInput!) { + salesTaxArchive(input: $input) { + didSucceed + inputErrors { + path + message + code + } + salesTax { + id + isArchived + } + } +} diff --git a/src/lib/graphql/wave/queries/accounts.gql b/src/lib/graphql/wave/queries/accounts.gql new file mode 100644 index 0000000..7aed8d2 --- /dev/null +++ b/src/lib/graphql/wave/queries/accounts.gql @@ -0,0 +1,105 @@ +query GetAccounts( + $businessId: ID! + $page: Int + $pageSize: Int + $types: [AccountTypeValue!] + $subtypes: [AccountSubtypeValue!] + $isArchived: Boolean +) { + business(id: $businessId) { + id + accounts( + page: $page + pageSize: $pageSize + types: $types + subtypes: $subtypes + isArchived: $isArchived + ) { + edges { + node { + id + name + description + displayId + type { + name + value + } + subtype { + name + value + } + normalBalanceType + currency { + code + symbol + } + balance + balanceInBusinessCurrency + isArchived + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + } +} + +query GetAccount($businessId: ID!, $accountId: ID!) { + business(id: $businessId) { + id + account(id: $accountId) { + id + name + description + displayId + type { + name + value + normalBalanceType + } + subtype { + name + value + archivable + systemCreated + description + } + normalBalanceType + currency { + code + symbol + name + } + balance + balanceInBusinessCurrency + isArchived + sequence + } + } +} + +query GetAccountTypes { + accountTypes { + name + value + normalBalanceType + } +} + +query GetAccountSubtypes { + accountSubtypes { + name + value + type { + name + value + } + archivable + systemCreated + description + } +} diff --git a/src/lib/graphql/wave/queries/business.gql b/src/lib/graphql/wave/queries/business.gql new file mode 100644 index 0000000..c9af394 --- /dev/null +++ b/src/lib/graphql/wave/queries/business.gql @@ -0,0 +1,55 @@ +query GetBusiness($id: ID) { + business(id: $id) { + id + name + isPersonal + currency { + code + symbol + name + } + timezone + address { + addressLine1 + addressLine2 + city + province { + code + name + } + country { + code + name + } + postalCode + } + phone + website + isArchived + createdAt + modifiedAt + emailSendEnabled + } +} + +query ListBusinesses($page: Int, $pageSize: Int) { + businesses(page: $page, pageSize: $pageSize) { + edges { + node { + id + name + isPersonal + currency { + code + symbol + } + isArchived + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } +} diff --git a/src/lib/graphql/wave/queries/customers.gql b/src/lib/graphql/wave/queries/customers.gql new file mode 100644 index 0000000..1397f8f --- /dev/null +++ b/src/lib/graphql/wave/queries/customers.gql @@ -0,0 +1,134 @@ +query GetCustomers( + $businessId: ID! + $page: Int + $pageSize: Int + $sort: [CustomerSort!]! = [NAME_ASC] +) { + business(id: $businessId) { + id + customers(page: $page, pageSize: $pageSize, sort: $sort) { + edges { + node { + id + name + firstName + lastName + email + phone + mobile + currency { + code + symbol + } + address { + addressLine1 + addressLine2 + city + province { + code + name + } + country { + code + name + } + postalCode + } + outstandingAmount { + value + currency { + code + } + } + overdueAmount { + value + currency { + code + } + } + createdAt + modifiedAt + isArchived + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + } +} + +query GetCustomer($businessId: ID!, $customerId: ID!) { + business(id: $businessId) { + id + customer(id: $customerId) { + id + name + firstName + lastName + displayId + email + phone + mobile + fax + tollFree + website + internalNotes + currency { + code + symbol + name + } + address { + addressLine1 + addressLine2 + city + province { + code + name + } + country { + code + name + } + postalCode + } + shippingDetails { + name + phone + instructions + address { + addressLine1 + addressLine2 + city + province { + code + name + } + country { + code + name + } + postalCode + } + } + outstandingAmount { + value + currency { + code + } + } + overdueAmount { + value + currency { + code + } + } + createdAt + modifiedAt + isArchived + } + } +} diff --git a/src/lib/graphql/wave/queries/invoices.gql b/src/lib/graphql/wave/queries/invoices.gql new file mode 100644 index 0000000..5242f0d --- /dev/null +++ b/src/lib/graphql/wave/queries/invoices.gql @@ -0,0 +1,207 @@ +query GetInvoices( + $businessId: ID! + $page: Int + $pageSize: Int + $sort: [InvoiceSort!]! = [INVOICE_DATE_DESC] + $status: InvoiceStatus + $customerId: ID +) { + business(id: $businessId) { + id + invoices( + page: $page + pageSize: $pageSize + sort: $sort + status: $status + customerId: $customerId + ) { + edges { + node { + id + invoiceNumber + invoiceDate + dueDate + status + title + customer { + id + name + email + } + amountDue { + value + currency { + code + symbol + } + } + amountPaid { + value + currency { + code + symbol + } + } + total { + value + currency { + code + symbol + } + } + currency { + code + symbol + } + createdAt + modifiedAt + lastSentAt + lastViewedAt + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + } +} + +query GetInvoice($businessId: ID!, $invoiceId: ID!) { + business(id: $businessId) { + id + invoice(id: $invoiceId) { + id + invoiceNumber + invoiceDate + dueDate + status + title + subhead + poNumber + memo + footer + pdfUrl + viewUrl + customer { + id + name + firstName + lastName + email + address { + addressLine1 + addressLine2 + city + province { + code + name + } + country { + code + name + } + postalCode + } + } + items { + product { + id + name + } + description + quantity + unitPrice + subtotal { + value + currency { + code + } + } + total { + value + currency { + code + } + } + taxes { + salesTax { + id + name + abbreviation + } + amount { + value + currency { + code + } + } + } + } + discounts { + ... on FixedInvoiceDiscount { + name + amount + } + ... on PercentageInvoiceDiscount { + name + percentage + } + } + subtotal { + value + currency { + code + symbol + } + } + taxTotal { + value + currency { + code + symbol + } + } + discountTotal { + value + currency { + code + symbol + } + } + total { + value + currency { + code + symbol + } + } + amountDue { + value + currency { + code + symbol + } + } + amountPaid { + value + currency { + code + symbol + } + } + currency { + code + symbol + name + } + exchangeRate + createdAt + modifiedAt + lastSentAt + lastSentVia + lastViewedAt + } + } +} diff --git a/src/lib/graphql/wave/queries/products.gql b/src/lib/graphql/wave/queries/products.gql new file mode 100644 index 0000000..a7e813e --- /dev/null +++ b/src/lib/graphql/wave/queries/products.gql @@ -0,0 +1,100 @@ +query GetProducts( + $businessId: ID! + $page: Int + $pageSize: Int + $sort: [ProductSort!]! = [NAME_ASC] + $isSold: Boolean + $isBought: Boolean + $isArchived: Boolean +) { + business(id: $businessId) { + id + products( + page: $page + pageSize: $pageSize + sort: $sort + isSold: $isSold + isBought: $isBought + isArchived: $isArchived + ) { + edges { + node { + id + name + description + unitPrice + isSold + isBought + isArchived + incomeAccount { + id + name + } + expenseAccount { + id + name + } + defaultSalesTaxes { + id + name + abbreviation + } + createdAt + modifiedAt + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + } +} + +query GetProduct($businessId: ID!, $productId: ID!) { + business(id: $businessId) { + id + product(id: $productId) { + id + name + description + unitPrice + isSold + isBought + isArchived + incomeAccount { + id + name + type { + name + value + } + subtype { + name + value + } + } + expenseAccount { + id + name + type { + name + value + } + subtype { + name + value + } + } + defaultSalesTaxes { + id + name + abbreviation + rate + } + createdAt + modifiedAt + } + } +} diff --git a/src/lib/graphql/wave/queries/salesTaxes.gql b/src/lib/graphql/wave/queries/salesTaxes.gql new file mode 100644 index 0000000..197e1a9 --- /dev/null +++ b/src/lib/graphql/wave/queries/salesTaxes.gql @@ -0,0 +1,56 @@ +query GetSalesTaxes($businessId: ID!, $page: Int, $pageSize: Int, $isArchived: Boolean) { + business(id: $businessId) { + id + salesTaxes(page: $page, pageSize: $pageSize, isArchived: $isArchived) { + edges { + node { + id + name + abbreviation + description + taxNumber + showTaxNumberOnInvoices + rate + rates { + effective + rate + } + isCompound + isRecoverable + isArchived + createdAt + modifiedAt + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + } +} + +query GetSalesTax($businessId: ID!, $salesTaxId: ID!) { + business(id: $businessId) { + id + salesTax(id: $salesTaxId) { + id + name + abbreviation + description + taxNumber + showTaxNumberOnInvoices + rate + rates { + effective + rate + } + isCompound + isRecoverable + isArchived + createdAt + modifiedAt + } + } +} diff --git a/src/lib/graphql/wave/schema.graphql b/src/lib/graphql/wave/schema.graphql new file mode 100644 index 0000000..f703177 --- /dev/null +++ b/src/lib/graphql/wave/schema.graphql @@ -0,0 +1,4479 @@ +"""Controls the rate of traffic.""" +directive @banLimit( + """Number of seconds before limit is reset.""" + duration: Int! = 60 + + """Number of occurrences allowed over duration.""" + limit: Int! = 60 +) on FIELD_DEFINITION | OBJECT + +directive @cost(complexity: Int, multipliers: [String]) on FIELD_DEFINITION | OBJECT + +"""Controls the rate of traffic.""" +directive @rateLimit( + """Number of seconds before limit is reset.""" + duration: Int! = 60 + + """Number of occurrences allowed over duration.""" + limit: Int! = 60 +) on FIELD_DEFINITION | OBJECT + +directive @schemas(include: [Schema!]!) on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + +""" +A unique record for each type of asset, liability, equity, income and expense. Used as part of a Chart of Accounts. +""" +type Account implements BusinessNode & Node { + """The balance of the account as of the current date.""" + balance: Decimal + + """ + The balance of the account as of the current date in the business currency. + """ + balanceInBusinessCurrency: Decimal + + """Business that the account belongs to.""" + business: Business! + + """The classic primary key used internally at Wave.""" + classicId: String + + """Currency of the account.""" + currency: Currency! + + """User defined description for the account.""" + description: String + + """User defined id for the account.""" + displayId: String + + """Unique identifier for the account.""" + id: ID! + + """Indicates whether the account is hidden from view by default.""" + isArchived: Boolean! + + """Name of the account.""" + name: String! + + """Credit or Debit.""" + normalBalanceType: AccountNormalBalanceType! + + """ + Numerically increasing version, each representing a revision of account data. + As soon as something modifies an account, its sequence is incremented. + """ + sequence: Int! + + """The account subtype classification based on type.""" + subtype: AccountSubtype! + + """Account type.""" + type: AccountType! +} + +"""Input to the `accountArchive` mutation.""" +input AccountArchiveInput { + """The unique identifier for the account.""" + id: ID! +} + +"""Output of the accountArchive mutation.""" +type AccountArchiveOutput { + """Indicates whether the account was successfully archived.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] +} + +"""Account connection.""" +type AccountConnection { + """List of accounts from the Chart of Accounts.""" + edges: [AccountEdge!]! + + """Information about pagination.""" + pageInfo: OffsetPageInfo! +} + +"""Input to the `accountCreate` mutation.""" +input AccountCreateInput { + """The unique identifier for the business.""" + businessId: ID! + + """Currency of the account. Will default to business's currency.""" + currency: CurrencyCode + + """User defined description for the account.""" + description: String + + """User defined id for the account.""" + displayId: String + + """Name of the account.""" + name: String! + + """The account subtype classification.""" + subtype: AccountSubtypeValue! +} + +"""Output of the `accountCreate` mutation.""" +type AccountCreateOutput { + """Account that was created.""" + account: Account + + """Indicates whether the account was successfully created.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] +} + +"""Account edge.""" +type AccountEdge { + """An account from the Chart of Accounts.""" + node: Account +} + +"""Account balance type.""" +enum AccountNormalBalanceType { + """Credit""" + CREDIT + + """Debit""" + DEBIT +} + +"""Input to the `accountPatch` mutation.""" +input AccountPatchInput { + """ + User defined description for the account. Use null to unset the current value. + """ + description: String + + """User defined id for the account. Use null to unset the current value.""" + displayId: String + + """The unique identifier for the account.""" + id: ID! + + """Name of the account.""" + name: String + + """ + The most recent reversion you are aware of. As soon as something modifies an account, its sequence is incremented. + """ + sequence: Int! +} + +"""Output of the accountPatch mutation.""" +type AccountPatchOutput { + """Account that was patched.""" + account: Account + + """Indicates whether the account was successfully patched.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] +} + +"""Account subtype.""" +type AccountSubtype { + """Indicates if accounts of this subtype can be archived.""" + archivable: Boolean! + + """Account subtype description.""" + description: String + + """Account subtype name.""" + name: String! + + """Indicates if accounts of this subtype is system created accounts.""" + systemCreated: Boolean! + + """Account type for the subtype.""" + type: AccountType! + + """Account subtype value.""" + value: AccountSubtypeValue! +} + +"""Subtypes of accounts, as used in the Chart of Accounts.""" +enum AccountSubtypeValue { + """Cash & Bank""" + CASH_AND_BANK + + """Cost of Goods Sold""" + COST_OF_GOODS_SOLD + + """Credit Card""" + CREDIT_CARD + + """Customer Prepayments and Customer Credits""" + CUSTOMER_PREPAYMENTS_AND_CREDITS + + """Depreciation and Amortization""" + DEPRECIATION_AND_AMORTIZATION + + """Discount""" + DISCOUNTS + + """Due For Payroll""" + DUE_FOR_PAYROLL + + """Due to You and Other Business Owners""" + DUE_TO_YOU_AND_OTHER_OWNERS + + """Expense""" + EXPENSE + + """Gain on Foreign Exchange""" + GAIN_ON_FOREIGN_EXCHANGE + + """Income""" + INCOME + + """Inventory""" + INVENTORY + + """Loan and Line of Credit""" + LOANS + + """Loss on Foreign Exchange""" + LOSS_ON_FOREIGN_EXCHANGE + + """Money in Transit""" + MONEY_IN_TRANSIT + + """Business Owner Contribution""" + NON_RETAINED_EARNINGS + + """Other Short-Term Asset""" + OTHER_CURRENT_ASSETS + + """Other Short-Term Liability""" + OTHER_CURRENT_LIABILITY + + """Other Income""" + OTHER_INCOME + + """Other Long-Term Asset""" + OTHER_LONG_TERM_ASSETS + + """Other Long-Term Liability""" + OTHER_LONG_TERM_LIABILITY + + """Payable""" + PAYABLE + + """System Payable Bill""" + PAYABLE_BILLS + + """System Payable Non-Bill""" + PAYABLE_OTHER + + """Payment Processing Fee""" + PAYMENT_PROCESSING_FEES + + """Payroll Expense""" + PAYROLL_EXPENSES + + """Property, Plant, Equipment""" + PROPERTY_PLANT_EQUIPMENT + + """Receivable""" + RECEIVABLE + + """System Receivable Invoice""" + RECEIVABLE_INVOICES + + """System Receivable Non-Invoice""" + RECEIVABLE_OTHER + + """Retained Earnings: Profit and Business Owner Drawing""" + RETAINED_EARNINGS + + """Sales Tax on Sales and Purchases""" + SALES_TAX + + """System Customer Credits""" + SYSTEM_CUSTOMER_CREDITS + + """Transfers""" + TRANSFERS + + """Uncategorized Expense""" + UNCATEGORIZED_EXPENSE + + """Uncategorized Income""" + UNCATEGORIZED_INCOME + + """Unknown Account""" + UNKNOWN_ACCOUNT + + """Vendor Prepayments and Vendor Credits""" + VENDOR_PREPAYMENTS_AND_CREDITS +} + +"""Account type.""" +type AccountType { + """Account type name.""" + name: String! + + """Normal balance type of the account type""" + normalBalanceType: AccountNormalBalanceType! + + """Account type value.""" + value: AccountTypeValue! +} + +"""Types of accounts, as used in the Chart of Accounts.""" +enum AccountTypeValue { + """ + Represents the different types of economic resources owned or controlled by an entity. + """ + ASSET + + """Represents the residual equity of an entity.""" + EQUITY + + """Represents the business's expenditures.""" + EXPENSE + + """Represents the business's earnings.""" + INCOME + + """Represents the different types of economic obligations of an entity.""" + LIABILITY +} + +"""An address.""" +type Address { + """Address line 1 (Street address/PO Box/Company name).""" + addressLine1: String + + """Address line 2 (Apartment/Suite/Unit/Building).""" + addressLine2: String + + """City/District/Suburb/Town/Village.""" + city: String + + """Country.""" + country: Country + + """Zip/Postal Code.""" + postalCode: String + + """State/County/Province/Region.""" + province: Province +} + +"""An address.""" +input AddressInput { + """Address line 1 (Street address/PO Box/Company name).""" + addressLine1: String + + """Address line 2 (Apartment/Suite/Unit/Building).""" + addressLine2: String + + """City/District/Suburb/Town/Village.""" + city: String + + """Country Code.""" + countryCode: CountryCode + + """Zip/Postal Code.""" + postalCode: String + + """ + State/County/Province/Region Code ([ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2)). + """ + provinceCode: String +} + +"""Balance type that expresses how to change an account.""" +enum BalanceType { + """Credit.""" + CREDIT + + """Debit.""" + DEBIT + + """ + Decrease using the inverse of the account's normal balance type. For contra accounts whose subtype is `DISCOUNTS` or `DEPRECIATION_AND_AMORTIZATION`, apply the amount in the account's normal balance type. + """ + DECREASE + + """ + Increase using the account's normal balance type. For contra accounts whose subtype is `DISCOUNTS` or `DEPRECIATION_AND_AMORTIZATION`, apply the amount in the inverse of the account's normal balance type. + """ + INCREASE +} + +"""An organization and legal entity made up of an association of people.""" +type Business implements Node { + """Get an account of the business.""" + account( + """ID of account.""" + id: ID! + ): Account + + """Chart of Accounts for the business.""" + accounts( + """Excludes accounts matching one of these subtypes.""" + excludedSubtypes: [AccountSubtypeValue!] + + """Find accounts matching isArchived. Use null to not filter.""" + isArchived: Boolean = false + + """1-based page number to retrieve.""" + page: Int = 1 + + """Limit on how many items each page should return.""" + pageSize: Int = 45 + + """Find accounts matching one of these subtypes.""" + subtypes: [AccountSubtypeValue!] + + """Find accounts matching one of these types.""" + types: [AccountTypeValue!] + ): AccountConnection + + """The address of the business.""" + address: Address + + """When the business was created.""" + createdAt: DateTime! + + """The currency of the business.""" + currency: Currency! + + """Get a customer of the business.""" + customer( + """ID of customer.""" + id: ID! + ): Customer + + """List of customers for the business.""" + customers( + """Find customers matching an email address.""" + email: String + + """Find customers that were modified after this date.""" + modifiedAtAfter: DateTime + + """Find customers that were modified before this date.""" + modifiedAtBefore: DateTime + + """1-based page number to retrieve.""" + page: Int = 1 + + """Limit on how many items each page should return.""" + pageSize: Int = 45 + + """Order result by specified options.""" + sort: [CustomerSort!]! = [CREATED_AT_DESC] + ): CustomerConnection + + """ + Indicates whether Wave email sending features, such as sending invoices, are enabled for this business + """ + emailSendEnabled: Boolean! + + """The fax number of the business.""" + fax: String + + """The unique identifier for the business.""" + id: ID! + + """Get an invoice of the business.""" + invoice( + """ID of invoice.""" + id: ID! + ): Invoice + + """Invoice and estimate settings for the business.""" + invoiceEstimateSettings: InvoiceEstimateSettings! + + """List of invoices for the business.""" + invoices( + """Find invoices that have this exact amount due.""" + amountDue: Decimal + + """Find invoices in a currency.""" + currency: CurrencyCode + + """Find invoices for a customer.""" + customerId: ID + + """Find invoices dated before or on this date.""" + invoiceDateEnd: Date + + """Find invoices dated on or after this date.""" + invoiceDateStart: Date + + """ + Find invoices with invoice number containing this string. Note that a query for `12` would find invoices with numbers `12`, `112`, `120`, `121`, `122`, etc. + """ + invoiceNumber: String + + """Find invoices that were modified after this date.""" + modifiedAtAfter: DateTime + + """Find invoices that were modified before this date.""" + modifiedAtBefore: DateTime + + """1-based page number to retrieve.""" + page: Int = 1 + + """Limit on how many items each page should return.""" + pageSize: Int = 45 + + """Order result by specified options.""" + sort: [InvoiceSort!]! = [CREATED_AT_DESC] + + """Find invoices that were created from a particular source.""" + sourceId: ID + + """Find invoices by status.""" + status: InvoiceStatus + ): InvoiceConnection + + """Is the business hidden from view by default.""" + isArchived: Boolean! + + """Does business use classic accounting system.""" + isClassicAccounting: Boolean! @deprecated(reason: "Classic Accounting is deprecated") + + """Does business use classic invoicing system.""" + isClassicInvoicing: Boolean! @deprecated(reason: "Classic Invoicing is deprecated") + + """ + Is the business a personal one with limited functionality compared to regular businesses. + """ + isPersonal: Boolean! + + """The mobile/cell number of the business.""" + mobile: String + + """When the business was last modified.""" + modifiedAt: DateTime! + + """The name of the business.""" + name: String! + + """The organization type of the business.""" + organizationalType: OrganizationalType + + """The phone number of the business.""" + phone: String + + """Get a product (or service) of the business.""" + product( + """ID of product.""" + id: ID! + ): Product + + """List of products (and services) for the business.""" + products( + """Find products matching isArchived. Use null to not filter.""" + isArchived: Boolean = false + + """Find products bought by the business.""" + isBought: Boolean + + """Find products sold by the business.""" + isSold: Boolean + + """Find products that were modified after this date.""" + modifiedAtAfter: DateTime + + """Find products that were modified before this date.""" + modifiedAtBefore: DateTime + + """1-based page number to retrieve.""" + page: Int = 1 + + """Limit on how many items each page should return.""" + pageSize: Int = 45 + + """Order result by specified options.""" + sort: [ProductSort!]! = [CREATED_AT_DESC] + ): ProductConnection + + """Get a sales tax of the business.""" + salesTax( + """ID of sales tax.""" + id: ID! + ): SalesTax + + """List of sales taxes for the business.""" + salesTaxes( + """Find sales taxes matching isArchived. Use null to not filter.""" + isArchived: Boolean = false + + """Find sales taxes that were modified after this date.""" + modifiedAtAfter: DateTime + + """Find sales taxes that were modified before this date.""" + modifiedAtBefore: DateTime + + """1-based page number to retrieve.""" + page: Int = 1 + + """Limit on how many items each page should return.""" + pageSize: Int = 45 + ): SalesTaxConnection + + """The subtype of the business.""" + subtype: BusinessSubtype + + """The timezone of the business.""" + timezone: String + + """The toll free number of the business.""" + tollFree: String + + """The type of the business.""" + type: BusinessType + + """Get a vendor of the business.""" + vendor( + """Id of vendor""" + id: ID! + ): Vendor + + """List of vendors for the business.""" + vendors( + """Find vendors matching an email address.""" + email: String + + """Find vendors that were modified after this date.""" + modifiedAtAfter: DateTime + + """Find vendors that were modified before this date.""" + modifiedAtBefore: DateTime + + """1-based page number to retrieve.""" + page: Int = 1 + + """Limit on how many items each page should return.""" + pageSize: Int = 45 + ): VendorConnection + + """The website of the business.""" + website: String +} + +"""Business connection.""" +type BusinessConnection { + """List of businesses.""" + edges: [BusinessEdge!]! + + """Information about pagination.""" + pageInfo: OffsetPageInfo! +} + +"""Business edge.""" +type BusinessEdge { + """A business.""" + node: Business +} + +"""An object belonging to a `Business` with an `ID`.""" +interface BusinessNode { + """Business that the node belongs to.""" + business: Business! + + """ID of the object.""" + id: ID! +} + +"""Granular area of focus of a business.""" +type BusinessSubtype { + """The description of the business subtype in human-friendly form.""" + name: String! + + """The enum value of the business subtype.""" + value: BusinessSubtypeValue! +} + +"""Granular area of focus of a business.""" +enum BusinessSubtypeValue { + """Advertising, Public Relations""" + ADVERTISING_PUBLIC_RELATIONS + + """Agriculture, Ranching and Farming""" + AGRICULTURE_RANCHING_FARMING + + """Actor""" + ARTISTS_PHOTOGRAPHERS_CREATIVE__ACTOR + + """Audio/Visual Production""" + ARTISTS_PHOTOGRAPHERS_CREATIVE__AUDIO_VISUAL_PRODUCTION + + """Craftsperson""" + ARTISTS_PHOTOGRAPHERS_CREATIVE__CRAFTSPERSON + + """Dancer, Choreographer""" + ARTISTS_PHOTOGRAPHERS_CREATIVE__DANCER_CHOREOG + + """Musician""" + ARTISTS_PHOTOGRAPHERS_CREATIVE__MUSICIAN + + """Other Creative""" + ARTISTS_PHOTOGRAPHERS_CREATIVE__OTHER + + """Performing Arts (acting, music, dance)""" + ARTISTS_PHOTOGRAPHERS_CREATIVE__PERFORMING_ARTS_ACTING_MUSIC_DANCE + + """Photographer""" + ARTISTS_PHOTOGRAPHERS_CREATIVE__PHOTOGRAPHER + + """Visual Artist""" + ARTISTS_PHOTOGRAPHERS_CREATIVE__VISUAL_ARTIST + + """Automotive Repair & Sales""" + AUTOMOTIVE_SALES_AND_REPAIR + + """Church, Religious Organization""" + CHURCH_RELIGIOUS_ORGANIZATION + + """Contractor""" + CONSTRUCTION_HOME_IMPROVEMENT__CONTRACTOR + + """Engineer""" + CONSTRUCTION_HOME_IMPROVEMENT__ENGINEER + + """Home Inspector""" + CONSTRUCTION_HOME_IMPROVEMENT__HOME_INSPECTOR + + """Trade""" + CONSTRUCTION_HOME_IMPROVEMENT__OTHER_TRADES + + """Accountant, Bookkeeper""" + CONSULTANTS_PROFESSIONALS__ACCOUNTANTS_BOOKKEEPERS + + """Communications, Marketing, PR""" + CONSULTANTS_PROFESSIONALS__COMMUNICATIONS + + """Executive Coach""" + CONSULTANTS_PROFESSIONALS__EXECUTIVE_COACH + + """HR, Recruitment, Staffing""" + CONSULTANTS_PROFESSIONALS__HR_RECRUITMENT_STAFFING + + """IT, Technical""" + CONSULTANTS_PROFESSIONALS__IT_TECHNICAL + + """Other Consultant""" + CONSULTANTS_PROFESSIONALS__OTHER + + """Sales""" + CONSULTANTS_PROFESSIONALS__SALES + + """Design, Architecture, Engineering""" + DESIGN_ARCHITECTURE_ENGINEERING + + """Other Financial Service""" + FINANCIAL_SERVICES + + """Salon, Spa""" + HAIR_SPA_AESTHETICS__HAIR_SALON + + """Massage""" + HAIR_SPA_AESTHETICS__MASSAGE + + """Nails, Skin, Aesthetics""" + HAIR_SPA_AESTHETICS__NAIL_SKIN_AESTHETICS + + """Other Aesthetics/Spa""" + HAIR_SPA_AESTHETICS__OTHER + + """Insurance Agency, Broker""" + INSURANCE_AGENCY_BROKER + + """Landlord""" + LANDLORD_PROPERTY_MANAGER__LANDLORD + + """Property Manager""" + LANDLORD_PROPERTY_MANAGER__PROPERTY_MANAGER + + """Lawn Care, Landscaping""" + LAWN_CARE_LANDSCAPING + + """Legal Services""" + LEGAL_SERVICES + + """Lodging, Hotel, Motel""" + LODGING_HOTEL_MOTEL + + """Manufacturing Representative, Agent""" + MANUFACTURER_REPRESENTATIVE_AGENT + + """Chiropractor""" + MEDICAL_DENTAL_HEALTH_SERVICE__CHIROPRACTOR + + """Dentist""" + MEDICAL_DENTAL_HEALTH_SERVICE__DENTIST + + """Fitness""" + MEDICAL_DENTAL_HEALTH_SERVICE__FITNESS + + """Massage Therapist""" + MEDICAL_DENTAL_HEALTH_SERVICE__MASSAGE_THERAPIST + + """Mental Health""" + MEDICAL_DENTAL_HEALTH_SERVICE__MENTAL_HEALTH + + """Nutrition""" + MEDICAL_DENTAL_HEALTH_SERVICE__NUTRITION + + """Occupational Therapist""" + MEDICAL_DENTAL_HEALTH_SERVICE__OCCUP_THERAPIST + + """Other Health""" + MEDICAL_DENTAL_HEALTH_SERVICE__OTHER + + """Physical Therapist""" + MEDICAL_DENTAL_HEALTH_SERVICE__PHYSICAL_THERAPIST + + """Association""" + NONPROFIT_ASSOCIATIONS_GROUPS__ASSOCIATION + + """Charity""" + NONPROFIT_ASSOCIATIONS_GROUPS__CHARITABLE + + """Club""" + NONPROFIT_ASSOCIATIONS_GROUPS__CLUB + + """Condo""" + NONPROFIT_ASSOCIATIONS_GROUPS__CONDO + + """Other Non-Profit""" + NONPROFIT_ASSOCIATIONS_GROUPS__OTHER + + """Parent Booster USA""" + NONPROFIT_ASSOCIATIONS_GROUPS__PARENT_BOOSTER + + """Other (please specify)""" + OTHER__OTHER_PLEASE_SPECIFY + + """Manufacturer""" + PRODUCT_PROVIDER__MANUFACTURER + + """Manufacturer and Vendor""" + PRODUCT_PROVIDER__MANUFACTURER_AND_VENDOR + + """Other Product-based Business""" + PRODUCT_PROVIDER__OTHER + + """Vendor""" + PRODUCT_PROVIDER__VENDOR + + """Real Estate Agent""" + REAL_ESTATE_SALES__AGENT + + """Real Estate Broker""" + REAL_ESTATE_SALES__BROKER + + """Other Real Estate""" + REAL_ESTATE_SALES__OTHER + + """Real Estate Rental""" + RENTAL + + """Repairs/Maintenance""" + REPAIR_AND_MAINTENANCE + + """Restaurant, Caterer, Bar""" + RESTAURANT_CATERER_BAR + + """eBay Resellers""" + RETAILERS_AND_RESELLERS__EBAY + + """Etsy Vendors""" + RETAILERS_AND_RESELLERS__ETSY + + """Non-Store Retailers""" + RETAILERS_AND_RESELLERS__NON_STORE_RETAILER + + """Other Retailers""" + RETAILERS_AND_RESELLERS__OTHER + + """Store Retailers""" + RETAILERS_AND_RESELLERS__STORE_RETAILER + + """Sales: Independent Agent""" + SALES_INDEPENDENT_AGENT + + """Cleaning, Janitorial Services""" + SERVICE_PROVIDER__CLEANING_JANITORIAL_SERVICES + + """Customer Service/Support""" + SERVICE_PROVIDER__CUSTOMER_SERVICE_SUPPORT + + """Household Employer""" + SERVICE_PROVIDER__DOMESTIC_CAREGIVER_EMPLOYER + + """Fitness""" + SERVICE_PROVIDER__FITNESS + + """Office Admin/Support""" + SERVICE_PROVIDER__OFFICE_ADMIN_SUPPORT + + """Other Service-based Business""" + SERVICE_PROVIDER__OTHER + + """Personal Care""" + SERVICE_PROVIDER__PERSONAL_CARE + + """Telemarketing""" + SERVICE_PROVIDER__TELEMARKETING + + """Transcription""" + SERVICE_PROVIDER__TRANSCRIPTION + + """Transportation, Trucking, Deliver""" + TRANSPORTATION_TRUCKING_DELIVERY + + """Designer""" + WEB_MEDIA_FREELANCER__DESIGNER + + """Marketing, Social Media""" + WEB_MEDIA_FREELANCER__MARKETING_SOCIAL_MEDIA + + """Other Media/Tech""" + WEB_MEDIA_FREELANCER__OTHER + + """Programmer""" + WEB_MEDIA_FREELANCER__PROGRAMMER + + """SEO""" + WEB_MEDIA_FREELANCER__SEO + + """Writer""" + WEB_MEDIA_FREELANCER__WRITER + + """Wholesale Distribution and Sales""" + WHOLESALE_DISTRIBUTION_SALES +} + +"""Area of focus of a business.""" +type BusinessType { + """The description of the business type in human-friendly form.""" + name: String! + + """The enum value of the business type.""" + value: BusinessTypeValue! +} + +"""Area of focus of a business.""" +enum BusinessTypeValue { + """Artists, Photographers & Creative Types""" + ARTISTS_PHOTOGRAPHERS_CREATIVE + + """Consultants & Professionals""" + CONSULTANTS_PROFESSIONALS + + """Financial Services""" + FINANCE_INSURANCE + + """Hair, Spa & Aesthetics""" + HAIR_SPA_AESTHETICS + + """Medical, Dental, Health""" + MEDICAL_DENTAL_HEALTH_SERVICE + + """Non-profits, Associations & Groups""" + NONPROFIT_ASSOCIATIONS_GROUPS + + """Other (please specify)""" + OTHER + + """General: I make or sell a PRODUCT""" + PRODUCT_PROVIDER + + """Real Estate, Construction & Home Improvement""" + REALESTATE_HOME + + """Retailers, Resellers & Sales""" + RETAILERS_AND_RESELLERS + + """General: I provide a SERVICE""" + SERVICE_PROVIDER + + """Web, Tech & Media""" + WEB_MEDIA_FREELANCER +} + +"""A country.""" +type Country { + """Country code.""" + code: CountryCode! + + """Default currency of the country.""" + currency: Currency! + + """Plain-language representation.""" + name: String! + + """Name of the country with the appropriate article.""" + nameWithArticle: String! + + """List of principal subdivisions.""" + provinces: [Province!]! +} + +""" +Country codes ([ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)). +""" +enum CountryCode { + """Andorra""" + AD + + """United Arab Emirates""" + AE + + """Afghanistan""" + AF + + """Antigua and Barbuda""" + AG + + """Anguilla""" + AI + + """Albania""" + AL + + """Armenia""" + AM + + """Angola""" + AO + + """Antarctica""" + AQ + + """Argentina""" + AR + + """American Samoa""" + AS + + """Austria""" + AT + + """Australia""" + AU + + """Aruba""" + AW + + """Åland Islands""" + AX + + """Azerbaijan""" + AZ + + """Bosnia and Herzegovina""" + BA + + """Barbados""" + BB + + """Bangladesh""" + BD + + """Belgium""" + BE + + """Burkina Faso""" + BF + + """Bulgaria""" + BG + + """Bahrain""" + BH + + """Burundi""" + BI + + """Benin""" + BJ + + """Saint Barthélemy""" + BL + + """Bermuda""" + BM + + """Brunei Darussalam""" + BN + + """Bolivia, Plurinational State of""" + BO + + """Bonaire, Sint Eustatius and Saba""" + BQ + + """Brazil""" + BR + + """Bahamas""" + BS + + """Bhutan""" + BT + + """Bouvet Island""" + BV + + """Botswana""" + BW + + """Belarus""" + BY + + """Belize""" + BZ + + """Canada""" + CA + + """Cocos (Keeling) Islands""" + CC + + """Congo, The Democratic Republic of the""" + CD + + """Central African Republic""" + CF + + """Congo""" + CG + + """Switzerland""" + CH + + """Côte d'Ivoire""" + CI + + """Cook Islands""" + CK + + """Chile""" + CL + + """Cameroon""" + CM + + """China""" + CN + + """Colombia""" + CO + + """Costa Rica""" + CR + + """Cuba""" + CU + + """Cape Verde""" + CV + + """Curaçao""" + CW + + """Christmas Island""" + CX + + """Cyprus""" + CY + + """Czech Republic""" + CZ + + """Germany""" + DE + + """Djibouti""" + DJ + + """Denmark""" + DK + + """Dominica""" + DM + + """Dominican Republic""" + DO + + """Algeria""" + DZ + + """Ecuador""" + EC + + """Estonia""" + EE + + """Egypt""" + EG + + """Western Sahara""" + EH + + """Eritrea""" + ER + + """Spain""" + ES + + """Ethiopia""" + ET + + """Finland""" + FI + + """Fiji""" + FJ + + """Falkland Islands""" + FK + + """Micronesia, Federated States of""" + FM + + """Faroe Islands""" + FO + + """France""" + FR + + """Gabon""" + GA + + """United Kingdom""" + GB + + """Grenada""" + GD + + """Georgia""" + GE + + """French Guiana""" + GF + + """Guernsey""" + GG + + """Ghana""" + GH + + """Gibraltar""" + GI + + """Greenland""" + GL + + """Gambia""" + GM + + """Guinea""" + GN + + """Guadeloupe""" + GP + + """Equatorial Guinea""" + GQ + + """Greece""" + GR + + """South Georgia and the South Sandwich Islands""" + GS + + """Guatemala""" + GT + + """Guam""" + GU + + """Guinea-Bissau""" + GW + + """Guyana""" + GY + + """Hong Kong""" + HK + + """Heard Island and McDonald Islands""" + HM + + """Honduras""" + HN + + """Croatia""" + HR + + """Haiti""" + HT + + """Hungary""" + HU + + """Indonesia""" + ID + + """Ireland""" + IE + + """Israel""" + IL + + """Isle of Man""" + IM + + """India""" + IN + + """British Indian Ocean Territory""" + IO + + """Iraq""" + IQ + + """Iran""" + IR + + """Iceland""" + IS + + """Italy""" + IT + + """Jersey""" + JE + + """Jamaica""" + JM + + """Jordan""" + JO + + """Japan""" + JP + + """Kenya""" + KE + + """Kyrgyzstan""" + KG + + """Cambodia""" + KH + + """Kiribati""" + KI + + """Comoros""" + KM + + """Saint Kitts and Nevis""" + KN + + """Korea, Democratic People's Republic of""" + KP + + """Korea, Republic of""" + KR + + """Kuwait""" + KW + + """Cayman Islands""" + KY + + """Kazakhstan""" + KZ + + """Lao People's Democratic Republic""" + LA + + """Lebanon""" + LB + + """Saint Lucia""" + LC + + """Liechtenstein""" + LI + + """Sri Lanka""" + LK + + """Liberia""" + LR + + """Lesotho""" + LS + + """Lithuania""" + LT + + """Luxembourg""" + LU + + """Latvia""" + LV + + """Libya""" + LY + + """Morocco""" + MA + + """Monaco""" + MC + + """Moldova, Republic of""" + MD + + """Montenegro""" + ME + + """Saint Martin""" + MF + + """Madagascar""" + MG + + """Marshall Islands""" + MH + + """North Macedonia""" + MK + + """Mali""" + ML + + """Myanmar""" + MM + + """Mongolia""" + MN + + """Macao""" + MO + + """Northern Mariana Islands""" + MP + + """Martinique""" + MQ + + """Mauritania""" + MR + + """Montserrat""" + MS + + """Malta""" + MT + + """Mauritius""" + MU + + """Maldives""" + MV + + """Malawi""" + MW + + """Mexico""" + MX + + """Malaysia""" + MY + + """Mozambique""" + MZ + + """Namibia""" + NA + + """New Caledonia""" + NC + + """Niger""" + NE + + """Norfolk Island""" + NF + + """Nigeria""" + NG + + """Nicaragua""" + NI + + """Netherlands""" + NL + + """Norway""" + NO + + """Nepal""" + NP + + """Nauru""" + NR + + """Niue""" + NU + + """New Zealand""" + NZ + + """Oman""" + OM + + """Panama""" + PA + + """Peru""" + PE + + """French Polynesia""" + PF + + """Papua New Guinea""" + PG + + """Philippines""" + PH + + """Pakistan""" + PK + + """Poland""" + PL + + """Saint Pierre and Miquelon""" + PM + + """Pitcairn""" + PN + + """Puerto Rico""" + PR + + """Palestine""" + PS + + """Portugal""" + PT + + """Palau""" + PW + + """Paraguay""" + PY + + """Qatar""" + QA + + """Réunion""" + RE + + """Romania""" + RO + + """Serbia""" + RS + + """Russian Federation""" + RU + + """Rwanda""" + RW + + """Saudi Arabia""" + SA + + """Solomon Islands""" + SB + + """Seychelles""" + SC + + """Sudan""" + SD + + """Sweden""" + SE + + """Singapore""" + SG + + """Saint Helena, Ascension and Tristan da Cunha""" + SH + + """Slovenia""" + SI + + """Svalbard and Jan Mayen""" + SJ + + """Slovakia""" + SK + + """Sierra Leone""" + SL + + """San Marino""" + SM + + """Senegal""" + SN + + """Somalia""" + SO + + """Suriname""" + SR + + """South Sudan""" + SS + + """Sao Tome and Principe""" + ST + + """El Salvador""" + SV + + """Sint Maarten""" + SX + + """Syria""" + SY + + """Eswatini""" + SZ + + """Turks and Caicos Islands""" + TC + + """Chad""" + TD + + """French Southern Territories""" + TF + + """Togo""" + TG + + """Thailand""" + TH + + """Tajikistan""" + TJ + + """Tokelau""" + TK + + """Timor-Leste""" + TL + + """Turkmenistan""" + TM + + """Tunisia""" + TN + + """Tonga""" + TO + + """Turkey""" + TR + + """Trinidad and Tobago""" + TT + + """Tuvalu""" + TV + + """Taiwan""" + TW + + """Tanzania, United Republic of""" + TZ + + """Ukraine""" + UA + + """Uganda""" + UG + + """United States Minor Outlying Islands""" + UM + + """United States""" + US + + """Uruguay""" + UY + + """Uzbekistan""" + UZ + + """Holy See""" + VA + + """Saint Vincent and the Grenadines""" + VC + + """Venezuela, Bolivarian Republic of""" + VE + + """Virgin Islands (British)""" + VG + + """Virgin Islands (U.S)""" + VI + + """Viet Nam""" + VN + + """Vanuatu""" + VU + + """Wallis and Futuna""" + WF + + """Samoa""" + WS + + """Yemen""" + YE + + """Mayotte""" + YT + + """South Africa""" + ZA + + """Zambia""" + ZM + + """Zimbabwe""" + ZW +} + +"""A medium of exchange in common use.""" +type Currency { + """Currency code.""" + code: CurrencyCode! + + """ + Expresses the relationship between a major currency unit and its minor currency unit. The number of digits found to the right of the decimal place to represent the fractional part of this currency (assumes a base of 10). + """ + exponent: Int! + + """Plain-language representation.""" + name: String! + + """Plural version of currency name.""" + plural: String! + + """Symbol used to denote that a number is a monetary value.""" + symbol: String! +} + +"""Currency codes based on ISO 4217.""" +enum CurrencyCode { + """UAE dirham""" + AED + + """Afghani""" + AFN + + """Lek""" + ALL + + """Armenian dram""" + AMD + + """Netherlands Antillean Guilder""" + ANG + + """Kwanza""" + AOA + + """Argentinian peso""" + ARS + + """Australian dollar""" + AUD + + """Aruban Guilder""" + AWG + + """New Manat""" + AZN + + """Convertible Marks""" + BAM + + """Barbados dollar""" + BBD + + """Taka""" + BDT + + """Lev""" + BGN + + """Bahraini dinar""" + BHD + + """Burundi franc""" + BIF + + """Bermuda dollar""" + BMD + + """Brunei dollar""" + BND + + """Boliviano""" + BOB + + """Real""" + BRL + + """Bahamian dollar""" + BSD + + """Ngultrum""" + BTN + + """Pula""" + BWP + + """Belarussian rouble""" + BYR + + """Belize dollar""" + BZD + + """Canadian dollar""" + CAD + + """Franc congolais""" + CDF + + """Swiss franc""" + CHF + + """Chilean peso""" + CLP + + """Ren-Min-Bi yuan""" + CNY + + """Colombian peso""" + COP + + """Costa Rican colon""" + CRC + + """Cuban peso""" + CUP + + """Cape Verde escudo""" + CVE + + """Czech koruna""" + CZK + + """Djibouti franc""" + DJF + + """Danish krone""" + DKK + + """Dominican peso""" + DOP + + """Algerian dinar""" + DZD + + """Estonian kroon""" + EEK + + """Egyptian pound""" + EGP + + """Nakfa""" + ERN + + """Ethiopian birr""" + ETB + + """Euro""" + EUR + + """Fiji dollar""" + FJD + + """Falkland Islands (Malvinas) Pound""" + FKP + + """Pound sterling""" + GBP + + """Lari""" + GEL + + """Ghana Cedi""" + GHS + + """Gibraltar pound""" + GIP + + """Dalasi""" + GMD + + """Guinean franc""" + GNF + + """Quetzal""" + GTQ + + """Guinean bissau Peso""" + GWP + + """Guyana dollar""" + GYD + + """Hong Kong dollar""" + HKD + + """Lempira""" + HNL + + """Kuna""" + HRK + + """Haitian gourde""" + HTG + + """Forint""" + HUF + + """Rupiah""" + IDR + + """New Israeli sheqel""" + ILS + + """Indian rupee""" + INR + + """Iraqi dinar""" + IQD + + """Iranian rial""" + IRR + + """Icelandic Krona""" + ISK + + """Jamaican dollar""" + JMD + + """Jordanian dinar""" + JOD + + """Yen""" + JPY + + """Kenyan shilling""" + KES + + """Kyrgyz Som""" + KGS + + """Riel""" + KHR + + """Comoro franc""" + KMF + + """Won""" + KRW + + """Kuwaiti dinar""" + KWD + + """Cayman Islands dollar""" + KYD + + """Tenge""" + KZT + + """Kip""" + LAK + + """Lebanese pound""" + LBP + + """Sri Lankan rupee""" + LKR + + """Liberian dollar""" + LRD + + """Loti""" + LSL + + """Lithuanian litus""" + LTL + + """Latvian lats""" + LVL + + """Libyan dinar""" + LYD + + """Moroccan dirham""" + MAD + + """Moldovan leu""" + MDL + + """Malagasy Ariary""" + MGA + + """Denar""" + MKD + + """Kyat""" + MMK + + """Tugrik""" + MNT + + """Pataca""" + MOP + + """Ouguiya""" + MRO + + """Ouguiya""" + MRU + + """Mauritian rupee""" + MUR + + """Rufiyaa""" + MVR + + """Kwacha""" + MWK + + """Mexican peso""" + MXN + + """Malaysian ringgit""" + MYR + + """Metical""" + MZN + + """Namibian dollar""" + NAD + + """Naira""" + NGN + + """Cordoba Oro""" + NIO + + """Norwegian krone""" + NOK + + """Nepalese rupee""" + NPR + + """New Zealand dollar""" + NZD + + """Omani rial""" + OMR + + """Balboa""" + PAB + + """Nuevo Sol""" + PEN + + """Kina""" + PGK + + """Philippine peso""" + PHP + + """Pakistani rupee""" + PKR + + """Zloty""" + PLN + + """Guarani""" + PYG + + """Qatari riyal""" + QAR + + """New Leu""" + RON + + """Serbian Dinar""" + RSD + + """Russian rouble""" + RUB + + """Rwanda franc""" + RWF + + """Saudi riyal""" + SAR + + """Solomon Islands Dollar""" + SBD + + """Seychelles rupee""" + SCR + + """Sudanese Pound""" + SDG + + """Swedish Krona""" + SEK + + """Singapore dollar""" + SGD + + """Saint Helena Pound""" + SHP + + """Leone""" + SLL + + """Somali shilling""" + SOS + + """Surinam dollar""" + SRD + + """South Sudanese pound""" + SSP + + """Dobra""" + STD + + """El Salvador colon""" + SVC + + """Syrian pound""" + SYP + + """Lilangeni""" + SZL + + """Baht""" + THB + + """Somoni""" + TJS + + """Manat""" + TMM + + """Tunisian dinar""" + TND + + """Pa'anga""" + TOP + + """Turkish Lira""" + TRY + + """Trinidad and Tobago dollar""" + TTD + + """Taiwan New Dollar""" + TWD + + """Tanzanian shilling""" + TZS + + """Hryvnia""" + UAH + + """Ugandan shilling""" + UGX + + """United States dollar""" + USD + + """Uruguayo peso""" + UYU + + """Uzbekistan sum""" + UZS + + """Bolivar Fuerte""" + VEF + + """Dong""" + VND + + """Vatu""" + VUV + + """Samoan Tala""" + WST + + """CFA Franc - BEAC""" + XAF + + """Eastern Caribbean dollar""" + XCD + + """CFA franc - BCEAO""" + XOF + + """Comptoirs Francais du Pacifique Francs""" + XPF + + """Yemeni rial""" + YER + + """Rand""" + ZAR + + """Kwacha""" + ZMK + + """Kwacha""" + ZMW + + """Zimbabwean dollar""" + ZWD +} + +"""A customer of the business.""" +type Customer implements BusinessNode & Node { + """Address of the customer.""" + address: Address + + """Business that the customer belongs to.""" + business: Business! + + """When the customer was created.""" + createdAt: DateTime! + + """Default currency used by the customer.""" + currency: Currency + + """ + User defined id for the customer. Commonly referred to as Account Number. + """ + displayId: String + + """Email of the principal contact.""" + email: String + + """Fax number of the customer.""" + fax: String + + """First name of the principal contact.""" + firstName: String + + """Unique identifier for the customer.""" + id: ID! + + """The primary key used internally at Wave.""" + internalId: String @deprecated(reason: "Exposed internal IDs will eventually be removed in favor of global ID. Use Node.id instead.") + + """Internal notes about the customer.""" + internalNotes: String + + """Whether or not the customer is archived.""" + isArchived: Boolean + + """Last name of the principal contact.""" + lastName: String + + """Mobile telephone number of the principal contact.""" + mobile: String + + """When the customer was last modified.""" + modifiedAt: DateTime! + + """Name or business name of the customer.""" + name: String! + + """Amount due on customer's invoices.""" + outstandingAmount: Money! + + """Amount due on customer's invoices with due date that have passed.""" + overdueAmount: Money! + + """Telephone number of the customer.""" + phone: String + + """Details for shipping to the customer.""" + shippingDetails: CustomerShippingDetails + + """Toll-free number of the customer.""" + tollFree: String + + """Website address of the customer.""" + website: String +} + +"""Customer connection.""" +type CustomerConnection { + """List of customers.""" + edges: [CustomerEdge!]! + + """Information about pagination.""" + pageInfo: OffsetPageInfo! +} + +"""Input to the `customerCreate` mutation.""" +input CustomerCreateInput { + """Address""" + address: AddressInput + + """The unique identifier for the business.""" + businessId: ID! + + """Default currency used by the customer.""" + currency: CurrencyCode + + """User defined id for the customer.""" + displayId: String + + """Email of the principal contact.""" + email: String + + """Fax number of the customer.""" + fax: String + + """First name of the principal contact.""" + firstName: String + + """Internal notes about the customer.""" + internalNotes: String + + """Last name of the principal contact.""" + lastName: String + + """Mobile telephone number of the principal contact.""" + mobile: String + + """Name or business name of the customer.""" + name: String! + + """Telephone number of the customer.""" + phone: String + + """Details for shipping to the customer.""" + shippingDetails: CustomerShippingDetailsInput + + """Toll-free number of the customer.""" + tollFree: String + + """Website address of the customer.""" + website: String +} + +"""Output of the `customerCreate` mutation.""" +type CustomerCreateOutput { + """Customer that was created.""" + customer: Customer + + """Indicates whether the customer was successfully created.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] +} + +"""Input to the `customerDelete` mutation.""" +input CustomerDeleteInput { + """The unique identifier for the customer.""" + id: ID! +} + +"""Output of the `customerDelete` mutation.""" +type CustomerDeleteOutput { + """Indicates whether the customer was successfully deleted.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] +} + +"""Customer edge.""" +type CustomerEdge { + """A customer.""" + node: Customer +} + +"""Input to the `customerPatch` mutation.""" +input CustomerPatchInput { + """Address""" + address: AddressInput + + """Default currency used by the customer.""" + currency: CurrencyCode + + """User defined id for the customer.""" + displayId: String + + """Email of the principal contact.""" + email: String + + """Fax number of the customer.""" + fax: String + + """First name of the principal contact.""" + firstName: String + + """The unique identifier for the customer.""" + id: ID! + + """Internal notes about the customer.""" + internalNotes: String + + """Last name of the principal contact.""" + lastName: String + + """Mobile telephone number of the principal contact.""" + mobile: String + + """Name or business name of the customer.""" + name: String + + """Telephone number of the customer.""" + phone: String + + """Details for shipping to the customer.""" + shippingDetails: CustomerPatchShippingDetailsInput + + """Toll-free number of the customer.""" + tollFree: String + + """Website address of the customer.""" + website: String +} + +"""Output of the `customerPatch` mutation.""" +type CustomerPatchOutput { + """Customer that was patched.""" + customer: Customer + + """Indicates whether the customer was successfully patched.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] +} + +"""Shipping details related to a customer.""" +input CustomerPatchShippingDetailsInput { + """Address of the customer.""" + address: AddressInput + + """Delivery instructions for handling.""" + instructions: String + + """Name or business name of the customer.""" + name: String + + """Telephone number of the customer.""" + phone: String +} + +"""Shipping details related to a customer.""" +type CustomerShippingDetails { + """Address of the customer.""" + address: Address + + """Delivery instructions for handling.""" + instructions: String + + """Name or business name of the customer.""" + name: String + + """Telephone number of the customer.""" + phone: String +} + +"""Shipping details related to a customer.""" +input CustomerShippingDetailsInput { + """Address of the customer.""" + address: AddressInput + + """Delivery instructions for handling.""" + instructions: String + + """Name or business name of the customer.""" + name: String + + """Telephone number of the customer.""" + phone: String +} + +"""Options by which customers can be ordered.""" +enum CustomerSort { + """Ascending by creation time.""" + CREATED_AT_ASC + + """Descending by creation time.""" + CREATED_AT_DESC + + """Ascending by modified time.""" + MODIFIED_AT_ASC + + """Descending by modified time.""" + MODIFIED_AT_DESC + + """Ascending by name.""" + NAME_ASC + + """Descending by name.""" + NAME_DESC +} + +"""ISO-8601 date object. Format returned will follow `yyyy-MM-dd`.""" +scalar Date + +""" +ISO-8601 date and time object. Format returned will follow `yyyy-MM-ddThh:mm:ss.sssZ` where the timezone is UTC. +""" +scalar DateTime + +""" +A signed decimal number, which supports arbitrary precision and is serialized as a string. Example value: `14.99`. +""" +scalar Decimal + +""" +An approximate bill given to a buyer indicating the products or services, quantities, and expected prices (not a request for payment). +""" +type Estimate implements BusinessNode & Node { + """Business that the Estimate belongs to""" + business: Business! + + """Unique identifier for the estimate.""" + id: ID! + + """The primary key used internally at Wave.""" + internalId: String @deprecated(reason: "Exposed internal IDs will eventually be removed in favor of global ID. Use Node.id instead.") +} + +"""A fixed discount applied to an Invoice.""" +type FixedInvoiceDiscount implements InvoiceDiscount { + """The amount of the discount.""" + amount: Decimal + + """When the invoice discount was created.""" + createdAt: DateTime! + + """When the invoice discount was last modified.""" + modifiedAt: DateTime! + + """A description of the discount.""" + name: String +} + +"""General settings on an invoice and estimate.""" +type GeneralSettings { + """Color to represent the brand of the business.""" + accentColor: HexColorCode + + """Logo of the business.""" + logoUrl: URL +} + +""" +A field whose value is a hex color code: https://en.wikipedia.org/wiki/Web_colors. +""" +scalar HexColorCode + +"""Mutation validation error.""" +type InputError { + """Error code.""" + code: String + + """Error message.""" + message: String + + """Path to the input value.""" + path: [String!] +} + +""" +Document issued to a buyer for payment indicating the products or services, quantities, and agreed prices. +""" +type Invoice implements BusinessNode & Node { + """Invoice total less amount already paid.""" + amountDue: Money! + + """Total of all payments so far made against this invoice.""" + amountPaid: Money! + + """ + The label for the 'Amount' (= unit x price) column in the listing of line items on the invoice. + """ + amountTitle: String! + + """Business that the invoice belongs to.""" + business: Business! + + """When the invoice was created.""" + createdAt: DateTime! + + """Currency of the invoice.""" + currency: Currency! + + """Customer the invoice is for.""" + customer: Customer! + + """ + Within a business that is enabled to accept credit card payments, indicates if this individual invoice has been marked to not be payable by American Express. + """ + disableAmexPayments: Boolean! + + """ + Within a business that is enabled to accept bank payments, indicates if this individual invoice has been marked to not be payable by bank payment. + """ + disableBankPayments: Boolean! + + """ + Within a business that is enabled to accept credit card payments, indicates if this individual invoice has been marked to not be payable by card. + """ + disableCreditCardPayments: Boolean! + + """Total value of all discounts.""" + discountTotal: Money! + + """Invoice discounts.""" + discounts: [InvoiceDiscount!] + + """Date when payment is due.""" + dueDate: Date! + + """ + Exchange rate to business's currency from the invoice's currency. Used to value the invoice income within Wave's accounting transactions. + """ + exchangeRate: Decimal! + + """Invoice footer text.""" + footer: String + + """Indicates whether item's amount is hidden in the line items listing.""" + hideAmount: Boolean! + + """ + Indicates whether item's description in item column is hidden in the line items listing. + """ + hideDescription: Boolean! + + """ + Indicates whether item's product name in item column is hidden in the line items listing. + """ + hideName: Boolean! + + """Indicates whether item's price is hidden in the line items listing.""" + hidePrice: Boolean! + + """Indicates whether item's unit is hidden in the line items listing.""" + hideUnit: Boolean! + + """Unique identifier for the invoice.""" + id: ID! + + """The primary key used internally at Wave.""" + internalId: String @deprecated(reason: "Exposed internal IDs will eventually be removed in favor of global ID. Use Node.id instead.") + + """Date when invoice is issued.""" + invoiceDate: Date! + + """Unique number assigned to the invoice.""" + invoiceNumber: String! + + """The label for the 'Item' column in the line items listing.""" + itemTitle: String! + + """ + The line items (product, unit and price) that make up the invoiced sale. + """ + items: [InvoiceItem!] + + """When the invoice was last sent.""" + lastSentAt: DateTime + + """How the invoice was last sent.""" + lastSentVia: InvoiceSendMethod + + """When the invoice was last viewed by the customer.""" + lastViewedAt: DateTime + + """Invoice memo (notes) text.""" + memo: String + + """When the invoice was last modified.""" + modifiedAt: DateTime! + + """URL to access PDF representation of the invoice.""" + pdfUrl: String! + + """Purchase order or sales order number for the invoice.""" + poNumber: String + + """ + The label for the 'Price' column in the listing of line items on the invoice. + """ + priceTitle: String! + + """ + Indicates whether the customer is required to accept the terms of service. + """ + requireTermsOfServiceAgreement: Boolean! + + """Entity that was the precursor to the invoice.""" + source: InvoiceSource + + """Status of the Invoice.""" + status: InvoiceStatus! + + """Invoice subheading text.""" + subhead: String + + """Pretax total.""" + subtotal: Money! + + """Total of all sales taxes on all line items within the invoice.""" + taxTotal: Money! + + """Invoice title at the top of the document.""" + title: String! + + """Total value of the invoice including sales taxes.""" + total: Money! + + """ + The label for the 'Unit' column in the listing of line items on the invoice. + """ + unitTitle: String! + + """URL to view the invoice online as seen by a customer.""" + viewUrl: String! +} + +"""Input to the `invoiceApprove` mutation.""" +input InvoiceApproveInput { + """The unique identifier for the invoice.""" + invoiceId: ID! +} + +"""Output of the `invoiceApprove` mutation.""" +type InvoiceApproveOutput { + """Indicates whether the invoice was successfully approved.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Invoice that was approved.""" + invoice: Invoice +} + +"""Input to the `invoiceClone` mutation.""" +input InvoiceCloneInput { + """The unique identifier for the invoice.""" + invoiceId: ID! +} + +"""Output of the `invoiceClone` mutation.""" +type InvoiceCloneOutput { + """Indicates whether the invoice was successfully cloned.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Invoice that was cloned.""" + invoice: Invoice +} + +"""Invoice connection.""" +type InvoiceConnection { + """List of invoices.""" + edges: [InvoiceEdge!]! + + """Information about pagination.""" + pageInfo: OffsetPageInfo! +} + +"""Input to the `invoiceCreate` mutation.""" +input InvoiceCreateInput { + """ + The label for the 'Amount' (= unit x price) column in the listing of line items on the invoice. If not provided, will use the business's default invoice column amount title. + """ + amountTitle: String + + """The unique identifier for the business.""" + businessId: ID! + + """ + Currency of the invoice. If not provided, will use the business's default currency. + """ + currency: CurrencyCode + + """The customer identifier to associate with invoice.""" + customerId: ID! + + """ + Within a business that is enabled to accept credit card payments, indicates if this individual invoice has been marked to not be payable by american express payment. If not provided, will use the business's default invoice settings american express payment state. + """ + disableAmexPayments: Boolean + + """ + Within a business that is enabled to accept bank payments, indicates if this individual invoice has been marked to not be payable by bank payment. If not provided, will use the business's default invoice bank payment state. + """ + disableBankPayments: Boolean + + """ + Within a business that is enabled to accept credit card payments, indicates if this individual invoice has been marked to not be payable by card. If not provided, will use the business's default invoice credit card payment state. + """ + disableCreditCardPayments: Boolean + + """The discounts applied to the invoice (currently limited to max 1).""" + discounts: [InvoiceDiscountInput!] + + """ + Date when payment is due. If not provided, will apply the business's default invoice payment terms to `invoiceDate` value. + """ + dueDate: Date + + """ + Exchange rate to business's currency from the invoice's currency. Used to value the invoice income within Wave's accounting transactions. + """ + exchangeRate: Decimal + + """ + Invoice footer text. If not provided, will use the business's default invoice footer. + """ + footer: String + + """ + Indicates whether item's amount is hidden in the line items listing. If not provided, will use the business's default invoice item amount visibility. + """ + hideAmount: Boolean + + """ + Indicates whether item's description in item column is hidden in the line items listing. If not provided, will use the business's default invoice item description visibility. + """ + hideDescription: Boolean + + """ + Indicates whether item's product name in item column is hidden in the line items listing. If not provided, will use the business's default invoice item name visibility. + """ + hideName: Boolean + + """ + Indicates whether item's price is hidden in the line items listing. If not provided, will use the business's default invoice item price visibility. + """ + hidePrice: Boolean + + """ + Indicates whether item's unit is hidden in the line items listing. If not provided, will use the business's default invoice item unit visibility. + """ + hideUnit: Boolean + + """Date when invoice is issued. If not provided, will use today's date.""" + invoiceDate: Date + + """ + Unique number assigned to the invoice. If not provided, will find the current largest invoice number and add 1. + """ + invoiceNumber: String + + """ + The label for the 'Item' column in the listing of line items on the invoice. If not provided, will use the business's default invoice column item title. + """ + itemTitle: String + + """ + The line items (product, unit and price) that make up the invoiced sale. + """ + items: [InvoiceCreateItemInput!] + + """ + Invoice memo (notes) text. If not provided, will use the business's default invoice memo. + """ + memo: String + + """Purchase order or sales order number for the invoice.""" + poNumber: String + + """ + The label for the 'Price' column in the listing of line items on the invoice. If not provided, will use the business's default invoice column price title. + """ + priceTitle: String + + """ + Indicates whether the customer is required to accept the terms of service. + """ + requireTermsOfServiceAgreement: Boolean + + """Status of the Invoice.""" + status: InvoiceCreateStatus = DRAFT + + """ + Invoice subheading text. If not provided, will use the business's default invoice subheading. + """ + subhead: String + + """ + Invoice title at the top of the document. If not provided, will use the business's default invoice title. + """ + title: String + + """ + The label for the 'Unit' column in the listing of line items on the invoice. If not provided, will use the business's default invoice column unit title. + """ + unitTitle: String +} + +"""Invoice line item.""" +input InvoiceCreateItemInput { + """Override product's description.""" + description: String + + """Associated product.""" + productId: ID! + + """ + Number of units (rounded to nearest 8 decimal places with ties going away from zero). + """ + quantity: Decimal + + """ + Taxes. To have the product's default sales taxes applied, provide `undefined` as the value. + """ + taxes: [InvoiceCreateItemTaxInput!] + + """ + Override product's unitPrice. Price per unit in the major currency unit (rounded to nearest 8 decimal places with ties going away from zero). + """ + unitPrice: Decimal +} + +"""Invoice line item's sales tax.""" +input InvoiceCreateItemTaxInput { + """ + *DEPRECATED - DO NOT USE* Sales Tax Amount is calculated by Wave using your Sales Tax settings. + """ + amount: Decimal + + """Sales tax.""" + salesTaxId: ID! +} + +"""Output of the `invoiceCreate` mutation.""" +type InvoiceCreateOutput { + """Indicates whether the invoice was successfully created.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Invoice that was created.""" + invoice: Invoice +} + +"""Status of an invoice.""" +enum InvoiceCreateStatus { + """The invoice is still a draft.""" + DRAFT + + """The invoice was saved.""" + SAVED +} + +"""Input to the `invoiceDelete` mutation.""" +input InvoiceDeleteInput { + """The unique identifier for the invoice.""" + invoiceId: ID! +} + +"""Output of the `invoiceDelete` mutation.""" +type InvoiceDeleteOutput { + """Indicates whether the invoice was successfully deleted.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] +} + +"""Common base properties of InvoiceDiscounts.""" +interface InvoiceDiscount { + """When the invoice discount was created.""" + createdAt: DateTime! + + """When the invoice discount was last modified.""" + modifiedAt: DateTime! + + """A description of the discount.""" + name: String +} + +"""Invoice Discount.""" +input InvoiceDiscountInput { + """Discount amount (for FIXED-type discounts).""" + amount: Decimal + + """Discount type.""" + discountType: InvoiceDiscountType! + + """Discount name.""" + name: String + + """Discount percentage (for PERCENTAGE-type discounts).""" + percentage: Decimal +} + +"""Type of invoice discount.""" +enum InvoiceDiscountType { + """Fixed dollar amount discount.""" + FIXED + + """Type of invoice discount.""" + PERCENTAGE +} + +"""Invoice edge.""" +type InvoiceEdge { + """An invoice.""" + node: Invoice! +} + +"""Business invoice and estimates settings information.""" +type InvoiceEstimateSettings { + """Settings applied to both invoices and estimates.""" + generalSettings: GeneralSettings! +} + +"""Invoice line item.""" +type InvoiceItem { + """Income account.""" + account: Account! + + """Detailed description.""" + description: String + + """Price per unit.""" + price: Decimal! @deprecated(reason: "Use unitPrice to avoid ambiguity in how the value relates to quantity and the subtotal.") + + """Associated product.""" + product: Product! + + """Number of units.""" + quantity: Decimal! + + """Pretax total.""" + subtotal: Money! + + """Taxes.""" + taxes: [InvoiceItemTax!]! + + """Total including sales taxes.""" + total: Money! + + """Price per unit in the major currency unit.""" + unitPrice: Decimal! +} + +"""Invoice line item's sales tax.""" +type InvoiceItemTax { + """Sales tax amount.""" + amount: Money + + """Sales tax rate.""" + rate: Decimal @deprecated(reason: "Use `salesTax.rate`. `rate` will be removed on Oct 20th 2022.") + + """Sales tax.""" + salesTax: SalesTax! +} + +"""Input to the `invoiceMarkSent` mutation.""" +input InvoiceMarkSentInput { + """The unique identifier for the invoice.""" + invoiceId: ID! + + """How the invoice was sent.""" + sendMethod: InvoiceSendMethod! + + """When the invoice was sent.""" + sentAt: DateTime +} + +"""Output of the `invoiceMarkSent` mutation.""" +type InvoiceMarkSentOutput { + """Indicates whether the invoice was successfully marked as sent.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Invoice that was marked as sent.""" + invoice: Invoice +} + +""" +Input to the `invoicePatch` mutation. For each value if it's not provided - do not update it. +""" +input InvoicePatchInput { + """ + The label for the 'Amount' (= unit x price) column in the listing of line items on the invoice. + """ + amountTitle: String + + """Currency of the invoice.""" + currency: CurrencyCode + + """The customer identifier to associate with invoice.""" + customerId: ID + + """ + Within a business that is enabled to accept credit card payments, indicates if this individual invoice has been marked to not be payable by american express payment. + """ + disableAmexPayments: Boolean + + """ + Within a business that is enabled to accept bank payments, indicates if this individual invoice has been marked to not be payable by bank payment. + """ + disableBankPayments: Boolean + + """ + Within a business that is enabled to accept credit card payments, indicates if this individual invoice has been marked to not be payable by card. + """ + disableCreditCardPayments: Boolean + + """The discounts applied to the invoice (currently limited to max 1).""" + discounts: [InvoiceDiscountInput!] + + """Date when payment is due.""" + dueDate: Date + + """ + Exchange rate to business's currency from the invoice's currency. Used to value the invoice income within Wave's accounting transactions. + """ + exchangeRate: Decimal + + """Invoice footer text.""" + footer: String + + """Indicates whether item's amount is hidden in the line items listing.""" + hideAmount: Boolean + + """ + Indicates whether item's description in item column is hidden in the line items listing. + """ + hideDescription: Boolean + + """ + Indicates whether item's product name in item column is hidden in the line items listing. + """ + hideName: Boolean + + """Indicates whether item's price is hidden in the line items listing.""" + hidePrice: Boolean + + """Indicates whether item's unit is hidden in the line items listing.""" + hideUnit: Boolean + + """Unique identifier for the invoice.""" + id: ID! + + """Date when invoice is issued.""" + invoiceDate: Date + + """Unique number assigned to the invoice.""" + invoiceNumber: String + + """ + The label for the 'Item' column in the listing of line items on the invoice. + """ + itemTitle: String + + """ + The line items (product, unit and price) that make up the invoiced sale. If provided, it would replace all items with given ones. + """ + items: [InvoiceCreateItemInput!] + + """Invoice memo (notes) text.""" + memo: String + + """Purchase order or sales order number for the invoice.""" + poNumber: String + + """ + The label for the 'Price' column in the listing of line items on the invoice. + """ + priceTitle: String + + """ + Indicates whether the customer is required to accept the terms of service. + """ + requireTermsOfServiceAgreement: Boolean + + """Status of the Invoice.""" + status: InvoiceCreateStatus + + """Invoice subheading text.""" + subhead: String + + """Invoice title at the top of the document.""" + title: String + + """ + The label for the 'Unit' column in the listing of line items on the invoice. + """ + unitTitle: String +} + +"""Output of the `invoicePatch` mutation.""" +type InvoicePatchOutput { + """Indicates whether the invoice was successfully created.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Invoice that was created.""" + invoice: Invoice +} + +"""Input to the `invoiceSend` mutation.""" +input InvoiceSendInput { + """Include a PDF of the invoice as an attachment.""" + attachPDF: Boolean! = false + + """Carbon copy email.""" + ccMyself: Boolean + + """Email address from""" + fromAddress: String + + """The unique identifier for the invoice.""" + invoiceId: ID! + + """Message body of the email.""" + message: String + + """Subject line of the email.""" + subject: String + + """Email addresses to receive an email.""" + to: [String!]! +} + +"""Invoice send method.""" +enum InvoiceSendMethod { + """Export PDF.""" + EXPORT_PDF + + """Gmail""" + GMAIL + + """Marked as sent.""" + MARKED_SENT + + """Not sent.""" + NOT_SENT + + """Outlook.""" + OUTLOOK + + """Shared link.""" + SHARED_LINK + + """Skipped.""" + SKIPPED + + """Wave.""" + WAVE + + """Yahoo.""" + YAHOO +} + +"""Output of the `invoiceSend` mutation.""" +type InvoiceSendOutput { + """Indicates whether the invoice was successfully queued for sending.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Invoice that was sent.""" + invoice: Invoice +} + +"""Options by which invoices can be ordered.""" +enum InvoiceSort { + """Ascending by amount due.""" + AMOUNT_DUE_ASC + + """Descending by amount due.""" + AMOUNT_DUE_DESC + + """Ascending by amount paid.""" + AMOUNT_PAID_ASC + + """Descending by amount paid.""" + AMOUNT_PAID_DESC + + """Ascending by creation time.""" + CREATED_AT_ASC + + """Descending by creation time.""" + CREATED_AT_DESC + + """Ascending by customer's name.""" + CUSTOMER_NAME_ASC + + """Descending by customer's name.""" + CUSTOMER_NAME_DESC + + """Ascending by due date.""" + DUE_AT_ASC + + """Descending by due date.""" + DUE_AT_DESC + + """Ascending by invoice date.""" + INVOICE_DATE_ASC + + """Descending by invoice date.""" + INVOICE_DATE_DESC + + """Ascending by invoice number.""" + INVOICE_NUMBER_ASC + + """Descending by invoice number.""" + INVOICE_NUMBER_DESC + + """Ascending by modified date.""" + MODIFIED_AT_ASC + + """Descending by modified date.""" + MODIFIED_AT_DESC + + """Ascending by status.""" + STATUS_ASC + + """Descending by status.""" + STATUS_DESC + + """Ascending by total amount.""" + TOTAL_ASC + + """Descending by total amount.""" + TOTAL_DESC +} + +""" +Specifies either the invoice is made from estimate, is recurring, or created manually. +""" +union InvoiceSource = Estimate | NewEstimate | RecurringInvoice + +"""Status of an invoice.""" +enum InvoiceStatus { + """The invoice is still a draft.""" + DRAFT + + """The invoice is overdue.""" + OVERDUE + + """The invoice was overpaid.""" + OVERPAID + + """The invoice was paid.""" + PAID + + """The invoice was partially paid.""" + PARTIAL + + """The invoice was saved.""" + SAVED + + """The invoice was sent.""" + SENT + + """The invoice is unpaid.""" + UNPAID + + """The invoice was viewed.""" + VIEWED +} + +""" +The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). +""" +scalar JSON + +"""A medium of exchange in common use.""" +type Money { + """Currency""" + currency: Currency! + + """Value represented in only the minor currency unit.""" + minorUnitValue: Decimal! + + """Value represented in only the minor currency unit.""" + raw: Int! @deprecated(reason: "Use `minorUnitValue` instead, as `raw` can overflow for large numbers.") + + """ + Amount represented as a combination of the major and minor currency unit (uses a decimal separator). + """ + value: String! +} + +"""Input representing a deposit.""" +input MoneyDepositTransactionCreateDepositInput { + """Id of the account.""" + accountId: ID! + + """Date of the transaction.""" + amount: Float! +} + +"""Fee input.""" +input MoneyDepositTransactionCreateFeeInput { + """ID of the account associated with the fee.""" + accountId: ID! + + """Amount.""" + amount: Float! +} + +"""Input of the moneyDepositTransactionCreate Mutation""" +input MoneyDepositTransactionCreateInput { + """Id of the business.""" + businessId: ID! + + """Transaction timestamp.""" + createdAt: DateTime + + """Date of the transaction.""" + date: Date! + + """Deposit account and amount.""" + deposit: MoneyDepositTransactionCreateDepositInput! + + """Description for the transaction.""" + description: String! + + """ID of the transaction in an external system.""" + externalId: String + + """Fees.""" + fees: [MoneyDepositTransactionCreateFeeInput!] + + """Line items.""" + lineItems: [MoneyDepositTransactionCreateLineItemInput!]! + + """Extra notes about the transaction.""" + notes: String + + """Origin of the transaction.""" + origin: TransactionOrigin! +} + +"""Line item input.""" +input MoneyDepositTransactionCreateLineItemInput { + """ID of the account associated with the line item.""" + accountId: ID! + + """Amount.""" + amount: Float! + + """ID of the customer associated with the line item.""" + customerId: ID + + """Taxes applied to the line item.""" + taxes: [TransactionCreateSalesTaxInput!]! +} + +"""Output of the moneyDepositTransactionCreate Mutation""" +type MoneyDepositTransactionCreateOutput { + """Whether or not the transaction was successfully created.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] +} + +"""Anchor input.""" +input MoneyTransactionCreateAnchorInput { + """ID of the anchor account.""" + accountId: ID! + + """Amount of the transaction (unsigned).""" + amount: Decimal! + + """Direction of a transaction""" + direction: TransactionDirection! +} + +"""Input of the `moneyTransactionCreate` Mutation""" +input MoneyTransactionCreateInput { + """Anchor item.""" + anchor: MoneyTransactionCreateAnchorInput! + + """The unique identifier for the business.""" + businessId: ID! + + """Date of the transaction.""" + date: Date! + + """Description for the transaction.""" + description: String! + + """ + ID of the transaction in an external system. If you don't have one, generate a UUID and provide it. + """ + externalId: String! + + """Line items.""" + lineItems: [MoneyTransactionCreateLineItemInput!]! + + """Extra notes about the transaction.""" + notes: String +} + +"""Line item input.""" +input MoneyTransactionCreateLineItemInput { + """ID of the account associated with the line item.""" + accountId: ID! + + """Amount of the line item (unsigned).""" + amount: Decimal! + + """How the account should change in relation to the amount.""" + balance: BalanceType! = INCREASE + + """ID of the customer associated with the line item.""" + customerId: ID + + """Optional description for line item.""" + description: String + + """Taxes applied to the line item.""" + taxes: [MoneyTransactionCreateSalesTaxInput!] +} + +"""Output of the `moneyTransactionCreate` Mutation""" +type MoneyTransactionCreateOutput { + """Whether or not the transaction was successfully created.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Created transaction.""" + transaction: Transaction +} + +"""Sales tax input.""" +input MoneyTransactionCreateSalesTaxInput { + """Override the amount of the tax (unsigned).""" + amount: Decimal! + + """ID of the sales tax.""" + salesTaxId: ID! +} + +"""Input for creating a money transaction.""" +input MoneyTransactionDetails { + """Anchor item.""" + anchor: MoneyTransactionCreateAnchorInput! + + """Date of the transaction.""" + date: Date! + + """Description for the transaction.""" + description: String! + + """ + ID of the transaction in an external system. If you don't have one, generate a UUID and provide it. + """ + externalId: String! + + """Line items.""" + lineItems: [MoneyTransactionCreateLineItemInput!]! + + """Extra notes about the transaction.""" + notes: String +} + +"""Input of the `moneyTransactionsCreate` Mutation""" +input MoneyTransactionsCreateInput { + """The unique identifier for the business.""" + businessId: ID! + + """Array of transactions to create.""" + transactions: [MoneyTransactionDetails!]! +} + +"""Output of the `moneyTransactionsCreate` Mutation""" +type MoneyTransactionsCreateOutput { + """Whether or not all transactions were successfully created.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Created transactions.""" + transactions: [Transaction] +} + +"""The schema’s entry point for mutations.""" +type Mutation { + """No-op placeholder for code generation.""" + _: Boolean + + """Archive an account.""" + accountArchive(input: AccountArchiveInput!): AccountArchiveOutput + + """Create an account.""" + accountCreate(input: AccountCreateInput!): AccountCreateOutput + + """Patch an account.""" + accountPatch(input: AccountPatchInput!): AccountPatchOutput + + """Create a customer.""" + customerCreate(input: CustomerCreateInput!): CustomerCreateOutput + + """Delete customer.""" + customerDelete(input: CustomerDeleteInput!): CustomerDeleteOutput + + """Patch a customer.""" + customerPatch(input: CustomerPatchInput!): CustomerPatchOutput + + """Approve an invoice.""" + invoiceApprove(input: InvoiceApproveInput!): InvoiceApproveOutput + + """Clones an invoice.""" + invoiceClone(input: InvoiceCloneInput!): InvoiceCloneOutput + + """Create an invoice.""" + invoiceCreate(input: InvoiceCreateInput!): InvoiceCreateOutput + + """Delete an invoice.""" + invoiceDelete(input: InvoiceDeleteInput!): InvoiceDeleteOutput + + """Mark the invoice as sent.""" + invoiceMarkSent(input: InvoiceMarkSentInput!): InvoiceMarkSentOutput + + """Patch an invoice.""" + invoicePatch(input: InvoicePatchInput!): InvoicePatchOutput + + """Send an invoice. Requires `Business.emailSendEnabled` to be true.""" + invoiceSend(input: InvoiceSendInput!): InvoiceSendOutput + + """Create a money transaction.""" + moneyDepositTransactionCreate(input: MoneyDepositTransactionCreateInput!): MoneyDepositTransactionCreateOutput @deprecated(reason: "Not available for public use at this time.") + + """ + **BETA**: Create money transaction. Requires `isClassicAccounting` to be `false`. + """ + moneyTransactionCreate(input: MoneyTransactionCreateInput!): MoneyTransactionCreateOutput + + """ + **BETA**: Bulk create money transactions. Requires `isClassicAccounting` to be `false`. + """ + moneyTransactionsCreate(input: MoneyTransactionsCreateInput!): MoneyTransactionsCreateOutput + + """Archive a product.""" + productArchive(input: ProductArchiveInput!): ProductArchiveOutput + + """Create a product.""" + productCreate(input: ProductCreateInput!): ProductCreateOutput + + """Patch a product.""" + productPatch(input: ProductPatchInput!): ProductPatchOutput + + """Archive a sales tax.""" + salesTaxArchive(input: SalesTaxArchiveInput!): SalesTaxArchiveOutput! + + """Create a sales tax.""" + salesTaxCreate(input: SalesTaxCreateInput!): SalesTaxCreateOutput! + + """Update a sales tax.""" + salesTaxPatch(input: SalesTaxPatchInput!): SalesTaxPatchOutput! +} + +"""An estimate created in our new platform.""" +type NewEstimate implements BusinessNode & Node { + """Business that the Estimate belongs to""" + business: Business! + + """Unique identifier for the estimate.""" + id: ID! +} + +"""An object with an `ID`.""" +interface Node { + """ID of the object.""" + id: ID! +} + +"""An OAuth application.""" +type OAuthApplication implements Node { + """ + The client identifier issued to the client during the registration process. + """ + clientId: String! + + """When the application was created.""" + createdAt: DateTime! + + """A description of the application.""" + description: String + + """ + Additional data for the application. + - If the requested `clientId` does not match that of the current OAuth application, `extraData` will not be returned. + """ + extraData: JSON + + """The unique identifier for the application.""" + id: ID! + + """The URL to the application logo.""" + logoUrl: URL + + """When the application was last modified.""" + modifiedAt: DateTime! + + """The name of the application.""" + name: String! +} + +"""Information about pagination in a connection.""" +type OffsetPageInfo { + """Current page number.""" + currentPage: Int! + + """Total number of nodes in the connection.""" + totalCount: Int + + """Total number of pages in the connection.""" + totalPages: Int +} + +"""Forms of business ownership.""" +enum OrganizationalType { + """Corporation""" + CORPORATION + + """Partnership""" + PARTNERSHIP + + """Sole Proprietorship""" + SOLE_PROPRIETORSHIP +} + +"""A percentage discount applied to an Invoice.""" +type PercentageInvoiceDiscount implements InvoiceDiscount { + """When the invoice discount was created.""" + createdAt: DateTime! + + """When the invoice discount was last modified.""" + modifiedAt: DateTime! + + """A description of the discount.""" + name: String + + """The percentage of the discount.""" + percentage: Decimal +} + +""" +Product (or service) that a business sells to a customer or purchases from a vendor. +""" +type Product implements BusinessNode & Node { + """Business that the product belongs to.""" + business: Business! + + """When the product was created.""" + createdAt: DateTime! + + """Default sales taxes to apply on product.""" + defaultSalesTaxes: [SalesTax!]! + + """Description of the product.""" + description: String + + """The expense account to associate with this product, set when isBought.""" + expenseAccount: Account + + """Unique identifier for the product.""" + id: ID! + + """The income account to associate with this product, set when isSold.""" + incomeAccount: Account + + """The primary key used internally at Wave.""" + internalId: String @deprecated(reason: "Exposed internal IDs will eventually be removed in favor of global ID. Use Node.id instead.") + + """Is the product hidden from view by default.""" + isArchived: Boolean! + + """ + Is product bought by the business. Allow this product or service to be added to Bills. + """ + isBought: Boolean! + + """ + Is product sold by the business. Allow this product or service to be added to Invoices. + """ + isSold: Boolean! + + """When the product was last modified.""" + modifiedAt: DateTime! + + """Name of the product.""" + name: String! + + """Price per unit in the major currency unit.""" + unitPrice: Decimal! +} + +"""Input to the `productArchive` mutation.""" +input ProductArchiveInput { + """The unique identifier for the product.""" + id: ID! +} + +"""Output of the `productArchive` mutation.""" +type ProductArchiveOutput { + """Indicates whether the product was successfully deleted.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Product that was archived.""" + product: Product +} + +"""Product connection.""" +type ProductConnection { + """List of products.""" + edges: [ProductEdge!]! + + """Information about pagination.""" + pageInfo: OffsetPageInfo! +} + +"""Input to the `productCreate` mutation.""" +input ProductCreateInput { + """The unique identifier for the business.""" + businessId: ID! + + """Default sales taxes to apply on product.""" + defaultSalesTaxIds: [ID!] + + """Product description.""" + description: String + + """ + Expense account to associate with this product. Account must be one of subtypes: `EXPENSE`, `COST_OF_GOODS_SOLD`, `PAYMENT_PROCESSING_FEES`, `PAYROLL_EXPENSES`. + """ + expenseAccountId: ID + + """ + Income account to associate with this product. Account must be one of subtypes: `INCOME`, `DISCOUNTS`, `OTHER_INCOME`. + """ + incomeAccountId: ID + + """Name of the product.""" + name: String! + + """ + Price per unit in the major currency unit (rounded to nearest 5 decimal places with ties going away from zero). + """ + unitPrice: Decimal! +} + +"""Output of the `productCreate` mutation.""" +type ProductCreateOutput { + """Indicates whether the product was successfully created.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Product that was created.""" + product: Product +} + +"""Product edge.""" +type ProductEdge { + """A product.""" + node: Product! +} + +"""Input to the `productPatch` mutation.""" +input ProductPatchInput { + """Default sales taxes to apply on product.""" + defaultSalesTaxIds: [ID!] + + """Description of the product.""" + description: String + + """ + Expense account to associate with this product. Account must be one of subtypes: `EXPENSE`, `COST_OF_GOODS_SOLD`, `PAYMENT_PROCESSING_FEES`, `PAYROLL_EXPENSES`. + """ + expenseAccountId: ID + + """The unique identifier for the product.""" + id: ID! + + """ + Income account to associate with this product. Account must be one of subtypes: `INCOME`, `DISCOUNTS`, `OTHER_INCOME`. + """ + incomeAccountId: ID + + """Name of the product.""" + name: String + + """ + Price per unit in the major currency unit (rounded to nearest 5 decimal places with ties going away from zero). + """ + unitPrice: Decimal +} + +"""Output of the `productPatch` mutation.""" +type ProductPatchOutput { + """Indicates whether the product was successfully patched.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Product that was updated.""" + product: Product +} + +"""Options by which products can be ordered.""" +enum ProductSort { + """Ascending by creation time.""" + CREATED_AT_ASC + + """Descending by creation time.""" + CREATED_AT_DESC + + """Ascending by modified time.""" + MODIFIED_AT_ASC + + """Descending by modified time.""" + MODIFIED_AT_DESC + + """Ascending by name.""" + NAME_ASC + + """Descending by name.""" + NAME_DESC +} + +"""A state/county/province/region.""" +type Province { + """[ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) identifier.""" + code: String! + + """Plain-lanuage representaton.""" + name: String! + + """Informal name for identification.""" + slug: String @deprecated(reason: "Nonstandard values. Use code instead.") +} + +"""The schema’s entry point for queries.""" +type Query { + """No-op placeholder for code generation.""" + _: Boolean + + """List subtypes of accounts.""" + accountSubtypes: [AccountSubtype!]! + + """List types of accounts.""" + accountTypes: [AccountType!]! + + """Get a business.""" + business( + """ + ID of business. + - If defined, it will fetch that business. + - If not defined and the access token is restricted to a single business, it will fetch that business. + - If not defined and the access token can access multiple businesses, it will fetch the user's default business. To set a default business see https://support.waveapps.com/hc/en-us/articles/208621226. + """ + id: ID + ): Business + + """List businesses.""" + businesses( + """ + Filter by archived status. If not provided, excludes archived businesses by default. + """ + isArchived: Boolean + + """1-based page number to retrieve.""" + page: Int = 1 + + """Limit on how many items each page should return.""" + pageSize: Int = 10 + ): BusinessConnection + + """List countries.""" + countries: [Country!]! + + """Get a country.""" + country( + """Code of country.""" + code: CountryCode! + ): Country + + """List currencies.""" + currencies: [Currency!]! + + """Get a currency.""" + currency( + """Code of currency.""" + code: CurrencyCode! + ): Currency + + """Get the current OAuth application.""" + oAuthApplication: OAuthApplication + + """Get a province.""" + province( + """Code of province.""" + code: String! + ): Province + + """The currently authenticated user.""" + user: User +} + +""" +A template that can be used to generate and possibly pay an invoice at regular intervals. +""" +type RecurringInvoice implements BusinessNode & Node { + """Business that the RecurringInvoice belongs to""" + business: Business! + + """Unique identifier for the recurring invoice.""" + id: ID! + + """The primary key used internally at Wave.""" + internalId: String @deprecated(reason: "Exposed internal IDs will eventually be removed in favor of global ID. Use Node.id instead.") +} + +""" +A tax paid to a taxing authority for the sales of certain goods and services. +""" +type SalesTax implements BusinessNode & Node { + """A short form or code representing the sales tax.""" + abbreviation: String! + + """Business that the sales tax belongs to.""" + business: Business! + + """When the sales tax was created.""" + createdAt: DateTime! + + """User defined description for the sales tax.""" + description: String + + """The unique identifier for the sales tax.""" + id: ID! + + """The primary key used internally at Wave.""" + internalId: String @deprecated(reason: "Exposed internal IDs will eventually be removed in favor of global ID. Use Node.id instead.") + + """Is the sales tax hidden from view by default.""" + isArchived: Boolean! + + """ + Is a compound tax, or stacked tax. This tax is calculated on top of the subtotal and other tax amounts. + """ + isCompound: Boolean! + + """ + Is a recoverable tax. It is recoverable if you can deduct the tax that you as a business paid from the tax that you have collected. + """ + isRecoverable: Boolean! + + """When the sales tax was last modified.""" + modifiedAt: DateTime! + + """Name of the tax.""" + name: String! + + """ + Tax rate effective on 'for' date, or current date if no parameter, as a decimal (e.g. 0.15 represents 15%). + """ + rate(for: Date): Decimal! + + """Tax rates with their effective dates of application""" + rates: [SalesTaxRate!]! + + """Display tax number beside the tax name on an invoice.""" + showTaxNumberOnInvoices: Boolean! + + """The tax's issued identification number from a taxing authority.""" + taxNumber: String +} + +"""Input to the `salesTaxArchive` mutation.""" +input SalesTaxArchiveInput { + """The unique identifier for the sales tax.""" + id: ID! +} + +"""Output of the `salesTaxArchive` mutation.""" +type SalesTaxArchiveOutput { + """Indicates whether the sales tax was successfully deleted.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Sales tax that was archived.""" + salesTax: SalesTax +} + +"""Sales tax connection.""" +type SalesTaxConnection { + """List of sales taxes.""" + edges: [SalesTaxEdge!]! + + """Information about pagination.""" + pageInfo: OffsetPageInfo! +} + +"""Input to the `salesTaxCreate` mutation.""" +input SalesTaxCreateInput { + """ + An short form or code representing the sales tax. Max 10 characters, and MUST BE UNIQUE within business. + """ + abbreviation: String! + + """The unique identifier for the business.""" + businessId: ID! + + """User defined description for the sales tax.""" + description: String + + """ + Is a compound tax, or stacked tax. This tax is calculated on top of the subtotal and other tax amounts. + """ + isCompound: Boolean + + """ + Is a recoverable tax. It is recoverable if you can deduct the tax that you as a business paid from the tax that you have collected. + """ + isRecoverable: Boolean + + """Name of the tax.""" + name: String! + + """ + The current rate, as a decimal (e.g. 0.15 represents 15%; rounded to nearest 6 decimal places with ties going away from zero). + """ + rate: Decimal! + + """Display tax number beside the tax name on an invoice.""" + showTaxNumberOnInvoices: Boolean + + """The tax's issued identification number from a taxing authority.""" + taxNumber: String +} + +"""Output of the `salesTaxCreate` mutation.""" +type SalesTaxCreateOutput { + """Indicates whether the sales tax was successfully created.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Sales tax that was created.""" + salesTax: SalesTax +} + +"""Sales tax edge.""" +type SalesTaxEdge { + """A sales tax.""" + node: SalesTax +} + +"""Input to the `salesTaxPatch` mutation.""" +input SalesTaxPatchInput { + """An short form or code representing the sales tax""" + abbreviation: String + + """User defined description for the sales tax.""" + description: String + + """The unique identifier for the sales tax.""" + id: ID! + + """Name of the tax.""" + name: String + + """Tax rate information.""" + rates: [SalesTaxRateInput!] + + """Display tax number beside the tax name on an invoice.""" + showTaxNumberOnInvoices: Boolean + + """The tax's issued identification number from a taxing authority.""" + taxNumber: String +} + +"""Output of the `salesTaxPatch` mutation.""" +type SalesTaxPatchOutput { + """Indicates whether the sales tax was successfully patched.""" + didSucceed: Boolean! + + """Mutation validation errors.""" + inputErrors: [InputError!] + + """Sales tax that was patched.""" + salesTax: SalesTax +} + +""" +A Sales Tax rate with effective date. New entry for each change of rate. +""" +type SalesTaxRate { + """Date from which the sales tax rate applies.""" + effective: Date! + + """ + Tax rate applying from the effective date as a decimal (e.g. 0.15 represents 15%). + """ + rate: Decimal! +} + +"""Sales tax rate input for the `salesTaxPatch` mutation""" +input SalesTaxRateInput { + """Date from which the sales tax rate applies.""" + effective: Date! + + """ + Tax rate applying from the effective date as a decimal (e.g. 0.15 represents 15%). + """ + rate: Decimal! +} + +"""Wave's schemas.""" +enum Schema { + """Available only to HR Block integration.""" + HRBLOCK + + """Available only to Wave.""" + INTERNAL + + """Available to all third parties.""" + PUBLIC + + """Available only to Wave staff.""" + STAFF +} + +"""An created transaction.""" +type Transaction { + """Unique identifier for the transaction.""" + id: ID! +} + +"""Sales tax input.""" +input TransactionCreateSalesTaxInput { + """Tax Abbreviation.""" + abbreviation: String! + + """Tax Amount.""" + amount: Float! +} + +"""Represents the direction of a transaction.""" +enum TransactionDirection { + """To put in.""" + DEPOSIT + + """To remove from.""" + WITHDRAWAL +} + +"""Represents the origin of a transaction.""" +enum TransactionOrigin { + """Manually created transaction.""" + MANUAL + + """Transaction created through Zapier.""" + ZAPIER +} + +""" +A field whose value conforms to the standard URL format as specified in RFC3986: https://www.ietf.org/rfc/rfc3986.txt. +""" +scalar URL + +"""A user is an individual's account.""" +type User implements Node { + """When the user was created.""" + createdAt: DateTime! + + """The user's primary email address.""" + defaultEmail: String + + """The user's first name.""" + firstName: String + + """The unique identifier for the user.""" + id: ID! + + """The user's last name.""" + lastName: String + + """When the user was last modified.""" + modifiedAt: DateTime! +} + +"""A vendor of the business.""" +type Vendor implements BusinessNode & Node { + """The address of the vendor.""" + address: Address + + """Business that the vendor belongs to.""" + business: Business! + + """When the vendor was created.""" + createdAt: DateTime! + + """Default currency used by the vendor.""" + currency: Currency + + """ + User defined id for the vendor. Commonly referred to as Account Number. + """ + displayId: String + + """Email of the principal vendor.""" + email: String + + """Fax number of the vendor.""" + fax: String + + """The first name of the principal contact.""" + firstName: String + + """Unique identifier for the customer.""" + id: ID! + + """Internal notes about the vendor.""" + internalNotes: String + + """Whether or not the vendor is archived.""" + isArchived: Boolean + + """The last name of the principal contact.""" + lastName: String + + """The mobile number of the vendor.""" + mobile: String + + """When the vendor was last modified.""" + modifiedAt: DateTime! + + """Name or business name of the vendor.""" + name: String! + + """The phone number of the vendor.""" + phone: String + + """Details for shipping to the vendor.""" + shippingDetails: VendorShippingDetails + + """Toll-free number of the vendor.""" + tollFree: String + + """Website address of the vendor.""" + website: String +} + +"""Vendor connection.""" +type VendorConnection { + """List of vendors.""" + edges: [VendorEdge!]! + + """Information about pagination.""" + pageInfo: OffsetPageInfo! +} + +"""Vendor edge.""" +type VendorEdge { + """A vendor.""" + node: Vendor! +} + +"""Shipping details related to a vendor.""" +type VendorShippingDetails { + """Address of the vendor.""" + address: Address + + """Delivery instructions for handling.""" + instructions: String + + """Name or business name of the vendor.""" + name: String + + """Telephone number of the vendor.""" + phone: String +} \ No newline at end of file diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/src/lib/server/teamDataLoader.ts b/src/lib/server/teamDataLoader.ts new file mode 100644 index 0000000..767cf00 --- /dev/null +++ b/src/lib/server/teamDataLoader.ts @@ -0,0 +1,96 @@ +import type { ServicesForTeam$input, ProjectsForTeam$input } from '$houdini'; +import { fromGlobalId } from '$lib/utils/relay'; +import { getMonthDateRange } from '$lib/utils/date'; + +export interface TeamDataParams { + teamProfileId: string; // Relay global ID - will be decoded to UUID + month: string; // YYYY-MM format +} + +export interface StatusVariables { + scheduled: T; + inProgress: T; + completed: T; +} + +/** + * Build GraphQL variables for all service status categories + */ +export function buildAllServiceVariables( + params: TeamDataParams +): StatusVariables { + const { start, end } = getMonthDateRange(params.month); + const baseDateFilter = { gte: start, lte: end }; + const teamProfileId = fromGlobalId(params.teamProfileId); + + return { + scheduled: { + teamProfileId, + filters: { + date: baseDateFilter, + status: { exact: 'SCHEDULED' } + }, + ordering: 'ASC', + first: 20 + }, + inProgress: { + teamProfileId, + filters: { + date: baseDateFilter, + status: { exact: 'IN_PROGRESS' } + }, + ordering: 'DESC', + first: 50 // Load all - typically small number + }, + completed: { + teamProfileId, + filters: { + date: baseDateFilter, + status: { exact: 'COMPLETED' } + }, + ordering: 'DESC', + first: 20 + } + }; +} + +/** + * Build GraphQL variables for all project status categories + */ +export function buildAllProjectVariables( + params: TeamDataParams +): StatusVariables { + const { start, end } = getMonthDateRange(params.month); + const baseDateFilter = { gte: start, lte: end }; + const teamProfileId = fromGlobalId(params.teamProfileId); + + return { + scheduled: { + teamProfileId, + filters: { + date: baseDateFilter, + status: { exact: 'SCHEDULED' } + }, + ordering: 'ASC', + first: 20 + }, + inProgress: { + teamProfileId, + filters: { + date: baseDateFilter, + status: { exact: 'IN_PROGRESS' } + }, + ordering: 'DESC', + first: 50 // Load all - typically small number + }, + completed: { + teamProfileId, + filters: { + date: baseDateFilter, + status: { exact: 'COMPLETED' } + }, + ordering: 'DESC', + first: 20 + } + }; +} diff --git a/src/lib/services/calendar.ts b/src/lib/services/calendar.ts new file mode 100644 index 0000000..db5059c --- /dev/null +++ b/src/lib/services/calendar.ts @@ -0,0 +1,219 @@ +import { PUBLIC_CALENDAR_API_URL, PUBLIC_CALENDAR_API_KEY } from '$env/static/public'; + +// Types matching the scheduler microservice API + +export interface EventDateTime { + dateTime?: string; // ISO 8601 datetime (e.g., "2023-06-15T10:00:00Z") + date?: string; // For all-day events (YYYY-MM-DD format) + timeZone?: string; // Optional timezone (e.g., "UTC", "America/New_York") +} + +export interface Attendee { + email: string; + displayName?: string; + optional?: boolean; +} + +export interface EventReminder { + method: 'email' | 'popup'; // Notification method + minutes: number; // Minutes before event to send reminder +} + +export interface EventReminders { + useDefault: boolean; // Whether to use the calendar's default reminders + overrides?: EventReminder[]; // Custom reminders (only used if useDefault is false) +} + +export interface CalendarEvent { + id: string; + summary: string; + description?: string; + location?: string; + start: EventDateTime; + end: EventDateTime; + attendees?: Attendee[]; + reminders?: EventReminders; + colorId?: string; + htmlLink?: string; + created?: string; + updated?: string; +} + +// Google Calendar color IDs and their display values +export const EVENT_COLORS = [ + { id: '1', name: 'Lavender', bg: 'bg-purple-300', text: 'text-purple-900' }, + { id: '2', name: 'Sage', bg: 'bg-green-300', text: 'text-green-900' }, + { id: '3', name: 'Grape', bg: 'bg-violet-400', text: 'text-violet-900' }, + { id: '4', name: 'Flamingo', bg: 'bg-pink-400', text: 'text-pink-900' }, + { id: '5', name: 'Banana', bg: 'bg-yellow-300', text: 'text-yellow-900' }, + { id: '6', name: 'Tangerine', bg: 'bg-orange-400', text: 'text-orange-900' }, + { id: '7', name: 'Peacock', bg: 'bg-cyan-500', text: 'text-cyan-900' }, + { id: '8', name: 'Graphite', bg: 'bg-gray-400', text: 'text-gray-900' }, + { id: '9', name: 'Blueberry', bg: 'bg-blue-500', text: 'text-blue-100' }, + { id: '10', name: 'Basil', bg: 'bg-emerald-600', text: 'text-emerald-100' }, + { id: '11', name: 'Tomato', bg: 'bg-red-500', text: 'text-red-100' } +] as const; + +export function getEventColor(colorId?: string) { + return EVENT_COLORS.find((c) => c.id === colorId) ?? null; +} + +export interface CreateEventInput { + id?: string; // Optional custom ID (5-1024 chars, a-v + 0-9 only) + summary: string; + description?: string; + location?: string; + start: EventDateTime; + end: EventDateTime; + attendees?: Attendee[]; + reminders?: EventReminders; + colorId?: string; +} + +export interface UpdateEventInput { + summary?: string; + description?: string; + location?: string; + start?: EventDateTime; + end?: EventDateTime; + attendees?: Attendee[]; + reminders?: EventReminders; + colorId?: string; +} + +export interface ListEventsParams { + timeMin?: string; // RFC3339 format + timeMax?: string; // RFC3339 format + maxResults?: number; + q?: string; // Search query +} + +export interface CalendarError { + error: string; + message: string; +} + +class CalendarService { + private baseUrl: string; + private apiKey: string; + + constructor() { + this.baseUrl = PUBLIC_CALENDAR_API_URL; + this.apiKey = PUBLIC_CALENDAR_API_KEY; + } + + private async request( + endpoint: string, + options: RequestInit = {} + ): Promise<{ data?: T; error?: CalendarError }> { + const url = `${this.baseUrl}${endpoint}`; + + const headers: HeadersInit = { + 'X-API-Key': this.apiKey, + 'Content-Type': 'application/json', + ...options.headers + }; + + try { + const response = await fetch(url, { + ...options, + headers + }); + + // Handle 204 No Content (for delete operations) + if (response.status === 204) { + return { data: undefined }; + } + + const data = await response.json(); + + if (!response.ok) { + return { error: data as CalendarError }; + } + + return { data: data as T }; + } catch (err) { + console.error('Calendar API error:', err); + return { + error: { + error: 'NetworkError', + message: err instanceof Error ? err.message : 'Unknown network error' + } + }; + } + } + + /** + * List calendar events with optional filters + */ + async listEvents(params: ListEventsParams = {}): Promise<{ + data?: CalendarEvent[]; + error?: CalendarError; + }> { + const searchParams = new URLSearchParams(); + + if (params.timeMin) searchParams.set('timeMin', params.timeMin); + if (params.timeMax) searchParams.set('timeMax', params.timeMax); + if (params.maxResults) searchParams.set('maxResults', params.maxResults.toString()); + if (params.q) searchParams.set('q', params.q); + + const queryString = searchParams.toString(); + const endpoint = `/events${queryString ? `?${queryString}` : ''}`; + + return this.request(endpoint); + } + + /** + * Get a single calendar event by ID + */ + async getEvent(eventId: string): Promise<{ data?: CalendarEvent; error?: CalendarError }> { + return this.request(`/events/${eventId}`); + } + + /** + * Create a new calendar event + */ + async createEvent( + input: CreateEventInput + ): Promise<{ data?: CalendarEvent; error?: CalendarError }> { + return this.request('/events', { + method: 'POST', + body: JSON.stringify(input) + }); + } + + /** + * Update an existing calendar event (partial updates supported) + */ + async updateEvent( + eventId: string, + input: UpdateEventInput + ): Promise<{ data?: CalendarEvent; error?: CalendarError }> { + return this.request(`/events/${eventId}`, { + method: 'PUT', + body: JSON.stringify(input) + }); + } + + /** + * Delete a calendar event + */ + async deleteEvent(eventId: string): Promise<{ error?: CalendarError }> { + return this.request(`/events/${eventId}`, { + method: 'DELETE' + }); + } + + /** + * Health check for the calendar service + */ + async healthCheck(): Promise<{ data?: { status: string }; error?: CalendarError }> { + return this.request<{ status: string }>('/health'); + } +} + +// Export singleton instance +export const calendarService = new CalendarService(); + +// Export class for testing or custom instances +export { CalendarService }; diff --git a/src/lib/services/email.ts b/src/lib/services/email.ts new file mode 100644 index 0000000..104c886 --- /dev/null +++ b/src/lib/services/email.ts @@ -0,0 +1,307 @@ +import { PUBLIC_EMAIL_API_URL, PUBLIC_EMAIL_API_KEY } from '$env/static/public'; + +// Types matching the emailer microservice API + +export interface EmailError { + error: string; + message: string; +} + +export interface EmailAttachment { + filename: string; + content: string; // base64 encoded + contentType: string; +} + +export interface SendEmailInput { + to: string[]; + cc?: string[]; + bcc?: string[]; + subject: string; + body: string; + contentType?: 'text/html' | 'text/plain'; + fromName?: string; + attachments?: EmailAttachment[]; +} + +export interface EmailHeader { + name: string; + value: string; +} + +export interface EmailBody { + attachmentId?: string; + size?: number; + data?: string; // base64 encoded +} + +export interface EmailPayload { + partId?: string; + mimeType?: string; + filename?: string; + headers?: EmailHeader[]; + body?: EmailBody; + parts?: EmailPayload[]; +} + +export interface EmailMessage { + id: string; + threadId: string; + labelIds?: string[]; + snippet?: string; + payload?: EmailPayload; + sizeEstimate?: number; + historyId?: string; + internalDate?: string; +} + +export interface EmailMessageRef { + id: string; + threadId: string; +} + +export interface ListEmailsResponse { + messages?: EmailMessageRef[]; + nextPageToken?: string; + resultSizeEstimate?: number; +} + +export interface ListEmailsParams { + q?: string; // Gmail search query + maxResults?: number; + pageToken?: string; + labelIds?: string[]; + includeSpamTrash?: boolean; +} + +export interface EmailTemplate { + templateId: string; + subjectTemplate: string; + bodyTemplate: string; + variables: Record; +} + +export interface SendTemplateInput { + to: string[]; + cc?: string[]; + bcc?: string[]; + templateId: string; + variables: Record; +} + +class EmailService { + private readonly baseUrl: string; + private readonly apiKey: string; + + constructor() { + this.baseUrl = PUBLIC_EMAIL_API_URL; + this.apiKey = PUBLIC_EMAIL_API_KEY; + } + + private async request( + endpoint: string, + options: RequestInit = {}, + impersonateUser?: string + ): Promise<{ data?: T; error?: EmailError }> { + const url = `${this.baseUrl}${endpoint}`; + + const headers: HeadersInit = { + 'X-API-Key': this.apiKey, + 'Content-Type': 'application/json', + ...options.headers + }; + + if (impersonateUser) { + (headers as Record)['X-Impersonate-User'] = impersonateUser; + } + + try { + const response = await fetch(url, { + ...options, + headers + }); + + // Handle 204 No Content (for delete operations) + if (response.status === 204) { + return { data: undefined }; + } + + const data = await response.json(); + + if (!response.ok) { + return { error: data as EmailError }; + } + + return { data: data as T }; + } catch (err) { + console.error('Email API error:', err); + return { + error: { + error: 'NetworkError', + message: err instanceof Error ? err.message : 'Unknown network error' + } + }; + } + } + + /** + * Send an email + */ + async sendEmail( + input: SendEmailInput, + impersonateUser?: string + ): Promise<{ data?: EmailMessage; error?: EmailError }> { + return this.request( + '/emails', + { + method: 'POST', + body: JSON.stringify(input) + }, + impersonateUser + ); + } + + /** + * List emails with optional filters + */ + async listEmails( + params: ListEmailsParams = {}, + impersonateUser?: string + ): Promise<{ data?: ListEmailsResponse; error?: EmailError }> { + const searchParams = new URLSearchParams(); + + if (params.q) searchParams.set('q', params.q); + if (params.maxResults) searchParams.set('maxResults', params.maxResults.toString()); + if (params.pageToken) searchParams.set('pageToken', params.pageToken); + if (params.labelIds) { + params.labelIds.forEach((id) => searchParams.append('labelIds', id)); + } + if (params.includeSpamTrash !== undefined) { + searchParams.set('includeSpamTrash', params.includeSpamTrash.toString()); + } + + const queryString = searchParams.toString(); + const endpoint = `/emails${queryString ? `?${queryString}` : ''}`; + + return this.request(endpoint, {}, impersonateUser); + } + + /** + * Get a single email by ID + */ + async getEmail( + emailId: string, + impersonateUser?: string + ): Promise<{ data?: EmailMessage; error?: EmailError }> { + return this.request(`/emails/${emailId}`, {}, impersonateUser); + } + + /** + * Delete an email + */ + async deleteEmail(emailId: string, impersonateUser?: string): Promise<{ error?: EmailError }> { + return this.request( + `/emails/${emailId}`, + { + method: 'DELETE' + }, + impersonateUser + ); + } + + /** + * Mark an email as read + */ + async markAsRead( + emailId: string, + impersonateUser?: string + ): Promise<{ data?: EmailMessage; error?: EmailError }> { + return this.request( + `/emails/${emailId}/read`, + { + method: 'POST' + }, + impersonateUser + ); + } + + /** + * Mark an email as unread + */ + async markAsUnread( + emailId: string, + impersonateUser?: string + ): Promise<{ data?: EmailMessage; error?: EmailError }> { + return this.request( + `/emails/${emailId}/unread`, + { + method: 'POST' + }, + impersonateUser + ); + } + + /** + * List available email templates + */ + async listTemplates(): Promise<{ data?: string[]; error?: EmailError }> { + return this.request('/templates'); + } + + /** + * Get a template by ID + */ + async getTemplate(templateId: string): Promise<{ data?: EmailTemplate; error?: EmailError }> { + return this.request(`/templates/${templateId}`); + } + + /** + * Send an email using a template + */ + async sendTemplate( + input: SendTemplateInput, + impersonateUser?: string + ): Promise<{ data?: EmailMessage; error?: EmailError }> { + return this.request( + '/templates/send', + { + method: 'POST', + body: JSON.stringify(input) + }, + impersonateUser + ); + } + + /** + * Health check for the email service + */ + async healthCheck(): Promise<{ data?: { status: string; message: string }; error?: EmailError }> { + // Health endpoint is at root, not under /api/v1 + const url = this.baseUrl.replace('/api/v1', '') + '/health'; + + try { + const response = await fetch(url); + const data = await response.json(); + + if (!response.ok) { + return { error: data as EmailError }; + } + + return { data: data as { status: string; message: string } }; + } catch (err) { + console.error('Email API health check error:', err); + return { + error: { + error: 'NetworkError', + message: err instanceof Error ? err.message : 'Unknown network error' + } + }; + } + } +} + +// Export singleton instance +export const emailService = new EmailService(); + +// Export class for testing or custom instances +export { EmailService }; diff --git a/src/lib/stores/assignServices.svelte.ts b/src/lib/stores/assignServices.svelte.ts new file mode 100644 index 0000000..2edd4e3 --- /dev/null +++ b/src/lib/stores/assignServices.svelte.ts @@ -0,0 +1,895 @@ +import { AdminServicesStore, UpdateServiceStore, DeleteServiceStore } from '$houdini'; +import { fromGlobalId, toGlobalId } from '$lib/utils/relay'; +import { formatAddress, type AccountAddressInfo } from '$lib/utils/lookup'; +import { SvelteDate, SvelteMap, SvelteSet } from 'svelte/reactivity'; + +// Enriched service type +export type EnrichedService = { + id: string; + date: string; + status: string; + notes: string | null; + teamMembers: { pk: string }[]; + accountAddressId: string; + accountName: string; + addressName: string | null; + address: string | null; +}; + +export type ViewMode = 'date' | 'week' | 'account'; +export type TabType = 'unassigned' | 'readyToAssign' | 'assigned'; +export type ColumnType = TabType; + +export interface TeamProfile { + id: string; + fullName: string; + role: string; +} + +export function createAssignServicesStore() { + // Services stores (mutation stores don't need state) + const servicesStore = new AdminServicesStore(); + const updateServiceStore = new UpdateServiceStore(); + const deleteServiceStore = new DeleteServiceStore(); + + // Store fetched services data + let servicesData = $state< + { + id: string; + date: string; + status: string; + notes: string | null; + accountAddressId: string; + teamMembers: { pk: string }[] | null; + }[] + >([]); + + // External data (set via init) + const accountLookup = new SvelteMap(); + let teamProfiles = $state([]); + + // Computed from team profiles + const dispatchProfile = $derived(teamProfiles.find((p) => p.role === 'ADMIN') ?? null); + const dispatchProfilePk = $derived(dispatchProfile ? fromGlobalId(dispatchProfile.id) : null); + const dispatchProfileGlobalId = $derived(dispatchProfile?.id ?? null); + const nonAdminTeamMembers = $derived(teamProfiles.filter((p) => p.role !== 'ADMIN')); + + // UI State + let currentMonth = new SvelteDate(); + let isLoading = $state(true); + let error = $state(''); + let viewMode = $state('date'); + let activeTab = $state('unassigned'); + + // Track services currently being updated + const updatingServices = new SvelteSet(); + + // Track which service is being date-edited + let editingDateServiceId = $state(null); + + // Optimistic updates for team members + const optimisticUpdates = new SvelteMap(); + + // Staged team members (not yet submitted) + const stagedTeamMembers = new SvelteMap(); + + // Track which service has dropdown open + let openTeamMemberDropdown = $state(null); + + // Track expanded groups per column + const expandedUnassigned = new SvelteSet(); + const expandedReadyToAssign = new SvelteSet(); + const expandedAssigned = new SvelteSet(); + + // Bulk selection state per column + const selectedUnassigned = new SvelteSet(); + const selectedReadyToAssign = new SvelteSet(); + const selectedAssigned = new SvelteSet(); + + // Bulk operation state + let isBulkOperating = $state(false); + let bulkSelectedTeamMember = $state(null); + let showBulkTeamMemberDropdown = $state(false); + + // Delete modal state + let showDeleteModal = $state(false); + let serviceToDelete = $state<{ + id: string; + accountName: string; + date: string; + } | null>(null); + let isDeleting = $state(false); + + // Initialize with external data + function init(lookup: Map, profiles: TeamProfile[]) { + accountLookup.clear(); + for (const [key, value] of lookup) { + accountLookup.set(key, value); + } + teamProfiles = profiles; + } + + // Fetch services for current month + async function fetchServices() { + isLoading = true; + error = ''; + + const year = currentMonth.getFullYear(); + const month = currentMonth.getMonth() + 1; + const startDate = `${year}-${String(month).padStart(2, '0')}-01`; + const lastDay = new SvelteDate(year, month, 0).getDate(); + const endDate = `${year}-${String(month).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`; + + try { + const result = await servicesStore.fetch({ + variables: { + filters: { + date: { gte: startDate, lte: endDate }, + status: { exact: 'SCHEDULED' } + } + } + }); + servicesData = result.data?.services ?? []; + } catch (e) { + error = e instanceof Error ? e.message : 'Failed to load services'; + } finally { + isLoading = false; + } + } + + // Enrich services with account info + const enrichedServices = $derived.by(() => { + const result: EnrichedService[] = []; + + for (const service of servicesData) { + const accountInfo = service.accountAddressId + ? accountLookup.get(service.accountAddressId) + : null; + + if (!accountInfo) continue; + + const teamMembers = optimisticUpdates.has(service.id) + ? optimisticUpdates.get(service.id)! + : (service.teamMembers ?? []); + + result.push({ + id: service.id, + date: service.date, + status: service.status, + notes: service.notes, + teamMembers, + accountAddressId: service.accountAddressId, + accountName: accountInfo.accountName, + addressName: accountInfo.addressName ?? null, + address: formatAddress(accountInfo) + }); + } + + return result.sort((a, b) => a.date.localeCompare(b.date)); + }); + + // Categorize services + const categorizedServices = $derived.by(() => { + const unassigned: EnrichedService[] = []; + const readyToAssign: EnrichedService[] = []; + const assigned: EnrichedService[] = []; + + for (const service of enrichedServices) { + const hasTeam = service.teamMembers.length > 0; + const hasDispatch = dispatchProfilePk + ? service.teamMembers.some((m) => m.pk === dispatchProfilePk) + : false; + const hasOthers = service.teamMembers.length > (hasDispatch ? 1 : 0); + + if (!hasTeam) { + unassigned.push(service); + } else if (hasDispatch && hasOthers) { + assigned.push(service); + } else if (hasDispatch) { + readyToAssign.push(service); + } else { + unassigned.push(service); + } + } + + return { unassigned, readyToAssign, assigned }; + }); + + // Grouping functions + function groupByDate(services: EnrichedService[]): Map { + const groups = new SvelteMap(); + for (const service of services) { + const date = new SvelteDate(service.date + 'T00:00:00'); + const key = date.toLocaleDateString('en-US', { + weekday: 'short', + month: 'short', + day: 'numeric' + }); + const existing = groups.get(key) ?? []; + existing.push(service); + groups.set(key, existing); + } + return groups; + } + + function groupByWeek(services: EnrichedService[]): Map { + const groups = new SvelteMap(); + for (const service of services) { + const date = new SvelteDate(service.date + 'T00:00:00'); + const dayOfWeek = date.getDay(); + const weekStart = new SvelteDate(date); + weekStart.setDate(date.getDate() - dayOfWeek); + const weekEnd = new SvelteDate(weekStart); + weekEnd.setDate(weekStart.getDate() + 6); + + const key = `${weekStart.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} - ${weekEnd.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}`; + const existing = groups.get(key) ?? []; + existing.push(service); + groups.set(key, existing); + } + return groups; + } + + function groupByAccount(services: EnrichedService[]): Map { + const groups = new SvelteMap(); + for (const service of services) { + const key = service.accountName; + const existing = groups.get(key) ?? []; + existing.push(service); + groups.set(key, existing); + } + return new SvelteMap([...groups.entries()].sort((a, b) => a[0].localeCompare(b[0]))); + } + + function getGroupedServices(services: EnrichedService[]): Map { + switch (viewMode) { + case 'date': + return groupByDate(services); + case 'week': + return groupByWeek(services); + case 'account': + return groupByAccount(services); + } + } + + // Grouped data + const groupedUnassigned = $derived(getGroupedServices(categorizedServices.unassigned)); + const groupedReadyToAssign = $derived(getGroupedServices(categorizedServices.readyToAssign)); + const groupedAssigned = $derived(getGroupedServices(categorizedServices.assigned)); + + // Month navigation + function previousMonth() { + currentMonth = new SvelteDate(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1); + } + + function nextMonth() { + currentMonth = new SvelteDate(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1); + } + + function goToCurrentMonth() { + currentMonth = new SvelteDate(); + } + + const monthDisplay = $derived( + currentMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }) + ); + + // Tab navigation + const tabOrder: TabType[] = ['unassigned', 'readyToAssign', 'assigned']; + + function previousTab() { + const currentIndex = tabOrder.indexOf(activeTab); + activeTab = tabOrder[(currentIndex - 1 + tabOrder.length) % tabOrder.length]; + } + + function nextTab() { + const currentIndex = tabOrder.indexOf(activeTab); + activeTab = tabOrder[(currentIndex + 1) % tabOrder.length]; + } + + // Toggle group expansion + function toggleGroup(column: ColumnType, groupKey: string) { + const set = + column === 'unassigned' + ? expandedUnassigned + : column === 'readyToAssign' + ? expandedReadyToAssign + : expandedAssigned; + if (set.has(groupKey)) { + set.delete(groupKey); + } else { + set.add(groupKey); + } + } + + // Reset expanded groups when view mode changes + function resetExpandedGroups() { + expandedUnassigned.clear(); + expandedReadyToAssign.clear(); + expandedAssigned.clear(); + } + + // Get team member names + function getTeamMemberNames(teamMembers: { pk: string }[]): string[] { + return teamMembers + .map((m) => { + const profile = teamProfiles.find((p) => fromGlobalId(p.id) === m.pk); + return profile?.fullName ?? null; + }) + .filter((name): name is string => name !== null); + } + + function getNonDispatchTeamMemberNames(teamMembers: { pk: string }[]): string[] { + return teamMembers + .filter((m) => m.pk !== dispatchProfilePk) + .map((m) => { + const profile = teamProfiles.find((p) => fromGlobalId(p.id) === m.pk); + return profile?.fullName ?? null; + }) + .filter((name): name is string => name !== null); + } + + // Service operations + // Helper to update servicesData after successful mutation + function updateServiceTeamMembers(serviceId: string, newTeamMembers: { pk: string }[]) { + servicesData = servicesData.map((s) => + s.id === serviceId ? { ...s, teamMembers: newTeamMembers } : s + ); + } + + async function addDispatchToService(service: EnrichedService) { + if (!dispatchProfilePk || !dispatchProfileGlobalId) return; + if (updatingServices.has(service.id)) return; + + const currentPks = service.teamMembers.map((m) => m.pk); + const newPks = [...currentPks, dispatchProfilePk]; + const newTeamMembers = newPks.map((pk) => ({ pk })); + const teamMemberGlobalIds = newPks.map((pk) => toGlobalId('TeamProfileType', pk)); + + optimisticUpdates.set(service.id, newTeamMembers); + updatingServices.add(service.id); + + try { + await updateServiceStore.mutate({ + input: { + id: service.id, + teamMemberIds: teamMemberGlobalIds + } + }); + updateServiceTeamMembers(service.id, newTeamMembers); + optimisticUpdates.delete(service.id); + } catch (e) { + optimisticUpdates.delete(service.id); + error = e instanceof Error ? e.message : 'Failed to add dispatch'; + } finally { + updatingServices.delete(service.id); + } + } + + async function removeDispatchFromService(service: EnrichedService) { + if (!dispatchProfilePk) return; + if (updatingServices.has(service.id)) return; + + const newPks = service.teamMembers.map((m) => m.pk).filter((pk) => pk !== dispatchProfilePk); + const newTeamMembers = newPks.map((pk) => ({ pk })); + const teamMemberGlobalIds = newPks.map((pk) => toGlobalId('TeamProfileType', pk)); + + optimisticUpdates.set(service.id, newTeamMembers); + updatingServices.add(service.id); + + try { + await updateServiceStore.mutate({ + input: { + id: service.id, + teamMemberIds: teamMemberGlobalIds + } + }); + updateServiceTeamMembers(service.id, newTeamMembers); + optimisticUpdates.delete(service.id); + stagedTeamMembers.delete(service.id); + } catch (e) { + optimisticUpdates.delete(service.id); + error = e instanceof Error ? e.message : 'Failed to remove dispatch'; + } finally { + updatingServices.delete(service.id); + } + } + + async function removeNonDispatchMembers(service: EnrichedService) { + if (!dispatchProfilePk) return; + if (updatingServices.has(service.id)) return; + + const newTeamMembers = [{ pk: dispatchProfilePk }]; + const teamMemberGlobalIds = [toGlobalId('TeamProfileType', dispatchProfilePk)]; + + optimisticUpdates.set(service.id, newTeamMembers); + updatingServices.add(service.id); + + try { + await updateServiceStore.mutate({ + input: { + id: service.id, + teamMemberIds: teamMemberGlobalIds + } + }); + updateServiceTeamMembers(service.id, newTeamMembers); + optimisticUpdates.delete(service.id); + } catch (e) { + optimisticUpdates.delete(service.id); + error = e instanceof Error ? e.message : 'Failed to remove team members'; + } finally { + updatingServices.delete(service.id); + } + } + + // Staging functions + function getAvailableTeamMembers(service: EnrichedService): { pk: string; name: string }[] { + const assignedPks = new SvelteSet(service.teamMembers.map((m) => m.pk)); + const stagedPks = new SvelteSet(stagedTeamMembers.get(service.id) ?? []); + + return nonAdminTeamMembers + .filter((p) => { + const pk = fromGlobalId(p.id); + if (!pk) return false; + if (assignedPks.has(pk)) return false; + return !stagedPks.has(pk); + }) + .map((p) => ({ + pk: fromGlobalId(p.id)!, + name: p.fullName + })); + } + + function getStagedTeamMemberDetails(serviceId: string): { pk: string; name: string }[] { + const staged = stagedTeamMembers.get(serviceId) ?? []; + return staged + .map((pk) => { + const profile = teamProfiles.find((p) => fromGlobalId(p.id) === pk); + return profile ? { pk, name: profile.fullName } : null; + }) + .filter((m): m is { pk: string; name: string } => m !== null); + } + + function stageTeamMember(serviceId: string, memberPk: string) { + const current = stagedTeamMembers.get(serviceId) ?? []; + if (!current.includes(memberPk)) { + stagedTeamMembers.set(serviceId, [...current, memberPk]); + } + openTeamMemberDropdown = null; + } + + function unstageTeamMember(serviceId: string, memberPk: string) { + const current = stagedTeamMembers.get(serviceId) ?? []; + stagedTeamMembers.set( + serviceId, + current.filter((pk) => pk !== memberPk) + ); + } + + function hasStagedMembers(serviceId: string): boolean { + const staged = stagedTeamMembers.get(serviceId); + return staged !== undefined && staged.length > 0; + } + + async function submitStagedTeamMembers(service: EnrichedService) { + const staged = stagedTeamMembers.get(service.id); + if (!staged || staged.length === 0) return; + if (updatingServices.has(service.id)) return; + + const currentPks = service.teamMembers.map((m) => m.pk); + const newPks = [...currentPks, ...staged]; + const newTeamMembers = newPks.map((pk) => ({ pk })); + const teamMemberGlobalIds = newPks.map((pk) => toGlobalId('TeamProfileType', pk)); + + optimisticUpdates.set(service.id, newTeamMembers); + updatingServices.add(service.id); + + try { + await updateServiceStore.mutate({ + input: { + id: service.id, + teamMemberIds: teamMemberGlobalIds + } + }); + updateServiceTeamMembers(service.id, newTeamMembers); + optimisticUpdates.delete(service.id); + stagedTeamMembers.delete(service.id); + } catch (e) { + optimisticUpdates.delete(service.id); + error = e instanceof Error ? e.message : 'Failed to assign team members'; + } finally { + updatingServices.delete(service.id); + } + } + + // Date editing + async function updateServiceDate(service: EnrichedService, newDate: string) { + if (updatingServices.has(service.id)) return; + + updatingServices.add(service.id); + editingDateServiceId = null; + + try { + await updateServiceStore.mutate({ + input: { + id: service.id, + date: newDate + } + }); + await fetchServices(); + } catch (e) { + error = e instanceof Error ? e.message : 'Failed to update date'; + } finally { + updatingServices.delete(service.id); + } + } + + function startEditDate(serviceId: string) { + editingDateServiceId = serviceId; + } + + function cancelEditDate() { + editingDateServiceId = null; + } + + // Delete operations + function promptDeleteService(service: EnrichedService) { + serviceToDelete = { + id: service.id, + accountName: service.accountName, + date: service.date + }; + showDeleteModal = true; + } + + async function confirmDeleteService() { + if (!serviceToDelete) return; + + isDeleting = true; + + try { + await deleteServiceStore.mutate({ + id: serviceToDelete.id + }); + showDeleteModal = false; + serviceToDelete = null; + await fetchServices(); + } catch (e) { + error = e instanceof Error ? e.message : 'Failed to delete service'; + } finally { + isDeleting = false; + } + } + + function cancelDeleteService() { + showDeleteModal = false; + serviceToDelete = null; + } + + // Selection functions + function toggleServiceSelection(column: ColumnType, serviceId: string) { + const set = + column === 'unassigned' + ? selectedUnassigned + : column === 'readyToAssign' + ? selectedReadyToAssign + : selectedAssigned; + if (set.has(serviceId)) { + set.delete(serviceId); + } else { + set.add(serviceId); + } + } + + function selectAllInColumn(column: ColumnType) { + const services = + column === 'unassigned' + ? categorizedServices.unassigned + : column === 'readyToAssign' + ? categorizedServices.readyToAssign + : categorizedServices.assigned; + const set = + column === 'unassigned' + ? selectedUnassigned + : column === 'readyToAssign' + ? selectedReadyToAssign + : selectedAssigned; + for (const service of services) { + set.add(service.id); + } + } + + function clearColumnSelection(column: ColumnType) { + const set = + column === 'unassigned' + ? selectedUnassigned + : column === 'readyToAssign' + ? selectedReadyToAssign + : selectedAssigned; + set.clear(); + if (column === 'readyToAssign') { + bulkSelectedTeamMember = null; + showBulkTeamMemberDropdown = false; + } + } + + function getSelectedServices(column: ColumnType): EnrichedService[] { + const services = + column === 'unassigned' + ? categorizedServices.unassigned + : column === 'readyToAssign' + ? categorizedServices.readyToAssign + : categorizedServices.assigned; + const set = + column === 'unassigned' + ? selectedUnassigned + : column === 'readyToAssign' + ? selectedReadyToAssign + : selectedAssigned; + return services.filter((s) => set.has(s.id)); + } + + // Bulk operations + async function bulkAddDispatch() { + if (!dispatchProfilePk || !dispatchProfileGlobalId) return; + const selected = getSelectedServices('unassigned'); + if (selected.length === 0) return; + + isBulkOperating = true; + error = ''; + + try { + for (const service of selected) { + if (updatingServices.has(service.id)) continue; + + const currentPks = service.teamMembers.map((m) => m.pk); + const newPks = [...currentPks, dispatchProfilePk]; + const teamMemberGlobalIds = newPks.map((pk) => toGlobalId('TeamProfileType', pk)); + + updatingServices.add(service.id); + await updateServiceStore.mutate({ + input: { + id: service.id, + teamMemberIds: teamMemberGlobalIds + } + }); + updatingServices.delete(service.id); + } + selectedUnassigned.clear(); + await fetchServices(); + } catch (e) { + error = e instanceof Error ? e.message : 'Failed to add dispatch to services'; + } finally { + isBulkOperating = false; + } + } + + async function bulkAddTeamMember() { + if (!bulkSelectedTeamMember || !dispatchProfilePk) return; + const selected = getSelectedServices('readyToAssign'); + if (selected.length === 0) return; + + isBulkOperating = true; + error = ''; + + try { + for (const service of selected) { + if (updatingServices.has(service.id)) continue; + + const currentPks = service.teamMembers.map((m) => m.pk); + const newPks = currentPks.includes(bulkSelectedTeamMember) + ? currentPks + : [...currentPks, bulkSelectedTeamMember]; + const teamMemberGlobalIds = newPks.map((pk) => toGlobalId('TeamProfileType', pk)); + + updatingServices.add(service.id); + await updateServiceStore.mutate({ + input: { + id: service.id, + teamMemberIds: teamMemberGlobalIds + } + }); + updatingServices.delete(service.id); + } + selectedReadyToAssign.clear(); + bulkSelectedTeamMember = null; + showBulkTeamMemberDropdown = false; + await fetchServices(); + } catch (e) { + error = e instanceof Error ? e.message : 'Failed to add team member to services'; + } finally { + isBulkOperating = false; + } + } + + async function bulkRemoveNonDispatch() { + if (!dispatchProfilePk) return; + const selected = getSelectedServices('assigned'); + if (selected.length === 0) return; + + isBulkOperating = true; + error = ''; + + try { + for (const service of selected) { + if (updatingServices.has(service.id)) continue; + + const teamMemberGlobalIds = [toGlobalId('TeamProfileType', dispatchProfilePk)]; + + updatingServices.add(service.id); + await updateServiceStore.mutate({ + input: { + id: service.id, + teamMemberIds: teamMemberGlobalIds + } + }); + updatingServices.delete(service.id); + } + selectedAssigned.clear(); + await fetchServices(); + } catch (e) { + error = e instanceof Error ? e.message : 'Failed to remove team members from services'; + } finally { + isBulkOperating = false; + } + } + + // Dropdown management + function closeAllDropdowns() { + openTeamMemberDropdown = null; + showBulkTeamMemberDropdown = false; + } + + function setOpenTeamMemberDropdown(id: string | null) { + openTeamMemberDropdown = id; + } + + function toggleBulkTeamMemberDropdown() { + showBulkTeamMemberDropdown = !showBulkTeamMemberDropdown; + } + + function setBulkSelectedTeamMember(pk: string | null) { + bulkSelectedTeamMember = pk; + } + + // Clear error + function clearError() { + error = ''; + } + + // Set view mode + function setViewMode(mode: ViewMode) { + viewMode = mode; + } + + return { + // Initialization + init, + fetchServices, + + // State getters (using $derived for reactivity) + get currentMonth() { + return currentMonth; + }, + get monthDisplay() { + return monthDisplay; + }, + get isLoading() { + return isLoading; + }, + get error() { + return error; + }, + get viewMode() { + return viewMode; + }, + get activeTab() { + return activeTab; + }, + get isBulkOperating() { + return isBulkOperating; + }, + get editingDateServiceId() { + return editingDateServiceId; + }, + get openTeamMemberDropdown() { + return openTeamMemberDropdown; + }, + get bulkSelectedTeamMember() { + return bulkSelectedTeamMember; + }, + get showBulkTeamMemberDropdown() { + return showBulkTeamMemberDropdown; + }, + get showDeleteModal() { + return showDeleteModal; + }, + get serviceToDelete() { + return serviceToDelete; + }, + get isDeleting() { + return isDeleting; + }, + get nonAdminTeamMembers() { + return nonAdminTeamMembers; + }, + + // Derived data + get categorizedServices() { + return categorizedServices; + }, + get groupedUnassigned() { + return groupedUnassigned; + }, + get groupedReadyToAssign() { + return groupedReadyToAssign; + }, + get groupedAssigned() { + return groupedAssigned; + }, + + // Selection sets + updatingServices, + expandedUnassigned, + expandedReadyToAssign, + expandedAssigned, + selectedUnassigned, + selectedReadyToAssign, + selectedAssigned, + + // Navigation + previousMonth, + nextMonth, + goToCurrentMonth, + previousTab, + nextTab, + + // View controls + setViewMode, + resetExpandedGroups, + toggleGroup, + closeAllDropdowns, + setOpenTeamMemberDropdown, + toggleBulkTeamMemberDropdown, + setBulkSelectedTeamMember, + + // Team member helpers + getTeamMemberNames, + getNonDispatchTeamMemberNames, + getAvailableTeamMembers, + getStagedTeamMemberDetails, + hasStagedMembers, + + // Service operations + addDispatchToService, + removeDispatchFromService, + removeNonDispatchMembers, + stageTeamMember, + unstageTeamMember, + submitStagedTeamMembers, + + // Date editing + updateServiceDate, + startEditDate, + cancelEditDate, + + // Delete operations + promptDeleteService, + confirmDeleteService, + cancelDeleteService, + + // Selection operations + toggleServiceSelection, + selectAllInColumn, + clearColumnSelection, + getSelectedServices, + + // Bulk operations + bulkAddDispatch, + bulkAddTeamMember, + bulkRemoveNonDispatch, + + // Error handling + clearError + }; +} diff --git a/src/lib/stores/auth.svelte.ts b/src/lib/stores/auth.svelte.ts new file mode 100644 index 0000000..aec8de5 --- /dev/null +++ b/src/lib/stores/auth.svelte.ts @@ -0,0 +1,152 @@ +const isBrowser = typeof window !== 'undefined'; + +// Kratos configuration - always use production auth server +const KRATOS_BASE_URL = 'https://auth.example.com'; + +// Get the app's origin for return_to URLs +const APP_ORIGIN = isBrowser ? window.location.origin : ''; + +export type SessionIdentity = { + id: string; + traits: { + email?: string; + name?: { + first?: string; + last?: string; + }; + phone?: string; + profile_type?: string; + }; + metadata_public?: { + django_profile_id?: string; + customer_id?: string; + }; +}; + +export type Session = { + id: string; + active: boolean; + identity: SessionIdentity; + expires_at?: string; + authenticated_at?: string; +} | null; + +function createAuthStore() { + let session = $state(null); + let checkInProgress = false; + + const isAuthenticated = $derived(Boolean(session?.active)); + const userEmail = $derived(session?.identity?.traits?.email ?? null); + const userFullName = $derived( + session?.identity?.traits?.name + ? `${session.identity.traits.name.first ?? ''} ${session.identity.traits.name.last ?? ''}`.trim() + : null + ); + + async function checkSession(fetchFn?: typeof fetch): Promise { + const fetchToUse = fetchFn || (isBrowser ? fetch : null); + if (!fetchToUse) return null; + if (checkInProgress && !fetchFn) return null; + + if (!fetchFn) checkInProgress = true; + + try { + const response = await fetchToUse(`${KRATOS_BASE_URL}/sessions/whoami`, { + credentials: 'include', + headers: { + Accept: 'application/json' + } + }); + + if (response.ok) { + const sessionData = await response.json(); + session = sessionData; + return sessionData; + } else { + session = null; + return null; + } + } catch (error) { + console.warn('Failed to check session:', error); + session = null; + return null; + } finally { + if (!fetchFn) checkInProgress = false; + } + } + + async function logout(returnTo?: string): Promise { + if (!isBrowser) return; + + try { + const returnUrl = returnTo || APP_ORIGIN; + const logoutEndpoint = `${KRATOS_BASE_URL}/self-service/logout/browser?return_to=${encodeURIComponent(returnUrl)}`; + const response = await fetch(logoutEndpoint, { + credentials: 'include' + }); + + if (response.ok) { + const logoutData = await response.json(); + if (logoutData.logout_url) { + window.location.href = logoutData.logout_url; + } + } + } catch (error) { + console.error('Logout failed:', error); + } finally { + session = null; + } + } + + function redirectToLogin(returnTo?: string): void { + if (!isBrowser) return; + + const returnUrl = returnTo + ? returnTo.startsWith('http') + ? returnTo + : `${APP_ORIGIN}${returnTo}` + : window.location.href; + + window.location.href = `${KRATOS_BASE_URL}/self-service/login/browser?return_to=${encodeURIComponent(returnUrl)}`; + } + + function redirectToRegistration(returnTo?: string): void { + if (!isBrowser) return; + + const returnUrl = returnTo + ? returnTo.startsWith('http') + ? returnTo + : `${APP_ORIGIN}${returnTo}` + : window.location.href; + + window.location.href = `${KRATOS_BASE_URL}/self-service/registration/browser?return_to=${encodeURIComponent(returnUrl)}`; + } + + // Initialize session check on browser + if (isBrowser) { + checkSession().catch(() => { + // Intentionally ignored - error already logged in checkSession + }); + } + + return { + get session() { + return session; + }, + get isAuthenticated() { + return isAuthenticated; + }, + get userEmail() { + return userEmail; + }, + get userFullName() { + return userFullName; + }, + checkSession, + logout, + redirectToLogin, + redirectToRegistration + }; +} + +export const auth = createAuthStore(); diff --git a/src/lib/stores/chat.svelte.ts b/src/lib/stores/chat.svelte.ts new file mode 100644 index 0000000..d9caaee --- /dev/null +++ b/src/lib/stores/chat.svelte.ts @@ -0,0 +1,320 @@ +/** + * Chat store - manages WebSocket connection and chat state + */ + +const isBrowser = typeof window !== 'undefined'; + +export interface ChatConversation { + id: string; + title: string; + created_at: string; + updated_at?: string; +} + +export interface ToolCall { + id: string; + name: string; + input: Record; +} + +export interface ToolResult { + tool_use_id: string; + result: unknown; +} + +export interface ChatMessage { + id: string; + role: 'user' | 'assistant'; + content: string; + tool_calls?: ToolCall[]; + tool_results?: ToolResult[]; + created_at: string; +} + +export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error'; + +function createChatStore() { + // Connection state + let ws: WebSocket | null = null; + let connectionStatus = $state('disconnected'); + let reconnectAttempts = 0; + const maxReconnectAttempts = 5; + let reconnectTimeout: ReturnType | null = null; + + // UI state + let isOpen = $state(false); + let isStreaming = $state(false); + + // Data state + let conversations = $state([]); + let activeConversationId = $state(null); + let messages = $state([]); + let streamingContent = $state(''); + let currentToolCalls = $state([]); + let user = $state<{ id: string; name: string; email: string } | null>(null); + let introMessage = $state(null); + + // Derive active conversation + const activeConversation = $derived( + conversations.find((c) => c.id === activeConversationId) ?? null + ); + + function getWebSocketUrl(): string { + // In development, connect directly to Django + // In production, go through the proxy + if (isBrowser) { + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + // Replace app. or admin. with api. for the backend + const host = window.location.host.replace(/^(app|admin)\./, 'api.'); + return `${protocol}//${host}/v1/ws/chat/`; + } + return ''; + } + + function connect(): void { + if (!isBrowser || ws?.readyState === WebSocket.OPEN) return; + + connectionStatus = 'connecting'; + + try { + ws = new WebSocket(getWebSocketUrl()); + + ws.onopen = () => { + connectionStatus = 'connected'; + reconnectAttempts = 0; + // Request conversation list + send({ type: 'conversations' }); + }; + + ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + handleMessage(data); + } catch (e) { + console.error('Failed to parse WebSocket message:', e); + } + }; + + ws.onclose = (event) => { + connectionStatus = 'disconnected'; + ws = null; + + // Attempt reconnection if not a normal close + if (event.code !== 1000 && reconnectAttempts < maxReconnectAttempts) { + const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000); + reconnectTimeout = setTimeout(() => { + reconnectAttempts++; + connect(); + }, delay); + } + }; + + ws.onerror = () => { + connectionStatus = 'error'; + }; + } catch (e) { + console.error('Failed to create WebSocket:', e); + connectionStatus = 'error'; + } + } + + function disconnect(): void { + if (reconnectTimeout) { + clearTimeout(reconnectTimeout); + reconnectTimeout = null; + } + if (ws) { + ws.close(1000); + ws = null; + } + connectionStatus = 'disconnected'; + } + + function send(data: Record): void { + if (ws?.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(data)); + } + } + + function handleMessage(data: Record): void { + const type = data.type as string; + + switch (type) { + case 'connected': + user = data.user as { id: string; name: string; email: string }; + break; + + case 'intro': + introMessage = data.content as string; + break; + + case 'conversations': + conversations = data.conversations as ChatConversation[]; + break; + + case 'conversation_created': + { + const conv = data.conversation as ChatConversation; + conversations = [conv, ...conversations]; + activeConversationId = conv.id; + } + break; + + case 'history': + { + const historyData = data.conversation as { + id: string; + messages: ChatMessage[]; + }; + messages = historyData.messages; + } + break; + + case 'user_message': + { + const userMsg = data.message as ChatMessage; + messages = [...messages, userMsg]; + } + break; + + case 'stream_start': + isStreaming = true; + streamingContent = ''; + currentToolCalls = []; + break; + + case 'content_delta': + streamingContent += data.content as string; + break; + + case 'tool_use': + currentToolCalls = [...currentToolCalls, data.tool as ToolCall]; + break; + + case 'tool_result': + // Tool results are handled as part of the message + break; + + case 'stream_end': + { + isStreaming = false; + const assistantMsg = data.message as ChatMessage; + messages = [...messages, assistantMsg]; + streamingContent = ''; + currentToolCalls = []; + + // Update conversation title if provided + if (data.title && activeConversationId) { + conversations = conversations.map((c) => + c.id === activeConversationId ? { ...c, title: data.title as string } : c + ); + } + } + break; + + case 'error': + console.error('Chat error:', data.error); + isStreaming = false; + break; + } + } + + function sendMessage(content: string): void { + if (!content.trim() || isStreaming) return; + + send({ + type: 'chat', + content: content.trim(), + conversation_id: activeConversationId + }); + } + + function selectConversation(conversationId: string): void { + if (conversationId === activeConversationId) return; + + activeConversationId = conversationId; + messages = []; + + // Request history for this conversation + send({ + type: 'history', + conversation_id: conversationId + }); + } + + function startNewConversation(): void { + activeConversationId = null; + messages = []; + } + + function toggle(): void { + isOpen = !isOpen; + if (isOpen && connectionStatus === 'disconnected') { + connect(); + } + } + + function open(): void { + isOpen = true; + if (connectionStatus === 'disconnected') { + connect(); + } + } + + function close(): void { + isOpen = false; + } + + return { + // Connection + get connectionStatus() { + return connectionStatus; + }, + connect, + disconnect, + + // UI + get isOpen() { + return isOpen; + }, + get isStreaming() { + return isStreaming; + }, + toggle, + open, + close, + + // Data + get user() { + return user; + }, + get conversations() { + return conversations; + }, + get activeConversation() { + return activeConversation; + }, + get activeConversationId() { + return activeConversationId; + }, + get messages() { + return messages; + }, + get streamingContent() { + return streamingContent; + }, + get currentToolCalls() { + return currentToolCalls; + }, + get introMessage() { + return introMessage; + }, + + // Actions + sendMessage, + selectConversation, + startNewConversation + }; +} + +export const chat = createChatStore(); diff --git a/src/lib/stores/offCanvas.svelte.ts b/src/lib/stores/offCanvas.svelte.ts new file mode 100644 index 0000000..528a0a6 --- /dev/null +++ b/src/lib/stores/offCanvas.svelte.ts @@ -0,0 +1,55 @@ +import type { Snippet } from 'svelte'; + +export interface OffCanvasContent { + title?: string; + content: Snippet; + width?: string; + class?: string; +} + +export interface OffCanvasState { + isOpen: boolean; + content: OffCanvasContent | null; +} + +function createOffCanvasStore() { + let right = $state({ + isOpen: false, + content: null + }); + + return { + get right() { + return right; + }, + get isRightOpen() { + return right.isOpen; + }, + get rightContent() { + return right.content; + }, + + showRight(content: OffCanvasContent) { + right = { + isOpen: true, + content + }; + }, + + closeRight() { + right = { + isOpen: false, + content: null + }; + }, + + closeAll() { + right = { + isOpen: false, + content: null + }; + } + }; +} + +export const offCanvas = createOffCanvasStore(); diff --git a/src/lib/stores/scopeEditor.svelte.ts b/src/lib/stores/scopeEditor.svelte.ts new file mode 100644 index 0000000..53742ff --- /dev/null +++ b/src/lib/stores/scopeEditor.svelte.ts @@ -0,0 +1,39 @@ +import { type GetAccount$result } from '$houdini'; + +// Types +export type ScopeType = NonNullable< + GetAccount$result['account'] +>['addresses'][number]['scopes'][number]; +export type AreaType = ScopeType['areas'][number]; +export type TaskType = AreaType['tasks'][number]; + +// Frequency options for tasks - values must be lowercase to match backend enum +export const FREQUENCY_OPTIONS = [ + { value: 'daily', label: 'Daily' }, + { value: 'weekly', label: 'Weekly' }, + { value: 'monthly', label: 'Monthly' }, + { value: 'quarterly', label: 'Quarterly' }, + { value: 'triannual', label: '3x/year' }, + { value: 'annual', label: 'Annual' }, + { value: 'as_needed', label: 'As Needed' } +] as const; + +export function getFrequencyLabel(frequency: string): string { + const lower = frequency?.toLowerCase(); + const option = FREQUENCY_OPTIONS.find((o) => o.value === lower); + return option?.label ?? frequency; +} + +export function getFrequencyShortLabel(frequency: string): string { + const labels: Record = { + daily: 'Daily', + weekly: 'Weekly', + monthly: 'Monthly', + quarterly: 'Qtrly', + triannual: '3x/yr', + annual: 'Annual', + as_needed: 'PRN' + }; + const lower = frequency?.toLowerCase(); + return labels[lower] || frequency; +} diff --git a/src/lib/stores/theme.svelte.ts b/src/lib/stores/theme.svelte.ts new file mode 100644 index 0000000..b631a7a --- /dev/null +++ b/src/lib/stores/theme.svelte.ts @@ -0,0 +1,79 @@ +import { browser } from '$app/environment'; + +type Theme = 'light' | 'dark' | 'system'; + +const STORAGE_KEY = 'theme-preference'; + +function getSystemTheme(): 'light' | 'dark' { + if (!browser) return 'light'; + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +} + +function getStoredTheme(): Theme { + if (!browser) return 'system'; + const stored = localStorage.getItem(STORAGE_KEY); + if (stored === 'light' || stored === 'dark' || stored === 'system') { + return stored; + } + return 'system'; +} + +function createThemeStore() { + let preference = $state(getStoredTheme()); + let resolved = $derived<'light' | 'dark'>( + preference === 'system' ? getSystemTheme() : preference + ); + + function applyTheme(theme: 'light' | 'dark') { + if (!browser) return; + document.documentElement.classList.remove('light', 'dark'); + document.documentElement.classList.add(theme); + } + + function setTheme(theme: Theme) { + preference = theme; + + if (browser) { + localStorage.setItem(STORAGE_KEY, theme); + applyTheme(resolved); + } + } + + function toggle() { + const newTheme = resolved === 'light' ? 'dark' : 'light'; + setTheme(newTheme); + } + + function init() { + if (!browser) return; + + applyTheme(resolved); + + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handleChange = (e: MediaQueryListEvent) => { + if (preference === 'system') { + resolved = e.matches ? 'dark' : 'light'; + applyTheme(resolved); + } + }; + + mediaQuery.addEventListener('change', handleChange); + } + + return { + get preference() { + return preference; + }, + get resolved() { + return resolved; + }, + get isDark() { + return resolved === 'dark'; + }, + setTheme, + toggle, + init + }; +} + +export const theme = createThemeStore(); diff --git a/src/lib/stores/unreadCounts.svelte.ts b/src/lib/stores/unreadCounts.svelte.ts new file mode 100644 index 0000000..0b10d7b --- /dev/null +++ b/src/lib/stores/unreadCounts.svelte.ts @@ -0,0 +1,116 @@ +import { GetUnreadMessageCountStore, GetUnreadNotificationCountStore } from '$houdini'; + +const isBrowser = typeof window !== 'undefined'; +const POLL_INTERVAL = 30000; // 30 seconds + +interface UnreadCounts { + messages: number; + notifications: number; +} + +function createUnreadCountsStore() { + let counts = $state({ messages: 0, notifications: 0 }); + let pollIntervalId: ReturnType | null = null; + let isPolling = $state(false); + + const messageCountStore = new GetUnreadMessageCountStore(); + const notificationCountStore = new GetUnreadNotificationCountStore(); + + async function fetchCounts(): Promise { + if (!isBrowser) return; + + try { + // Fetch both counts in parallel + const [messageResult, notificationResult] = await Promise.all([ + messageCountStore.fetch(), + notificationCountStore.fetch() + ]); + + counts = { + messages: messageResult.data?.unreadMessageCount ?? 0, + notifications: notificationResult.data?.myUnreadNotificationCount ?? 0 + }; + } catch (error) { + console.warn('Failed to fetch unread counts:', error); + } + } + + function startPolling(): void { + if (!isBrowser || isPolling) return; + + isPolling = true; + + // Initial fetch + fetchCounts(); + + // Set up polling interval + pollIntervalId = setInterval(() => { + fetchCounts(); + }, POLL_INTERVAL); + } + + function stopPolling(): void { + if (pollIntervalId) { + clearInterval(pollIntervalId); + pollIntervalId = null; + } + isPolling = false; + } + + function resetCounts(): void { + counts = { messages: 0, notifications: 0 }; + } + + function decrementMessages(by: number = 1): void { + counts = { + ...counts, + messages: Math.max(0, counts.messages - by) + }; + } + + function decrementNotifications(by: number = 1): void { + counts = { + ...counts, + notifications: Math.max(0, counts.notifications - by) + }; + } + + function incrementMessages(by: number = 1): void { + counts = { + ...counts, + messages: counts.messages + by + }; + } + + function incrementNotifications(by: number = 1): void { + counts = { + ...counts, + notifications: counts.notifications + by + }; + } + + return { + get counts() { + return counts; + }, + get messageCount() { + return counts.messages; + }, + get notificationCount() { + return counts.notifications; + }, + get isPolling() { + return isPolling; + }, + startPolling, + stopPolling, + fetchCounts, + resetCounts, + decrementMessages, + decrementNotifications, + incrementMessages, + incrementNotifications + }; +} + +export const unreadCounts = createUnreadCountsStore(); diff --git a/src/lib/utils/date.ts b/src/lib/utils/date.ts new file mode 100644 index 0000000..f0ff804 --- /dev/null +++ b/src/lib/utils/date.ts @@ -0,0 +1,67 @@ +import { parseISO, format } from 'date-fns'; + +export function formatDate(dateString: string | null | undefined): string { + if (!dateString) return ''; + try { + return format(parseISO(dateString), 'M/d/yyyy'); + } catch { + return dateString; + } +} + +export function formatDateTime(dateString: string | null | undefined): string { + if (!dateString) return ''; + try { + const date = parseISO(dateString); + return format(date, "M/d/yyyy 'at' h:mm a"); + } catch { + return dateString; + } +} + +/** + * Get the current month in YYYY-MM format + */ +export function getCurrentMonth(): string { + const now = new Date(); + const year = now.getFullYear(); + const month = (now.getMonth() + 1).toString().padStart(2, '0'); + return `${year}-${month}`; +} + +/** + * Parse a YYYY-MM string into start and end dates for that month + */ +export function getMonthDateRange(month: string): { start: string; end: string } { + const [year, monthNum] = month.split('-').map(Number); + + // First day of month + const start = `${year}-${monthNum.toString().padStart(2, '0')}-01`; + + // Last day of month (day 0 of next month = last day of current month) + const lastDay = new Date(year, monthNum, 0).getDate(); + const end = `${year}-${monthNum.toString().padStart(2, '0')}-${lastDay.toString().padStart(2, '0')}`; + + return { start, end }; +} + +/** + * Get the current week's date range (Monday to Sunday) in YYYY-MM-DD format + */ +export function getWeekDateRange(): { start: string; end: string } { + const now = new Date(); + const day = now.getDay(); + const diff = day === 0 ? -6 : 1 - day; // Monday = start + + const monday = new Date(now.getFullYear(), now.getMonth(), now.getDate() + diff); + const sunday = new Date(monday.getFullYear(), monday.getMonth(), monday.getDate() + 6); + + const formatYMD = (d: Date) => { + const y = d.getFullYear(); + const m = (d.getMonth() + 1).toString().padStart(2, '0'); + const day = d.getDate().toString().padStart(2, '0'); + return `${y}-${m}-${day}`; + }; + + return { start: formatYMD(monday), end: formatYMD(sunday) }; +} diff --git a/src/lib/utils/format.ts b/src/lib/utils/format.ts new file mode 100644 index 0000000..a9927bd --- /dev/null +++ b/src/lib/utils/format.ts @@ -0,0 +1,43 @@ +/** + * Shared formatting utilities for display purposes + */ + +/** + * Format a date string for display + * @param dateStr - Date string in YYYY-MM-DD format + */ +export function formatDate(dateStr: string): string { + const date = new Date(dateStr + 'T00:00:00'); + return date.toLocaleDateString('en-US', { + weekday: 'long', + month: 'long', + day: 'numeric', + year: 'numeric' + }); +} + +/** + * Format a status enum value for display + * @param status - Status string like "IN_PROGRESS" + * @returns Formatted string like "In Progress" + */ +export function formatStatus(status: string): string { + return status.replace('_', ' '); +} + +/** + * Get the CSS class for a status badge + * @param status - Status string + */ +export function getStatusBadgeClass(status: string): string { + switch (status) { + case 'SCHEDULED': + return 'badge-primary'; + case 'IN_PROGRESS': + return 'badge-accent'; + case 'COMPLETED': + return 'badge-secondary'; + default: + return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200'; + } +} diff --git a/src/lib/utils/invalidate.ts b/src/lib/utils/invalidate.ts new file mode 100644 index 0000000..59aaa6e --- /dev/null +++ b/src/lib/utils/invalidate.ts @@ -0,0 +1,25 @@ +import { invalidate, invalidateAll } from '$app/navigation'; + +/** + * Invalidate dashboard data after mutations. + * This triggers re-fetch of layout data that uses depends('app:dashboard'). + */ +export async function invalidateDashboard() { + await invalidate('app:dashboard'); +} + +/** + * Invalidate account data after account-related mutations. + * This triggers re-fetch of layout data that uses depends('app:accounts'). + */ +export async function invalidateAccounts() { + await invalidate('app:accounts'); +} + +/** + * Invalidate all data - use sparingly. + * Prefer targeted invalidation (invalidateDashboard, invalidateAccounts) when possible. + */ +export async function invalidateEverything() { + await invalidateAll(); +} diff --git a/src/lib/utils/lookup.ts b/src/lib/utils/lookup.ts new file mode 100644 index 0000000..86b91a3 --- /dev/null +++ b/src/lib/utils/lookup.ts @@ -0,0 +1,155 @@ +import type { AccountsBasic$result, Customers$result, Team$result } from '$houdini'; +import { fromGlobalId } from './relay'; + +export interface AccountAddressInfo { + accountId: string; + accountName: string; + addressId: string; + addressName: string; + streetAddress: string | null; + city: string | null; + state: string | null; + zipCode: string | null; +} + +export interface CustomerInfo { + customerId: string; + customerName: string; +} + +/** + * Builds a lookup map from accountAddressId (UUID) to account/address info. + * The accounts data has addresses with Relay IDs, but services/projects have UUID addressIds. + */ +export function buildAccountAddressLookup( + accounts: AccountsBasic$result['accounts'] | null | undefined +): Map { + const lookup = new Map(); + + if (!accounts) return lookup; + + for (const account of accounts) { + const accountId = fromGlobalId(account.id); + const accountName = account.name; + + // Add primary address if exists + if (account.primaryAddress) { + const addressId = fromGlobalId(account.primaryAddress.id); + lookup.set(addressId, { + accountId, + accountName, + addressId, + addressName: 'Primary Service Address', + streetAddress: account.primaryAddress.streetAddress ?? null, + city: account.primaryAddress.city ?? null, + state: account.primaryAddress.state ?? null, + zipCode: account.primaryAddress.zipCode ?? null + }); + } + + // Add all addresses from the addresses array + if (account.addresses) { + for (const address of account.addresses) { + const addressId = fromGlobalId(address.id); + lookup.set(addressId, { + accountId, + accountName, + addressId, + addressName: address.name || 'Primary Service Address', + streetAddress: address.streetAddress ?? null, + city: address.city ?? null, + state: address.state ?? null, + zipCode: address.zipCode ?? null + }); + } + } + } + + return lookup; +} + +/** + * Builds a lookup map from customerId (UUID) to customer info. + */ +export function buildCustomerLookup( + customers: Customers$result['customers'] | null | undefined +): Map { + const lookup = new Map(); + + if (!customers) return lookup; + + for (const customer of customers) { + const customerId = fromGlobalId(customer.id); + lookup.set(customerId, { + customerId, + customerName: customer.name + }); + } + + return lookup; +} + +/** + * Formats an address for display. + */ +export function formatAddress(info: { + streetAddress?: string | null; + city?: string | null; + state?: string | null; + zipCode?: string | null; +}): string | null { + const { streetAddress, city, state, zipCode } = info; + const cityStateZip = [city, state].filter(Boolean).join(', ') + (zipCode ? ` ${zipCode}` : ''); + const parts = [streetAddress, cityStateZip].filter(Boolean); + return parts.length > 0 ? parts.join(', ') : null; +} + +/** + * Builds a Set of non-ADMIN team member IDs (UUIDs). + * Used to filter team member counts on services/projects. + */ +export function buildNonAdminTeamIds( + teamProfiles: Team$result['teamProfiles'] | null | undefined +): Set { + const nonAdminIds = new Set(); + + if (!teamProfiles) return nonAdminIds; + + for (const profile of teamProfiles) { + if (profile.role !== 'ADMIN') { + const id = fromGlobalId(profile.id); + nonAdminIds.add(id); + } + } + + return nonAdminIds; +} + +export interface TeamMemberInfo { + id: string; // UUID + fullName: string; + role: string; +} + +/** + * Builds a lookup map from team member UUID to their info. + * The teamProfiles have Relay IDs, but services/projects have UUIDs in teamMembers.pk. + */ +export function buildTeamMemberLookup( + teamProfiles: Team$result['teamProfiles'] | null | undefined +): Map { + const lookup = new Map(); + + if (!teamProfiles) return lookup; + + for (const profile of teamProfiles) { + const id = fromGlobalId(profile.id); + lookup.set(id, { + id, + fullName: profile.fullName ?? 'Unknown', + role: profile.role + }); + } + + return lookup; +} diff --git a/src/lib/utils/messages.ts b/src/lib/utils/messages.ts new file mode 100644 index 0000000..b2c7c97 --- /dev/null +++ b/src/lib/utils/messages.ts @@ -0,0 +1,453 @@ +import { + CreateConversationStore, + SendMessageStore, + DeleteMessageStore, + MarkConversationAsReadStore, + ArchiveConversationStore, + MuteConversationStore, + DeleteConversationStore +} from '$houdini'; +import type { GetMyConversations$result, GetConversation$result } from '$houdini'; +import type { AccountAddressInfo, CustomerInfo } from '$lib/utils/lookup'; +import { formatDate } from '$lib/utils/date'; +import { toGlobalId } from '$lib/utils/relay'; + +// Type aliases for convenience +export type ConversationEdge = NonNullable< + NonNullable['edges'] +>[number]; +export type Conversation = NonNullable['node']; +export type ConversationDetail = NonNullable; +export type Message = NonNullable[number]; +export type Participant = NonNullable[number]; +export type ParticipantProfile = NonNullable; + +// Conversation type labels +const CONVERSATION_TYPE_LABELS: Record = { + DIRECT: 'Direct', + GROUP: 'Group', + SUPPORT: 'Support' +}; + +// Mutation stores (singleton instances) +const createConversationStore = new CreateConversationStore(); +const sendMessageStore = new SendMessageStore(); +const deleteMessageStore = new DeleteMessageStore(); +const markAsReadStore = new MarkConversationAsReadStore(); +const archiveConversationStore = new ArchiveConversationStore(); +const muteConversationStore = new MuteConversationStore(); +const deleteConversationStore = new DeleteConversationStore(); + +/** + * Check if a string is a valid UUID format + */ +function isUuid(str: string): boolean { + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + return uuidRegex.test(str); +} + +/** + * Convert participant ID to Global ID format if needed + * Backend expects Global IDs (base64 encoded), not raw UUIDs + */ +function ensureGlobalId(id: string): string { + if (!id) return id; + + // If it's a UUID, convert to Global ID (assume TeamProfileType) + if (isUuid(id)) { + return toGlobalId('TeamProfileType', id); + } + + // Already a Global ID (base64 encoded strings are typically longer and may contain =) + return id; +} + +/** + * Create a new conversation + */ +export async function createConversation(input: { + subject: string; + conversationType: string; + participantIds: string[]; + entityType?: string; + entityId?: string; + metadata?: string; +}) { + // Convert participant IDs to Global IDs if they're UUIDs + // Backend expects GlobalID format (base64 encoded) + const globalParticipantIds = input.participantIds.map(ensureGlobalId); + + const result = await createConversationStore.mutate({ + input: { + subject: input.subject, + conversationType: input.conversationType, + participantIds: globalParticipantIds, + entityType: input.entityType ?? null, + entityId: input.entityId ?? null, + metadata: input.metadata ?? null + } + }); + + if (result.errors?.length) { + throw new Error(result.errors[0].message); + } + + return result.data?.createConversation; +} + +/** + * Send a message in a conversation + */ +export async function sendMessage(input: { + conversationId: string; + body: string; + replyToId?: string; + attachments?: string; + metadata?: string; +}) { + const result = await sendMessageStore.mutate({ + input: { + conversationId: input.conversationId, + body: input.body, + replyToId: input.replyToId ?? null, + attachments: input.attachments ?? null, + metadata: input.metadata ?? null + } + }); + + if (result.errors?.length) { + throw new Error(result.errors[0].message); + } + + return result.data?.sendMessage; +} + +/** + * Delete a message + */ +export async function deleteMessage(id: string) { + const result = await deleteMessageStore.mutate({ id }); + + if (result.errors?.length) { + throw new Error(result.errors[0].message); + } + + return result.data?.deleteMessage; +} + +/** + * Mark a conversation as read + */ +export async function markAsRead(conversationId: string) { + const result = await markAsReadStore.mutate({ + input: { conversationId } + }); + + if (result.errors?.length) { + throw new Error(result.errors[0].message); + } + + return result.data?.markConversationAsRead; +} + +/** + * Archive or unarchive a conversation + */ +export async function archiveConversation(conversationId: string, isArchived: boolean) { + const result = await archiveConversationStore.mutate({ + input: { conversationId, isArchived } + }); + + if (result.errors?.length) { + throw new Error(result.errors[0].message); + } + + return result.data?.archiveConversation; +} + +/** + * Mute or unmute a conversation + */ +export async function muteConversation(conversationId: string, isMuted: boolean) { + const result = await muteConversationStore.mutate({ + input: { conversationId, isMuted } + }); + + if (result.errors?.length) { + throw new Error(result.errors[0].message); + } + + return result.data?.muteConversation; +} + +/** + * Delete a conversation + */ +export async function deleteConversation(id: string) { + const result = await deleteConversationStore.mutate({ id }); + + if (result.errors?.length) { + throw new Error(result.errors[0].message); + } + + return result.data?.deleteConversation; +} + +// Minimal profile type for name extraction +type ProfileWithName = { + teamProfile?: { id?: string; fullName?: string | null } | null; + customerProfile?: { id?: string; fullName?: string | null } | null; +}; + +/** + * Get display name from participant profile + */ +export function getParticipantName(participant: ProfileWithName | null | undefined): string { + if (!participant) return 'Unknown'; + + if (participant.teamProfile) { + return participant.teamProfile.fullName ?? 'Team Member'; + } + + if (participant.customerProfile) { + return participant.customerProfile.fullName ?? 'Customer'; + } + + return 'Unknown'; +} + +/** + * Get sender name from message sender + */ +export function getSenderName(sender: Message['sender'] | null | undefined): string { + if (!sender) return 'Unknown'; + + if (sender.teamProfile) { + return sender.teamProfile.fullName ?? 'Team Member'; + } + + if (sender.customerProfile) { + return sender.customerProfile.fullName ?? 'Customer'; + } + + return 'Unknown'; +} + +/** + * Get sender ID from message sender + */ +export function getSenderId(sender: Message['sender'] | null | undefined): string | null { + if (!sender) return null; + + if (sender.teamProfile) { + return sender.teamProfile.id; + } + + if (sender.customerProfile) { + return sender.customerProfile.id; + } + + return null; +} + +/** + * Get conversation type label + */ +export function getConversationTypeLabel(type: string | null | undefined): string { + if (!type) return 'Message'; + return CONVERSATION_TYPE_LABELS[type] ?? type; +} + +interface EntityDisplayOptions { + accountLookup?: Map; + customerLookup?: Map; + userType?: 'team' | 'customer'; +} + +/** + * Get entity display info from conversation + * - Service: " - " + * - Project: " - - " or " - - " + */ +export function getEntityDisplay( + entity: ConversationDetail['entity'] | null | undefined, + options: EntityDisplayOptions = {} +): { + type: string; + name: string; + id: string; + link: string | null; +} | null { + if (!entity) return null; + + const { entityType, entityId } = entity; + const { accountLookup, customerLookup, userType } = options; + + // Determine base path based on user type + const basePath = userType === 'customer' ? '/customer' : '/team'; + + if (entity.service) { + const service = entity.service; + const date = formatDate(service.date) || 'Service'; + + // Look up account name via accountAddressId + let accountName = ''; + if (accountLookup && service.accountAddressId) { + const accountInfo = accountLookup.get(service.accountAddressId); + if (accountInfo) { + accountName = accountInfo.accountName; + } + } + + const displayName = accountName ? `${date} - ${accountName}` : date; + + // For customers, link to schedule (they can navigate to history if needed) + // For team, always use /team/services + const serviceLink = + userType === 'customer' + ? `/customer/schedule/service/${service.id}` + : `/team/services/${service.id}`; + + return { + type: 'Service', + name: displayName, + id: service.id, + link: serviceLink + }; + } + + if (entity.project) { + const project = entity.project; + const date = formatDate(project.date) || ''; + const projectName = project.name || 'Project'; + + // Look up account name via accountAddressId, or customer name via customerId + let ownerName = ''; + if (accountLookup && project.accountAddressId) { + const accountInfo = accountLookup.get(project.accountAddressId); + if (accountInfo) { + ownerName = accountInfo.accountName; + } + } else if (customerLookup && project.customerId) { + const customerInfo = customerLookup.get(project.customerId); + if (customerInfo) { + ownerName = customerInfo.customerName; + } + } + + const parts = [date, projectName, ownerName].filter(Boolean); + const displayName = parts.join(' - '); + + // For customers, link to schedule (they can navigate to history if needed) + // For team, always use /team/projects + const projectLink = + userType === 'customer' + ? `/customer/schedule/project/${project.id}` + : `/team/projects/${project.id}`; + + return { + type: 'Project', + name: displayName, + id: project.id, + link: projectLink + }; + } + + if (entity.account) { + return { + type: 'Account', + name: entity.account.name ?? 'Account', + id: entity.account.id, + link: `${basePath}/accounts/${entity.account.id}` + }; + } + + if (entity.customer) { + return { + type: 'Customer', + name: entity.customer.name ?? 'Customer', + id: entity.customer.id, + link: null + }; + } + + // Fallback to entity type and ID + if (entityType && entityId) { + return { + type: entityType, + name: entityType, + id: entityId, + link: null + }; + } + + return null; +} + +/** + * Format count for display (99+ for large numbers) + */ +export function formatCount(count: number): string { + if (count <= 0) return ''; + if (count > 99) return '99+'; + return count.toString(); +} + +// Minimal participant type for name extraction +type ParticipantWithProfile = { + participant: { + teamProfile?: { id: string; fullName?: string | null } | null; + customerProfile?: { id: string; fullName?: string | null } | null; + }; +}; + +/** + * Get list of participant names (excluding current user) + */ +export function getParticipantNames( + participants: ParticipantWithProfile[] | null | undefined, + currentUserId?: string, + maxNames: number = 3 +): string { + if (!participants || participants.length === 0) return ''; + + const names = participants + .map((p) => { + const profile = p.participant; + const id = profile?.teamProfile?.id ?? profile?.customerProfile?.id; + + // Skip current user + if (currentUserId && id === currentUserId) return null; + + return getParticipantName(profile); + }) + .filter((name): name is string => name !== null); + + if (names.length === 0) return ''; + + if (names.length <= maxNames) { + return names.join(', '); + } + + const displayNames = names.slice(0, maxNames); + const remaining = names.length - maxNames; + return `${displayNames.join(', ')} +${remaining} more`; +} + +/** + * Get last message preview + */ +export function getLastMessagePreview( + messages: Conversation['messages'] | null | undefined, + maxLength: number = 50 +): string { + if (!messages || messages.length === 0) return 'No messages yet'; + + const lastMessage = messages[0]; + if (!lastMessage?.body) return 'No messages yet'; + + const body = lastMessage.body; + if (body.length <= maxLength) return body; + + return body.substring(0, maxLength) + '...'; +} diff --git a/src/lib/utils/notifications.ts b/src/lib/utils/notifications.ts new file mode 100644 index 0000000..db1b91c --- /dev/null +++ b/src/lib/utils/notifications.ts @@ -0,0 +1,211 @@ +import { + MarkNotificationAsReadStore, + MarkAllNotificationsAsReadStore, + DeleteNotificationStore +} from '$houdini'; +import type { GetMyNotifications$result, GetNotification$result } from '$houdini'; + +// Type aliases for convenience +export type Notification = NonNullable[number]; +export type NotificationDetail = NonNullable; +export type NotificationEvent = NonNullable; + +// Event type labels for display +const EVENT_TYPE_LABELS: Record = { + // Account events + ACCOUNT_CREATED: 'Account Created', + ACCOUNT_UPDATED: 'Account Updated', + ACCOUNT_DEACTIVATED: 'Account Deactivated', + ACCOUNT_REACTIVATED: 'Account Reactivated', + + // Customer events + CUSTOMER_CREATED: 'Customer Created', + CUSTOMER_UPDATED: 'Customer Updated', + + // Service events + SERVICE_CREATED: 'Service Created', + SERVICE_SCHEDULED: 'Service Scheduled', + SERVICE_STARTED: 'Service Started', + SERVICE_COMPLETED: 'Service Completed', + SERVICE_CANCELLED: 'Service Cancelled', + + // Project events + PROJECT_CREATED: 'Project Created', + PROJECT_SCHEDULED: 'Project Scheduled', + PROJECT_STARTED: 'Project Started', + PROJECT_COMPLETED: 'Project Completed', + PROJECT_CANCELLED: 'Project Cancelled', + + // Invoice events + INVOICE_CREATED: 'Invoice Created', + INVOICE_SENT: 'Invoice Sent', + INVOICE_PAID: 'Invoice Paid', + INVOICE_OVERDUE: 'Invoice Overdue', + + // Report events + REPORT_CREATED: 'Report Created', + REPORT_APPROVED: 'Report Approved', + REPORT_REJECTED: 'Report Rejected', + + // Messaging events + CONVERSATION_CREATED: 'Conversation Created', + CONVERSATION_ARCHIVED: 'Conversation Archived', + CONVERSATION_PARTICIPANT_ADDED: 'Participant Added', + CONVERSATION_PARTICIPANT_REMOVED: 'Participant Removed', + MESSAGE_SENT: 'Message Sent', + MESSAGE_RECEIVED: 'Message Received', + MESSAGE_READ: 'Message Read', + MESSAGE_DELETED: 'Message Deleted' +}; + +// Mutation stores (singleton instances) +const markAsReadStore = new MarkNotificationAsReadStore(); +const markAllAsReadStore = new MarkAllNotificationsAsReadStore(); +const deleteNotificationStore = new DeleteNotificationStore(); + +/** + * Mark a notification as read + */ +export async function markAsRead(id: string) { + const result = await markAsReadStore.mutate({ id }); + + if (result.errors?.length) { + throw new Error(result.errors[0].message); + } + + return result.data?.markNotificationAsRead; +} + +/** + * Mark all notifications as read + */ +export async function markAllAsRead() { + const result = await markAllAsReadStore.mutate(null); + + if (result.errors?.length) { + throw new Error(result.errors[0].message); + } + + return result.data?.markAllNotificationsAsRead; +} + +/** + * Delete a notification + */ +export async function deleteNotification(id: string) { + const result = await deleteNotificationStore.mutate({ id }); + + if (result.errors?.length) { + throw new Error(result.errors[0].message); + } + + return result.data?.deleteNotification; +} + +/** + * Get event type label for display + */ +export function getEventTypeLabel(eventType: string | null | undefined): string { + if (!eventType) return 'Event'; + return EVENT_TYPE_LABELS[eventType] ?? formatEventType(eventType); +} + +/** + * Format event type to human readable (fallback) + */ +function formatEventType(eventType: string): string { + return eventType + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(' '); +} + +/** + * Get action URL from notification + * Falls back to constructing URL from entity info + */ +export function getActionUrl(notification: Notification | NotificationDetail): string | null { + // Use explicit action URL if provided + if (notification.actionUrl) { + return notification.actionUrl; + } + + // Try to construct URL from entity info + const event = notification.event; + if (!event?.entityType || !event?.entityId) { + return null; + } + + const entityType = event.entityType.toLowerCase(); + const entityId = event.entityId; + + // Map entity types to routes + const routeMap: Record = { + project: `/admin/projects/${entityId}`, + service: `/admin/services/${entityId}`, + account: `/admin/accounts/${entityId}`, + customer: `/admin/customers/${entityId}`, + invoice: `/admin/invoices/${entityId}`, + report: `/admin/reports/${entityId}`, + conversation: `/messages/${entityId}`, + message: `/messages` // Messages don't have direct routes + }; + + return routeMap[entityType] ?? null; +} + +/** + * Format count for display (99+ for large numbers) + */ +export function formatCount(count: number): string { + if (count <= 0) return ''; + if (count > 99) return '99+'; + return count.toString(); +} + +/** + * Get notification body preview + */ +export function getBodyPreview(body: string | null | undefined, maxLength: number = 100): string { + if (!body) return ''; + + if (body.length <= maxLength) return body; + + return body.substring(0, maxLength) + '...'; +} + +/** + * Get entity type display name + */ +export function getEntityTypeLabel(entityType: string | null | undefined): string { + if (!entityType) return ''; + + const labels: Record = { + Project: 'Project', + Service: 'Service', + Account: 'Account', + Customer: 'Customer', + Invoice: 'Invoice', + Report: 'Report', + Conversation: 'Conversation', + Message: 'Message' + }; + + return labels[entityType] ?? entityType; +} + +/** + * Get notification status label + */ +export function getStatusLabel(status: string | null | undefined): string { + if (!status) return ''; + + const labels: Record = { + PENDING: 'Pending', + SENT: 'Sent', + READ: 'Read', + FAILED: 'Failed' + }; + + return labels[status] ?? status; +} diff --git a/src/lib/utils/relay.ts b/src/lib/utils/relay.ts new file mode 100644 index 0000000..716366d --- /dev/null +++ b/src/lib/utils/relay.ts @@ -0,0 +1,38 @@ +/** + * Decodes a Relay Global ID to extract the raw ID. + * @param globalId The Base64-encoded Relay ID (e.g., "Q3VzdG9tZXJUeXBlOjE=") + * @returns The raw ID (e.g., "1") + */ +export function fromGlobalId(globalId: string): string { + if (!globalId) return ''; + + let decoded: string; + try { + decoded = atob(globalId); + } catch (e) { + console.error('Failed to decode global ID:', e); + return ''; + } + + const sep = decoded.indexOf(':'); + if (sep <= 0 || sep === decoded.length - 1) { + // Silently fail for invalid Global ID format + return ''; + } + + return decoded.slice(sep + 1); +} + +/** + * Encodes a raw ID and type name into a Relay Global ID. + * @param typeName The GraphQL object type (e.g., 'CustomerType') + * @param id The raw ID (e.g., "1") + * @returns The Base64-encoded Relay ID (e.g., "Q3VzdG9tZXJUeXBlOjE=") + */ +export function toGlobalId(typeName: string, id: string): string { + if (!typeName || !id) { + console.error('Both typeName and id must be provided for encoding.'); + return ''; + } + return btoa(`${typeName}:${id}`); +} diff --git a/src/lib/utils/scopes.ts b/src/lib/utils/scopes.ts new file mode 100644 index 0000000..dcebf6b --- /dev/null +++ b/src/lib/utils/scopes.ts @@ -0,0 +1,946 @@ +import { + GetScopeTemplatesStore, + GetProjectScopeTemplatesStore, + // Scope Template stores + CreateScopeTemplateStore, + UpdateScopeTemplateStore, + DeleteScopeTemplateStore, + CreateAreaTemplateStore, + UpdateAreaTemplateStore, + DeleteAreaTemplateStore, + CreateTaskTemplateStore, + UpdateTaskTemplateStore, + DeleteTaskTemplateStore, + CreateScopeTemplateFromJsonStore, + CreateScopeFromTemplateStore, + // Project Scope Template stores + CreateProjectScopeTemplateStore, + UpdateProjectScopeTemplateStore, + DeleteProjectScopeTemplateStore, + CreateProjectAreaTemplateStore, + UpdateProjectAreaTemplateStore, + DeleteProjectAreaTemplateStore, + CreateProjectTaskTemplateStore, + UpdateProjectTaskTemplateStore, + DeleteProjectTaskTemplateStore, + CreateProjectScopeTemplateFromJsonStore, + CreateProjectScopeFromTemplateStore, + // Service Scope stores (for active scopes on accounts) + CreateScopeStore, + UpdateScopeStore, + DeleteScopeStore, + CreateAreaStore, + UpdateAreaStore, + DeleteAreaStore, + CreateTaskStore, + UpdateTaskStore, + DeleteTaskStore, + // Project Scope stores (for active scopes on projects) + CreateProjectScopeStore, + UpdateProjectScopeStore, + DeleteProjectScopeStore, + CreateProjectScopeCategoryStore, + UpdateProjectScopeCategoryStore, + DeleteProjectScopeCategoryStore, + CreateProjectScopeTaskStore, + UpdateProjectScopeTaskStore, + DeleteProjectScopeTaskStore +} from '$houdini'; +import type { + // Scope Template types + CreateScopeTemplate$input, + CreateScopeTemplate$result, + UpdateScopeTemplate$input, + UpdateScopeTemplate$result, + CreateAreaTemplate$input, + CreateAreaTemplate$result, + UpdateAreaTemplate$input, + UpdateAreaTemplate$result, + CreateTaskTemplate$input, + CreateTaskTemplate$result, + UpdateTaskTemplate$input, + UpdateTaskTemplate$result, + CreateScopeTemplateFromJson$result, + CreateScopeFromTemplate$input, + CreateScopeFromTemplate$result, + // Project Scope Template types + CreateProjectScopeTemplate$input, + CreateProjectScopeTemplate$result, + UpdateProjectScopeTemplate$input, + UpdateProjectScopeTemplate$result, + CreateProjectAreaTemplate$input, + CreateProjectAreaTemplate$result, + UpdateProjectAreaTemplate$input, + UpdateProjectAreaTemplate$result, + CreateProjectTaskTemplate$input, + CreateProjectTaskTemplate$result, + UpdateProjectTaskTemplate$input, + UpdateProjectTaskTemplate$result, + CreateProjectScopeTemplateFromJson$result, + CreateProjectScopeFromTemplate$input, + CreateProjectScopeFromTemplate$result, + // Service Scope types (active scopes on accounts) + CreateScope$input, + CreateScope$result, + UpdateScope$input, + UpdateScope$result, + CreateArea$input, + CreateArea$result, + UpdateArea$input, + UpdateArea$result, + CreateTask$input, + CreateTask$result, + UpdateTask$input, + UpdateTask$result, + // Project Scope types (active scopes on projects) + CreateProjectScope$input, + CreateProjectScope$result, + UpdateProjectScope$input, + UpdateProjectScope$result, + CreateProjectScopeCategory$input, + CreateProjectScopeCategory$result, + UpdateProjectScopeCategory$input, + UpdateProjectScopeCategory$result, + CreateProjectScopeTask$input, + CreateProjectScopeTask$result, + UpdateProjectScopeTask$input, + UpdateProjectScopeTask$result +} from '$houdini'; + +// Re-export stores for server-side use +export { GetScopeTemplatesStore, GetProjectScopeTemplatesStore }; + +// ============================================================================ +// Service Scope Template Functions +// ============================================================================ + +export type ScopeTemplateInput = CreateScopeTemplate$input['input']; +export type ScopeTemplate = CreateScopeTemplate$result['createScopeTemplate']; + +export async function createScopeTemplate(input: ScopeTemplateInput): Promise { + const store = new CreateScopeTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create scope template'); + } + + const result = data?.createScopeTemplate; + if (!result) { + throw new Error('No scope template returned by the server'); + } + + return result; +} + +export type ScopeTemplateUpdateInput = UpdateScopeTemplate$input['input']; +export type UpdatedScopeTemplate = UpdateScopeTemplate$result['updateScopeTemplate']; + +export async function updateScopeTemplate( + input: ScopeTemplateUpdateInput +): Promise { + const store = new UpdateScopeTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update scope template'); + } + + const result = data?.updateScopeTemplate; + if (!result) { + throw new Error('No scope template returned by the server'); + } + + return result; +} + +export async function deleteScopeTemplate(id: string): Promise { + const store = new DeleteScopeTemplateStore(); + const { data, errors } = await store.mutate({ id }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to delete scope template'); + } + + if (!data?.deleteScopeTemplate) { + throw new Error('Failed to delete scope template'); + } +} + +// ============================================================================ +// Service Area Template Functions +// ============================================================================ + +export type AreaTemplateInput = CreateAreaTemplate$input['input']; +export type AreaTemplate = CreateAreaTemplate$result['createAreaTemplate']; + +export async function createAreaTemplate(input: AreaTemplateInput): Promise { + const store = new CreateAreaTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create area template'); + } + + const result = data?.createAreaTemplate; + if (!result) { + throw new Error('No area template returned by the server'); + } + + return result; +} + +export type AreaTemplateUpdateInput = UpdateAreaTemplate$input['input']; +export type UpdatedAreaTemplate = UpdateAreaTemplate$result['updateAreaTemplate']; + +export async function updateAreaTemplate( + input: AreaTemplateUpdateInput +): Promise { + const store = new UpdateAreaTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update area template'); + } + + const result = data?.updateAreaTemplate; + if (!result) { + throw new Error('No area template returned by the server'); + } + + return result; +} + +export async function deleteAreaTemplate(id: string): Promise { + const store = new DeleteAreaTemplateStore(); + const { data, errors } = await store.mutate({ id }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to delete area template'); + } + + if (!data?.deleteAreaTemplate) { + throw new Error('Failed to delete area template'); + } +} + +// ============================================================================ +// Service Task Template Functions +// ============================================================================ + +export type TaskTemplateInput = CreateTaskTemplate$input['input']; +export type TaskTemplate = CreateTaskTemplate$result['createTaskTemplate']; + +export async function createTaskTemplate(input: TaskTemplateInput): Promise { + const store = new CreateTaskTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create task template'); + } + + const result = data?.createTaskTemplate; + if (!result) { + throw new Error('No task template returned by the server'); + } + + return result; +} + +export type TaskTemplateUpdateInput = UpdateTaskTemplate$input['input']; +export type UpdatedTaskTemplate = UpdateTaskTemplate$result['updateTaskTemplate']; + +export async function updateTaskTemplate( + input: TaskTemplateUpdateInput +): Promise { + const store = new UpdateTaskTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update task template'); + } + + const result = data?.updateTaskTemplate; + if (!result) { + throw new Error('No task template returned by the server'); + } + + return result; +} + +export async function deleteTaskTemplate(id: string): Promise { + const store = new DeleteTaskTemplateStore(); + const { data, errors } = await store.mutate({ id }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to delete task template'); + } + + if (!data?.deleteTaskTemplate) { + throw new Error('Failed to delete task template'); + } +} + +// ============================================================================ +// Service Scope Template from JSON +// ============================================================================ + +export type ScopeTemplateFromJson = + CreateScopeTemplateFromJson$result['createScopeTemplateFromJson']; + +export async function createScopeTemplateFromJson( + payload: unknown, + replace: boolean = false +): Promise { + const store = new CreateScopeTemplateFromJsonStore(); + const { data, errors } = await store.mutate({ payload, replace }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create scope template from JSON'); + } + + const result = data?.createScopeTemplateFromJson; + if (!result) { + throw new Error('No scope template returned by the server'); + } + + return result; +} + +// ============================================================================ +// Project Scope Template Functions +// ============================================================================ + +export type ProjectScopeTemplateInput = CreateProjectScopeTemplate$input['input']; +export type ProjectScopeTemplate = CreateProjectScopeTemplate$result['createProjectScopeTemplate']; + +export async function createProjectScopeTemplate( + input: ProjectScopeTemplateInput +): Promise { + const store = new CreateProjectScopeTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create project scope template'); + } + + const result = data?.createProjectScopeTemplate; + if (!result) { + throw new Error('No project scope template returned by the server'); + } + + return result; +} + +export type ProjectScopeTemplateUpdateInput = UpdateProjectScopeTemplate$input['input']; +export type UpdatedProjectScopeTemplate = + UpdateProjectScopeTemplate$result['updateProjectScopeTemplate']; + +export async function updateProjectScopeTemplate( + input: ProjectScopeTemplateUpdateInput +): Promise { + const store = new UpdateProjectScopeTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update project scope template'); + } + + const result = data?.updateProjectScopeTemplate; + if (!result) { + throw new Error('No project scope template returned by the server'); + } + + return result; +} + +export async function deleteProjectScopeTemplate(id: string): Promise { + const store = new DeleteProjectScopeTemplateStore(); + const { data, errors } = await store.mutate({ id }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to delete project scope template'); + } + + if (!data?.deleteProjectScopeTemplate) { + throw new Error('Failed to delete project scope template'); + } +} + +// ============================================================================ +// Project Category (Area) Template Functions +// ============================================================================ + +export type ProjectAreaTemplateInput = CreateProjectAreaTemplate$input['input']; +export type ProjectAreaTemplate = CreateProjectAreaTemplate$result['createProjectAreaTemplate']; + +export async function createProjectAreaTemplate( + input: ProjectAreaTemplateInput +): Promise { + const store = new CreateProjectAreaTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create project category template'); + } + + const result = data?.createProjectAreaTemplate; + if (!result) { + throw new Error('No project category template returned by the server'); + } + + return result; +} + +export type ProjectAreaTemplateUpdateInput = UpdateProjectAreaTemplate$input['input']; +export type UpdatedProjectAreaTemplate = + UpdateProjectAreaTemplate$result['updateProjectAreaTemplate']; + +export async function updateProjectAreaTemplate( + input: ProjectAreaTemplateUpdateInput +): Promise { + const store = new UpdateProjectAreaTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update project category template'); + } + + const result = data?.updateProjectAreaTemplate; + if (!result) { + throw new Error('No project category template returned by the server'); + } + + return result; +} + +export async function deleteProjectAreaTemplate(id: string): Promise { + const store = new DeleteProjectAreaTemplateStore(); + const { data, errors } = await store.mutate({ id }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to delete project category template'); + } + + if (!data?.deleteProjectAreaTemplate) { + throw new Error('Failed to delete project category template'); + } +} + +// ============================================================================ +// Project Task Template Functions +// ============================================================================ + +export type ProjectTaskTemplateInput = CreateProjectTaskTemplate$input['input']; +export type ProjectTaskTemplate = CreateProjectTaskTemplate$result['createProjectTaskTemplate']; + +export async function createProjectTaskTemplate( + input: ProjectTaskTemplateInput +): Promise { + const store = new CreateProjectTaskTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create project task template'); + } + + const result = data?.createProjectTaskTemplate; + if (!result) { + throw new Error('No project task template returned by the server'); + } + + return result; +} + +export type ProjectTaskTemplateUpdateInput = UpdateProjectTaskTemplate$input['input']; +export type UpdatedProjectTaskTemplate = + UpdateProjectTaskTemplate$result['updateProjectTaskTemplate']; + +export async function updateProjectTaskTemplate( + input: ProjectTaskTemplateUpdateInput +): Promise { + const store = new UpdateProjectTaskTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update project task template'); + } + + const result = data?.updateProjectTaskTemplate; + if (!result) { + throw new Error('No project task template returned by the server'); + } + + return result; +} + +export async function deleteProjectTaskTemplate(id: string): Promise { + const store = new DeleteProjectTaskTemplateStore(); + const { data, errors } = await store.mutate({ id }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to delete project task template'); + } + + if (!data?.deleteProjectTaskTemplate) { + throw new Error('Failed to delete project task template'); + } +} + +// ============================================================================ +// Project Scope Template from JSON +// ============================================================================ + +export type ProjectScopeTemplateFromJson = + CreateProjectScopeTemplateFromJson$result['createProjectScopeTemplateFromJson']; + +export async function createProjectScopeTemplateFromJson( + payload: unknown, + replace: boolean = false +): Promise { + const store = new CreateProjectScopeTemplateFromJsonStore(); + const { data, errors } = await store.mutate({ payload, replace }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create project scope template from JSON'); + } + + const result = data?.createProjectScopeTemplateFromJson; + if (!result) { + throw new Error('No project scope template returned by the server'); + } + + return result; +} + +// ============================================================================ +// Create Scope from Template (Instantiation) +// ============================================================================ + +export type CreateScopeFromTemplateInput = CreateScopeFromTemplate$input['input']; +export type CreatedScopeFromTemplate = CreateScopeFromTemplate$result['createScopeFromTemplate']; + +export async function createScopeFromTemplate( + input: CreateScopeFromTemplateInput +): Promise { + const store = new CreateScopeFromTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create scope from template'); + } + + const result = data?.createScopeFromTemplate; + if (!result) { + throw new Error('No scope returned by the server'); + } + + return result; +} + +// ============================================================================ +// Create Project Scope from Template (Instantiation) +// ============================================================================ + +export type CreateProjectScopeFromTemplateInput = CreateProjectScopeFromTemplate$input['input']; +export type CreatedProjectScopeFromTemplate = + CreateProjectScopeFromTemplate$result['createProjectScopeFromTemplate']; + +export async function createProjectScopeFromTemplate( + input: CreateProjectScopeFromTemplateInput +): Promise { + const store = new CreateProjectScopeFromTemplateStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create project scope from template'); + } + + const result = data?.createProjectScopeFromTemplate; + if (!result) { + throw new Error('No project scope returned by the server'); + } + + return result; +} + +// ============================================================================ +// Service Scope Functions (Active Scopes on Accounts) +// ============================================================================ + +export type ServiceScopeInput = CreateScope$input['input']; +export type ServiceScope = CreateScope$result['createScope']; + +export async function createScope(input: ServiceScopeInput): Promise { + const store = new CreateScopeStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create scope'); + } + + const result = data?.createScope; + if (!result) { + throw new Error('No scope returned by the server'); + } + + return result; +} + +export type ServiceScopeUpdateInput = UpdateScope$input['input']; +export type UpdatedServiceScope = UpdateScope$result['updateScope']; + +export async function updateScope(input: ServiceScopeUpdateInput): Promise { + const store = new UpdateScopeStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update scope'); + } + + const result = data?.updateScope; + if (!result) { + throw new Error('No scope returned by the server'); + } + + return result; +} + +export async function deleteScope(id: string): Promise { + const store = new DeleteScopeStore(); + const { data, errors } = await store.mutate({ id }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to delete scope'); + } + + if (!data?.deleteScope) { + throw new Error('Failed to delete scope'); + } +} + +// ============================================================================ +// Service Area Functions (Areas within Active Scopes) +// ============================================================================ + +export type ServiceAreaInput = CreateArea$input['input']; +export type ServiceArea = CreateArea$result['createArea']; + +export async function createArea(input: ServiceAreaInput): Promise { + const store = new CreateAreaStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create area'); + } + + const result = data?.createArea; + if (!result) { + throw new Error('No area returned by the server'); + } + + return result; +} + +export type ServiceAreaUpdateInput = UpdateArea$input['input']; +export type UpdatedServiceArea = UpdateArea$result['updateArea']; + +export async function updateArea(input: ServiceAreaUpdateInput): Promise { + const store = new UpdateAreaStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update area'); + } + + const result = data?.updateArea; + if (!result) { + throw new Error('No area returned by the server'); + } + + return result; +} + +export async function deleteArea(id: string): Promise { + const store = new DeleteAreaStore(); + const { data, errors } = await store.mutate({ id }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to delete area'); + } + + if (!data?.deleteArea) { + throw new Error('Failed to delete area'); + } +} + +// ============================================================================ +// Service Task Functions (Tasks within Active Scopes) +// ============================================================================ + +export type ServiceTaskInput = CreateTask$input['input']; +export type ServiceTask = CreateTask$result['createTask']; + +export async function createTask(input: ServiceTaskInput): Promise { + const store = new CreateTaskStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create task'); + } + + const result = data?.createTask; + if (!result) { + throw new Error('No task returned by the server'); + } + + return result; +} + +export type ServiceTaskUpdateInput = UpdateTask$input['input']; +export type UpdatedServiceTask = UpdateTask$result['updateTask']; + +export async function updateTask(input: ServiceTaskUpdateInput): Promise { + const store = new UpdateTaskStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update task'); + } + + const result = data?.updateTask; + if (!result) { + throw new Error('No task returned by the server'); + } + + return result; +} + +export async function deleteTask(id: string): Promise { + const store = new DeleteTaskStore(); + const { data, errors } = await store.mutate({ id }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to delete task'); + } + + if (!data?.deleteTask) { + throw new Error('Failed to delete task'); + } +} + +// ============================================================================ +// Project Scope Functions (Active Scopes on Projects) +// ============================================================================ + +export type ProjectScopeInput = CreateProjectScope$input['input']; +export type ProjectScope = CreateProjectScope$result['createProjectScope']; + +export async function createProjectScope(input: ProjectScopeInput): Promise { + const store = new CreateProjectScopeStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create project scope'); + } + + const result = data?.createProjectScope; + if (!result) { + throw new Error('No project scope returned by the server'); + } + + return result; +} + +export type ProjectScopeUpdateInput = UpdateProjectScope$input['input']; +export type UpdatedProjectScope = UpdateProjectScope$result['updateProjectScope']; + +export async function updateProjectScope( + input: ProjectScopeUpdateInput +): Promise { + const store = new UpdateProjectScopeStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update project scope'); + } + + const result = data?.updateProjectScope; + if (!result) { + throw new Error('No project scope returned by the server'); + } + + return result; +} + +export async function deleteProjectScope(id: string): Promise { + const store = new DeleteProjectScopeStore(); + const { data, errors } = await store.mutate({ id }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to delete project scope'); + } + + if (!data?.deleteProjectScope) { + throw new Error('Failed to delete project scope'); + } +} + +// ============================================================================ +// Project Scope Category Functions (Categories within Active Project Scopes) +// ============================================================================ + +export type ProjectScopeCategoryInput = CreateProjectScopeCategory$input['input']; +export type ProjectScopeCategory = CreateProjectScopeCategory$result['createProjectScopeCategory']; + +export async function createProjectScopeCategory( + input: ProjectScopeCategoryInput +): Promise { + const store = new CreateProjectScopeCategoryStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create project scope category'); + } + + const result = data?.createProjectScopeCategory; + if (!result) { + throw new Error('No project scope category returned by the server'); + } + + return result; +} + +export type ProjectScopeCategoryUpdateInput = UpdateProjectScopeCategory$input['input']; +export type UpdatedProjectScopeCategory = + UpdateProjectScopeCategory$result['updateProjectScopeCategory']; + +export async function updateProjectScopeCategory( + input: ProjectScopeCategoryUpdateInput +): Promise { + const store = new UpdateProjectScopeCategoryStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update project scope category'); + } + + const result = data?.updateProjectScopeCategory; + if (!result) { + throw new Error('No project scope category returned by the server'); + } + + return result; +} + +export async function deleteProjectScopeCategory(id: string): Promise { + const store = new DeleteProjectScopeCategoryStore(); + const { data, errors } = await store.mutate({ id }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to delete project scope category'); + } + + if (!data?.deleteProjectScopeCategory) { + throw new Error('Failed to delete project scope category'); + } +} + +// ============================================================================ +// Project Scope Task Functions (Tasks within Active Project Scopes) +// ============================================================================ + +export type ProjectScopeTaskInput = CreateProjectScopeTask$input['input']; +export type ProjectScopeTask = CreateProjectScopeTask$result['createProjectScopeTask']; + +export async function createProjectScopeTask( + input: ProjectScopeTaskInput +): Promise { + const store = new CreateProjectScopeTaskStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create project scope task'); + } + + const result = data?.createProjectScopeTask; + if (!result) { + throw new Error('No project scope task returned by the server'); + } + + return result; +} + +export type ProjectScopeTaskUpdateInput = UpdateProjectScopeTask$input['input']; +export type UpdatedProjectScopeTask = UpdateProjectScopeTask$result['updateProjectScopeTask']; + +export async function updateProjectScopeTask( + input: ProjectScopeTaskUpdateInput +): Promise { + const store = new UpdateProjectScopeTaskStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update project scope task'); + } + + const result = data?.updateProjectScopeTask; + if (!result) { + throw new Error('No project scope task returned by the server'); + } + + return result; +} + +export async function deleteProjectScopeTask(id: string): Promise { + const store = new DeleteProjectScopeTaskStore(); + const { data, errors } = await store.mutate({ id }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to delete project scope task'); + } + + if (!data?.deleteProjectScopeTask) { + throw new Error('Failed to delete project scope task'); + } +} diff --git a/src/lib/utils/searchConfig.ts b/src/lib/utils/searchConfig.ts new file mode 100644 index 0000000..9d5b3cd --- /dev/null +++ b/src/lib/utils/searchConfig.ts @@ -0,0 +1,291 @@ +export interface SearchPage { + label: string; + href: string; + keywords?: string[]; + icon?: + | 'home' + | 'page' + | 'account' + | 'service' + | 'project' + | 'report' + | 'invoice' + | 'customer' + | 'calendar' + | 'profile' + | 'notification' + | 'scope' + | 'settings' + | 'message'; +} + +export interface SearchEntity { + type: 'account' | 'customer' | 'service' | 'project' | 'report' | 'invoice' | 'scope'; + id: string; + label: string; + sublabel?: string; + href: string; +} + +export type SearchResult = SearchPage | SearchEntity; + +// Public pages - available to everyone +export const publicPages: SearchPage[] = [ + { label: 'Home', href: '/', keywords: ['home', 'main', 'landing'], icon: 'home' }, + { + label: 'Services', + href: '/services', + keywords: ['services', 'cleaning', 'janitorial'], + icon: 'page' + }, + { + label: 'Pricing', + href: '/pricing', + keywords: ['pricing', 'cost', 'rates', 'quote'], + icon: 'page' + }, + { label: 'About Us', href: '/about', keywords: ['about', 'company', 'story'], icon: 'page' }, + { + label: 'Contact', + href: '/contact', + keywords: ['contact', 'support', 'help', 'phone', 'email'], + icon: 'page' + }, + { + label: 'Our Standard', + href: '/standard', + keywords: ['standard', 'quality', 'guarantee'], + icon: 'page' + }, + { + label: 'Privacy Policy', + href: '/privacy', + keywords: ['privacy', 'policy', 'data'], + icon: 'page' + }, + { label: 'Terms of Use', href: '/terms', keywords: ['terms', 'use', 'legal'], icon: 'page' } +]; + +// Customer pages - available to CustomerProfileType users +export const customerPages: SearchPage[] = [ + { + label: 'Customer Dashboard', + href: '/customer', + keywords: ['dashboard', 'home', 'overview'], + icon: 'home' + }, + { + label: 'My Accounts', + href: '/customer/accounts', + keywords: ['accounts', 'locations', 'sites'], + icon: 'account' + }, + { + label: 'Schedule', + href: '/customer/schedule', + keywords: ['schedule', 'upcoming', 'calendar', 'appointments'], + icon: 'calendar' + }, + { + label: 'History', + href: '/customer/history', + keywords: ['history', 'past', 'completed', 'previous'], + icon: 'page' + }, + { + label: 'Invoices', + href: '/customer/invoices', + keywords: ['invoices', 'billing', 'payment', 'pay'], + icon: 'invoice' + }, + { + label: 'My Profile', + href: '/customer/profile', + keywords: ['profile', 'settings', 'account'], + icon: 'profile' + } +]; + +// Team pages - available to TeamProfileType users (any role) +export const teamPages: SearchPage[] = [ + { + label: 'Team Dashboard', + href: '/team', + keywords: ['dashboard', 'home', 'overview'], + icon: 'home' + }, + { + label: 'My Accounts', + href: '/team/accounts', + keywords: ['accounts', 'assigned', 'locations'], + icon: 'account' + }, + { + label: 'My Services', + href: '/team/services', + keywords: ['services', 'scheduled', 'work'], + icon: 'service' + }, + { + label: 'My Projects', + href: '/team/projects', + keywords: ['projects', 'assigned', 'jobs'], + icon: 'project' + }, + { + label: 'My Reports', + href: '/team/reports', + keywords: ['reports', 'submitted', 'documents'], + icon: 'report' + }, + { + label: 'My Profile', + href: '/team/profile', + keywords: ['profile', 'settings', 'account'], + icon: 'profile' + } +]; + +// Admin pages - available to ADMIN or TEAM_LEADER roles +export const adminPages: SearchPage[] = [ + { + label: 'Admin Dashboard', + href: '/admin', + keywords: ['dashboard', 'admin', 'overview'], + icon: 'home' + }, + { + label: 'Customers', + href: '/admin/customers', + keywords: ['customers', 'clients', 'companies'], + icon: 'customer' + }, + { + label: 'Accounts', + href: '/admin/accounts', + keywords: ['accounts', 'locations', 'sites'], + icon: 'account' + }, + { + label: 'Services', + href: '/admin/services', + keywords: ['services', 'scheduled', 'routine'], + icon: 'service' + }, + { + label: 'Assign Services', + href: '/admin/services/assign', + keywords: ['assign', 'services', 'team'], + icon: 'service' + }, + { + label: 'Projects', + href: '/admin/projects', + keywords: ['projects', 'jobs', 'one-time'], + icon: 'project' + }, + { + label: 'Scopes', + href: '/admin/scopes', + keywords: ['scopes', 'templates', 'tasks'], + icon: 'scope' + }, + { + label: 'Reports', + href: '/admin/reports', + keywords: ['reports', 'documents', 'submissions'], + icon: 'report' + }, + { + label: 'Invoices', + href: '/admin/invoices', + keywords: ['invoices', 'billing', 'payments'], + icon: 'invoice' + }, + { + label: 'Wave Invoices', + href: '/admin/invoices/wave', + keywords: ['wave', 'invoices', 'accounting'], + icon: 'invoice' + }, + { + label: 'Calendar', + href: '/admin/calendar', + keywords: ['calendar', 'schedule', 'events'], + icon: 'calendar' + }, + { + label: 'Profiles', + href: '/admin/profiles', + keywords: ['profiles', 'team', 'users', 'staff'], + icon: 'profile' + } +]; + +// Admin-only pages - available only to ADMIN role +export const adminOnlyPages: SearchPage[] = [ + { + label: 'Notification Rules', + href: '/admin/notifications', + keywords: ['notifications', 'alerts', 'rules'], + icon: 'notification' + }, + { + label: 'Event Log', + href: '/admin/events', + keywords: ['events', 'log', 'audit', 'history'], + icon: 'page' + } +]; + +// Shared pages - available to all authenticated users +export const sharedPages: SearchPage[] = [ + { + label: 'Messages', + href: '/messages', + keywords: ['messages', 'inbox', 'chat', 'conversations'], + icon: 'message' + }, + { + label: 'Notifications', + href: '/notifications', + keywords: ['notifications', 'alerts', 'updates'], + icon: 'notification' + } +]; + +// Color scheme mapping for entity types +export const entityColorSchemes: Record = { + account: 'primary', + service: 'secondary', + project: 'accent', + report: 'accent2', + customer: 'accent3', + profile: 'accent4', + invoice: 'accent6', + calendar: 'accent7', + scope: 'accent' +}; + +// Section header class mapping +export const sectionHeaderClasses: Record = { + pages: 'bg-surface-100 dark:bg-surface-800', + account: 'section-header-primary', + service: 'section-header-secondary', + project: 'section-header-accent', + report: 'section-header-accent2', + customer: 'section-header-accent3', + invoice: 'section-header-accent6', + scope: 'section-header-accent' +}; + +// Badge class mapping +export const badgeClasses: Record = { + account: 'badge-primary', + service: 'badge-secondary', + project: 'badge-accent', + report: 'badge-accent2', + customer: 'badge-accent3', + invoice: 'badge-accent6', + scope: 'badge-accent' +}; diff --git a/src/lib/utils/session.ts b/src/lib/utils/session.ts new file mode 100644 index 0000000..ecca35e --- /dev/null +++ b/src/lib/utils/session.ts @@ -0,0 +1,611 @@ +import { + OpenServiceSessionStore, + CloseServiceSessionStore, + RevertServiceSessionStore, + OpenProjectSessionStore, + CloseProjectSessionStore, + RevertProjectSessionStore, + AddServiceTaskStore, + RemoveServiceTaskStore, + AddProjectTaskStore, + RemoveProjectTaskStore, + CreateServiceNoteStore, + UpdateServiceNoteStore, + RemoveServiceNoteStore, + CreateProjectNoteStore, + UpdateProjectNoteStore, + RemoveProjectNoteStore, + UpdateServicePhotoStore, + RemoveServicePhotoStore, + UpdateProjectPhotoStore, + RemoveProjectPhotoStore, + UpdateServiceVideoStore, + RemoveServiceVideoStore, + UpdateProjectVideoStore, + RemoveProjectVideoStore +} from '$houdini'; +import type { + OpenServiceSession$input, + OpenServiceSession$result, + CloseServiceSession$input, + CloseServiceSession$result, + OpenProjectSession$input, + OpenProjectSession$result, + CloseProjectSession$input, + CloseProjectSession$result, + AddServiceTask$result, + RemoveServiceTask$result, + AddProjectTask$result, + RemoveProjectTask$result, + CreateServiceNote$result, + UpdateServiceNote$result, + CreateProjectNote$result, + UpdateProjectNote$result, + ServiceSessionNoteInput, + ServiceSessionNoteUpdateInput, + ProjectSessionNoteInput, + ProjectSessionNoteUpdateInput, + ServiceSessionImageUpdateInput, + ProjectSessionImageUpdateInput, + ServiceSessionVideoUpdateInput, + ProjectSessionVideoUpdateInput, + UpdateServicePhoto$result, + UpdateProjectPhoto$result, + UpdateServiceVideo$result, + UpdateProjectVideo$result +} from '$houdini'; + +const API_BASE_URL = import.meta.env.VITE_API_URL || 'https://api.example.com'; + +// Upload response types +export interface UploadPhotoResponse { + id: string; + title: string; + notes: string; + contentType: string; + width: number; + height: number; + image: string; + thumbnail: string; + createdAt: string; + uploadedById: string | null; + internal: boolean; + serviceSessionId?: string; + projectSessionId?: string; +} + +export interface UploadVideoResponse { + id: string; + title: string; + notes: string; + contentType: string; + width: number; + height: number; + durationSeconds: number; + fileSizeBytes: number; + video: string; + thumbnail: string; + createdAt: string; + uploadedById: string | null; + internal: boolean; + serviceSessionId?: string; + projectSessionId?: string; +} + +// Service Session functions +export type OpenServiceSessionInput = OpenServiceSession$input['input']; +export type ServiceSession = OpenServiceSession$result['openServiceSession']; + +export async function openServiceSession(input: OpenServiceSessionInput): Promise { + const store = new OpenServiceSessionStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to open service session'); + } + + const session = data?.openServiceSession; + if (!session) { + throw new Error('No service session returned by the server'); + } + + return session; +} + +export type CloseServiceSessionInput = CloseServiceSession$input['input']; +export type ClosedServiceSession = CloseServiceSession$result['closeServiceSession']; + +export async function closeServiceSession( + input: CloseServiceSessionInput +): Promise { + const store = new CloseServiceSessionStore(); + const { data, errors } = await store.mutate({ input }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to close service session'); + } + + const session = data?.closeServiceSession; + if (!session) { + throw new Error('No service session returned by the server'); + } + + return session; +} + +export async function revertServiceSession(serviceId: string): Promise { + const store = new RevertServiceSessionStore(); + const { data, errors } = await store.mutate({ input: { serviceId } }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to revert service session'); + } + + return data?.revertServiceSession ?? false; +} + +// Project Session functions +export type OpenProjectSessionInput = OpenProjectSession$input['input']; +export type ProjectSession = OpenProjectSession$result['openProjectSession']; + +export async function openProjectSession(input: OpenProjectSessionInput): Promise { + const store = new OpenProjectSessionStore(); + const { data, errors } = await store.mutate({ input }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to open project session'); + } + const session = data?.openProjectSession; + if (!session) { + throw new Error('No project session returned by the server'); + } + return session; +} + +export type CloseProjectSessionInput = CloseProjectSession$input['input']; +export type ClosedProjectSession = CloseProjectSession$result['closeProjectSession']; + +export async function closeProjectSession( + input: CloseProjectSessionInput +): Promise { + const store = new CloseProjectSessionStore(); + const { data, errors } = await store.mutate({ input }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to close project session'); + } + const session = data?.closeProjectSession; + if (!session) { + throw new Error('No project session returned by the server'); + } + return session; +} + +export async function revertProjectSession(projectId: string): Promise { + const store = new RevertProjectSessionStore(); + const { data, errors } = await store.mutate({ input: { projectId } }); + + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to revert project session'); + } + + return data?.revertProjectSession ?? false; +} + +// Service Task functions +export type AddServiceTaskResult = AddServiceTask$result['addTaskCompletion']; + +export async function addServiceTask( + serviceId: string, + taskId: string, + notes?: string +): Promise { + const store = new AddServiceTaskStore(); + const { data, errors } = await store.mutate({ serviceId, taskId, notes }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to add service task'); + } + const session = data?.addTaskCompletion; + if (!session) { + throw new Error('No session returned by the server'); + } + return session; +} + +export type RemoveServiceTaskResult = RemoveServiceTask$result['removeTaskCompletion']; + +export async function removeServiceTask( + serviceId: string, + taskId: string +): Promise { + const store = new RemoveServiceTaskStore(); + const { data, errors } = await store.mutate({ serviceId, taskId }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to remove service task'); + } + const session = data?.removeTaskCompletion; + if (!session) { + throw new Error('No session returned by the server'); + } + return session; +} + +// Project Task functions +export type AddProjectTaskResult = AddProjectTask$result['addProjectTaskCompletion']; + +export async function addProjectTask( + projectId: string, + taskId: string, + notes?: string +): Promise { + const store = new AddProjectTaskStore(); + const { data, errors } = await store.mutate({ projectId, taskId, notes }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to add project task'); + } + const session = data?.addProjectTaskCompletion; + if (!session) { + throw new Error('No session returned by the server'); + } + return session; +} + +export type RemoveProjectTaskResult = RemoveProjectTask$result['removeProjectTaskCompletion']; + +export async function removeProjectTask( + projectId: string, + taskId: string +): Promise { + const store = new RemoveProjectTaskStore(); + const { data, errors } = await store.mutate({ projectId, taskId }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to remove project task'); + } + const session = data?.removeProjectTaskCompletion; + if (!session) { + throw new Error('No session returned by the server'); + } + return session; +} + +// Service Note functions +export type CreateServiceNoteResult = CreateServiceNote$result['createServiceSessionNote']; + +export async function createServiceNote( + input: ServiceSessionNoteInput +): Promise { + const store = new CreateServiceNoteStore(); + const { data, errors } = await store.mutate({ input }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create service note'); + } + const note = data?.createServiceSessionNote; + if (!note) { + throw new Error('No note returned by the server'); + } + return note; +} + +export type UpdateServiceNoteResult = UpdateServiceNote$result['updateServiceSessionNote']; + +export async function updateServiceNote( + input: ServiceSessionNoteUpdateInput +): Promise { + const store = new UpdateServiceNoteStore(); + const { data, errors } = await store.mutate({ input }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update service note'); + } + const note = data?.updateServiceSessionNote; + if (!note) { + throw new Error('No note returned by the server'); + } + return note; +} + +export async function removeServiceNote(id: string): Promise { + const store = new RemoveServiceNoteStore(); + const { data, errors } = await store.mutate({ id }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to remove service note'); + } + if (!data?.deleteServiceSessionNote) { + throw new Error('Failed to delete service note'); + } +} + +// Project Note functions +export type CreateProjectNoteResult = CreateProjectNote$result['createProjectSessionNote']; + +export async function createProjectNote( + input: ProjectSessionNoteInput +): Promise { + const store = new CreateProjectNoteStore(); + const { data, errors } = await store.mutate({ input }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to create project note'); + } + const note = data?.createProjectSessionNote; + if (!note) { + throw new Error('No note returned by the server'); + } + return note; +} + +export type UpdateProjectNoteResult = UpdateProjectNote$result['updateProjectSessionNote']; + +export async function updateProjectNote( + input: ProjectSessionNoteUpdateInput +): Promise { + const store = new UpdateProjectNoteStore(); + const { data, errors } = await store.mutate({ input }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update project note'); + } + const note = data?.updateProjectSessionNote; + if (!note) { + throw new Error('No note returned by the server'); + } + return note; +} + +export async function removeProjectNote(id: string): Promise { + const store = new RemoveProjectNoteStore(); + const { data, errors } = await store.mutate({ id }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to remove project note'); + } + if (!data?.deleteProjectSessionNote) { + throw new Error('Failed to delete project note'); + } +} + +// Service Photo functions +export async function uploadServicePhoto( + file: File, + sessionId: string, + title?: string, + notes?: string, + internal: boolean = true +): Promise { + const formData = new FormData(); + formData.append('file', file); + formData.append('sessionId', sessionId); + formData.append('title', title || file.name); + formData.append('notes', notes || ''); + formData.append('internal', String(internal)); + + const response = await fetch(`${API_BASE_URL}/v1/api/upload/photo/service/`, { + method: 'POST', + credentials: 'include', + body: formData + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ detail: '' })); + throw new Error(errorData.detail || `Upload failed: ${response.status} ${response.statusText}`); + } + + return response.json(); +} + +export type UpdateServicePhotoResult = UpdateServicePhoto$result['updateServiceSessionImage']; + +export async function updateServicePhoto( + input: ServiceSessionImageUpdateInput +): Promise { + const store = new UpdateServicePhotoStore(); + const { data, errors } = await store.mutate({ input }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update service photo'); + } + const photo = data?.updateServiceSessionImage; + if (!photo) { + throw new Error('No photo returned by the server'); + } + return photo; +} + +export async function removeServicePhoto(id: string): Promise { + const store = new RemoveServicePhotoStore(); + const { data, errors } = await store.mutate({ id }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to remove service photo'); + } + if (!data?.deleteServiceSessionImage) { + throw new Error('Failed to delete service photo'); + } +} + +// Project Photo functions +export async function uploadProjectPhoto( + file: File, + sessionId: string, + title?: string, + notes?: string, + internal: boolean = true +): Promise { + const formData = new FormData(); + formData.append('file', file); + formData.append('sessionId', sessionId); + formData.append('title', title || file.name); + formData.append('notes', notes || ''); + formData.append('internal', String(internal)); + + const response = await fetch(`${API_BASE_URL}/v1/api/upload/photo/project/`, { + method: 'POST', + credentials: 'include', + body: formData + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ detail: '' })); + throw new Error(errorData.detail || `Upload failed: ${response.status} ${response.statusText}`); + } + + return response.json(); +} + +export type UpdateProjectPhotoResult = UpdateProjectPhoto$result['updateProjectSessionImage']; + +export async function updateProjectPhoto( + input: ProjectSessionImageUpdateInput +): Promise { + const store = new UpdateProjectPhotoStore(); + const { data, errors } = await store.mutate({ input }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update project photo'); + } + const photo = data?.updateProjectSessionImage; + if (!photo) { + throw new Error('No photo returned by the server'); + } + return photo; +} + +export async function removeProjectPhoto(id: string): Promise { + const store = new RemoveProjectPhotoStore(); + const { data, errors } = await store.mutate({ id }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to remove project photo'); + } + if (!data?.deleteProjectSessionImage) { + throw new Error('Failed to delete project photo'); + } +} + +// Service Video functions +export async function uploadServiceVideo( + file: File, + sessionId: string, + title?: string, + notes?: string, + internal: boolean = true +): Promise { + const formData = new FormData(); + formData.append('file', file); + formData.append('sessionId', sessionId); + formData.append('title', title || file.name); + formData.append('notes', notes || ''); + formData.append('internal', String(internal)); + + const response = await fetch(`${API_BASE_URL}/v1/api/upload/video/service/`, { + method: 'POST', + credentials: 'include', + body: formData + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ detail: '' })); + throw new Error(errorData.detail || `Upload failed: ${response.status} ${response.statusText}`); + } + + return response.json(); +} + +export type UpdateServiceVideoResult = UpdateServiceVideo$result['updateServiceSessionVideo']; + +export async function updateServiceVideo( + input: ServiceSessionVideoUpdateInput +): Promise { + const store = new UpdateServiceVideoStore(); + const { data, errors } = await store.mutate({ input }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update service video'); + } + const video = data?.updateServiceSessionVideo; + if (!video) { + throw new Error('No video returned by the server'); + } + return video; +} + +export async function removeServiceVideo(id: string): Promise { + const store = new RemoveServiceVideoStore(); + const { data, errors } = await store.mutate({ id }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to remove service video'); + } + if (!data?.deleteServiceSessionVideo) { + throw new Error('Failed to delete service video'); + } +} + +// Project Video functions +export async function uploadProjectVideo( + file: File, + sessionId: string, + title?: string, + notes?: string, + internal: boolean = true +): Promise { + const formData = new FormData(); + formData.append('file', file); + formData.append('sessionId', sessionId); + formData.append('title', title || file.name); + formData.append('notes', notes || ''); + formData.append('internal', String(internal)); + + const response = await fetch(`${API_BASE_URL}/v1/api/upload/video/project/`, { + method: 'POST', + credentials: 'include', + body: formData + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ detail: '' })); + throw new Error(errorData.detail || `Upload failed: ${response.status} ${response.statusText}`); + } + + return response.json(); +} + +export type UpdateProjectVideoResult = UpdateProjectVideo$result['updateProjectSessionVideo']; + +export async function updateProjectVideo( + input: ProjectSessionVideoUpdateInput +): Promise { + const store = new UpdateProjectVideoStore(); + const { data, errors } = await store.mutate({ input }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to update project video'); + } + const video = data?.updateProjectSessionVideo; + if (!video) { + throw new Error('No video returned by the server'); + } + return video; +} + +export async function removeProjectVideo(id: string): Promise { + const store = new RemoveProjectVideoStore(); + const { data, errors } = await store.mutate({ id }); + if (errors?.length) { + const message = errors.map((e) => e.message || 'Unknown error').join('; '); + throw new Error(message || 'Failed to remove project video'); + } + if (!data?.deleteProjectSessionVideo) { + throw new Error('Failed to delete project video'); + } +} diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte new file mode 100644 index 0000000..35c489a --- /dev/null +++ b/src/routes/+error.svelte @@ -0,0 +1,97 @@ + + +{#snippet shieldLock()} + + + + + + +{/snippet} + +{#snippet magnifyingGlass()} + + + + + + + +{/snippet} + +{#snippet warningTriangle()} + + + + + + +{/snippet} + + + {title} - Nexus + + +
+
+
+ {#if page.status === 403 || page.status === 401} + {@render shieldLock()} + {:else if page.status === 404} + {@render magnifyingGlass()} + {:else} + {@render warningTriangle()} + {/if} +
+
{page.status}
+

{title}

+ {#if page.error?.message} +

{page.error.message}

+ {/if} +
+ + +
+
+
diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts new file mode 100644 index 0000000..b06cb29 --- /dev/null +++ b/src/routes/+layout.server.ts @@ -0,0 +1,44 @@ +import { + MeStore as User, + TeamStore as Team, + CustomersStore as Customers, + AccountsBasicStore as Accounts +} from '$houdini'; +import type { LayoutServerLoad } from './$types'; +import { + buildAccountAddressLookup, + buildCustomerLookup, + buildNonAdminTeamIds, + buildTeamMemberLookup +} from '$lib/utils/lookup'; + +export const load: LayoutServerLoad = async (event) => { + const fetchParams = { + event, + metadata: { cookie: event.locals.cookie } + }; + + const [user, team, customers, accounts] = await Promise.all([ + new User().fetch(fetchParams), + new Team().fetch(fetchParams), + new Customers().fetch(fetchParams), + new Accounts().fetch(fetchParams) + ]); + + // Build lookups once at the root level + const accountLookup = buildAccountAddressLookup(accounts?.data?.accounts); + const customerLookup = buildCustomerLookup(customers?.data?.customers); + const nonAdminTeamIds = buildNonAdminTeamIds(team?.data?.teamProfiles); + const teamMemberLookup = buildTeamMemberLookup(team?.data?.teamProfiles); + + return { + user, + team, + customers, + accounts, + accountLookup, + customerLookup, + nonAdminTeamIds, + teamMemberLookup + }; +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..19685bf --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,110 @@ + + + + + {isPublicRoute ? 'Nexus Cleaning Solutions | Nexus v5.0' : 'Nexus v5.0'} + + +
+ + + +
+ {@render children()} +
+ + {#if isPublicRoute} + + {:else if isTeamRoute} + + {:else if isAdminRoute} + + {:else if isCustomerRoute} + + {/if} + + + + + {#if isAuthenticated && !isPublicRoute} + + + {/if} +
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..80c6393 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,406 @@ + + + + Nexus Cleaning Solutions - Commercial Cleaning Services + + + + +
+ +
+ Professional cleaning service + +
+
+ + + +
+

+ We Don't Just Clean. + We Deliver Peace of Mind. +

+

+ Nexus Cleaning Solutions provides exceptional commercial cleaning and floor care + services, built on 5 years of experience and specialized expertise. Outstanding workmanship + and complete client satisfaction, every single time. +

+ +
+
+
+ + +
+ +
+

Comprehensive Cleaning Solutions

+

+ From daily janitorial services to specialized deep cleaning, we keep your facility spotless + so you can focus on your business. +

+
+ +
+ +
+
+ + + +
+

Janitorial Service

+

+ Scheduled cleaning services tailored to your facility. Custom scope of work with task + frequencies that match your needs: daily, weekly, or monthly. +

+
+ + +
+
+ + + +
+

Floor Care

+

+ Professional floor maintenance including stripping, waxing, buffing, and deep cleaning. + Keep your floors looking their best year-round. +

+
+ + +
+
+ + + +
+

Commercial Kitchen

+

+ Comprehensive kitchen cleaning covering equipment, walls, ceiling tiles, and floors. Keep + your commercial kitchen spotless and sanitary. +

+
+
+ + +
+
+ + +
+ +
+

The Nexus Difference

+

+ From your first call to the final walkthrough, you'll experience clear, professional + communication and a team that truly listens. Big or small, every project benefits from our + unwavering commitment to safety, reliability, and top-tier quality. +

+
+ +
+ +
+
+ + + +
+

No Contracts

+

+ One consultation, then book as needed. Our Nexus Standard guarantees quality at a + transparent price. +

+
+ + +
+
+ + + +
+

Safe & Effective

+

+ We carefully select our products for a spotless shine that's gentle on your workplace and + your people. +

+
+ + +
+
+ + + +
+

Easy Scheduling

+

+ When cleaning falls off your to-do list, we step in. Book online or call for fast, + reliable service. +

+
+ + +
+
+ + + +
+

Transparent Pricing

+

+ No hidden fees, no surprises. You'll know exactly what you're paying for before we start. +

+
+
+
+
+ + +
+ +
+
+ Professional cleaning team at work +
+
+

Our Commitment to You

+

+ We understand that inviting a cleaning team into your business requires trust. That's why + we're committed to earning it every single day. +

+
    +
  • + + + +
    + Trained & Insured Team +

    + Our cleaning professionals are thoroughly trained, background-checked, and fully + insured. +

    +
    +
  • +
  • + + + +
    + Customized Cleaning Plans +

    + Every facility is different. We build a scope of work around your specific needs and + schedule. +

    +
    +
  • +
  • + + + +
    + Customer Portal Access +

    + Track services, view reports, and communicate with our team through your online + account. +

    +
    +
  • +
  • + + + +
    + The Nexus Standard +

    + Our quality guarantee means if you're not satisfied, we'll make it right. +

    +
    +
  • +
+
+
+
+
+ + +
+ +
+

Ready for a cleaner facility?

+

+ Serving businesses throughout Macomb County, Oakland County, and Metro Detroit. Get in touch + for a free consultation. We'll walk through your space, understand your needs, and build a + cleaning plan that works for you. +

+ + Get a Free Quote + +
+
+
diff --git a/src/routes/about/+page.svelte b/src/routes/about/+page.svelte new file mode 100644 index 0000000..0f9acc5 --- /dev/null +++ b/src/routes/about/+page.svelte @@ -0,0 +1,228 @@ + + + + About Us - Nexus + + + + +
+ + +
+

About Nexus

+

+ Nexus Cleaning Solutions is a Macomb County-based commercial cleaning company dedicated + to providing dependable janitorial services, floor care, and specialty cleaning for + businesses throughout Southeast Michigan. +

+
+
+
+ + +
+ +
+
+

Our Story

+
+

+ Nexus Cleaning Solutions was founded on a simple principle: businesses deserve a + cleaning partner they can rely on. We've built our reputation on showing up when we say + we will and delivering consistent, quality results. +

+

+ We understand that a clean facility is more than just appearances. It's about creating a + healthy, productive environment for your employees and a welcoming space for your + customers. +

+

+ Today, we're proud to serve a growing roster of commercial clients across Macomb County, + Oakland County, and Metro Detroit. From office buildings and medical facilities to + restaurants and industrial spaces. +

+
+
+
+ Professional cleaning team +
+
+
+
+ + +
+ +
+

Why We Do This

+
+

+ Commercial cleaning has a reputation problem. Too often, it's treated as an afterthought, + something to get done as cheaply as possible, by whoever will do it for less. The result? + A race to the bottom that hurts everyone: businesses get unreliable service, and the + people doing the work aren't valued for what they bring. +

+

+ We started Nexus because we believe cleaning deserves better. It's skilled work. It + keeps people healthy. It makes businesses function. And the people who do it well deserve + to be paid fairly and treated with respect. +

+

+ That's why we're committed to elevating the standard, not just in how we clean, but in how + we operate. We pay our team what the work is worth. We invest in proper training and + equipment. We show up when we say we will. And we stand behind every job with a guarantee that means something. +

+

+ This isn't about being the cheapest option. It's about being the partner you can actually + count on. +

+
+
+
+
+ + +
+ +
+

Our Values

+

+ These principles guide everything we do at Nexus. +

+
+ +
+
+
+ + + +
+

Dependability

+

+ We show up on time, every time. You can count on us. +

+
+ +
+
+ + + +
+

Quality

+

We take pride in our work and never cut corners.

+
+ +
+
+ + + +
+

Communication

+

We listen to your needs and keep you informed.

+
+ +
+
+ + + +
+

Respect

+

We treat your space and your people with care.

+
+
+
+
+ + +
+ +
+

Ready to work with us?

+

+ Contact us today for a free consultation and see how Nexus can help keep your facility + spotless. +

+ +
+
+
diff --git a/src/routes/admin/+layout.server.ts b/src/routes/admin/+layout.server.ts new file mode 100644 index 0000000..84d5326 --- /dev/null +++ b/src/routes/admin/+layout.server.ts @@ -0,0 +1,71 @@ +import type { LayoutServerLoad } from './$types'; +import { redirect, error } from '@sveltejs/kit'; +import { AdminDashboardStore, AccountsStore } from '$houdini'; +import { getCurrentMonth } from '$lib/utils/date'; + +export const load: LayoutServerLoad = async (event) => { + const { url, parent, locals, depends } = event; + + // Register dependency for invalidation + depends('app:dashboard'); + + const parentData = await parent(); + const me = parentData.user?.data?.me; + + // Not authenticated + if (!me) { + const returnTo = encodeURIComponent(url.pathname + url.search); + throw redirect(307, `/login?return_to=${returnTo}`); + } + + // Admin requires team profile with admin or team_leader role + if (me.__typename !== 'TeamProfileType') { + throw error(403, 'Admin access requires a team profile'); + } + + if (me.role !== 'ADMIN' && me.role !== 'TEAM_LEADER') { + throw error(403, 'Admin access requires an admin or team leader role'); + } + + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + // Get month from URL params or default to current + const month = url.searchParams.get('month') ?? getCurrentMonth(); + + // Fetch dashboard data and full accounts in parallel + // Admin pages need full account data with schedules, scopes, contacts, revenues + const [dashboardResult, accountsResult] = await Promise.all([ + new AdminDashboardStore().fetch({ + ...fetchParams, + variables: { month } + }), + new AccountsStore().fetch(fetchParams) + ]); + + const dashboard = dashboardResult.data?.adminDashboard; + + // Filter services and projects by status client-side + const services = dashboard?.services ?? []; + const projects = dashboard?.projects ?? []; + + return { + ...parentData, + // Override with full accounts data (includes schedules, scopes, contacts, revenues) + accounts: accountsResult, + services: { + scheduled: services.filter((s) => s.status === 'SCHEDULED'), + inProgress: services.filter((s) => s.status === 'IN_PROGRESS'), + completed: services.filter((s) => s.status === 'COMPLETED') + }, + projects: { + scheduled: projects.filter((p) => p.status === 'SCHEDULED'), + inProgress: projects.filter((p) => p.status === 'IN_PROGRESS'), + completed: projects.filter((p) => p.status === 'COMPLETED') + }, + invoices: dashboard?.invoices ?? [], + reports: dashboard?.reports ?? [], + serviceScopes: dashboard?.serviceScopeTemplates ?? [], + projectScopes: dashboard?.projectScopeTemplates ?? [], + currentMonth: month + }; +}; diff --git a/src/routes/admin/+layout.svelte b/src/routes/admin/+layout.svelte new file mode 100644 index 0000000..ae9c9d0 --- /dev/null +++ b/src/routes/admin/+layout.svelte @@ -0,0 +1,7 @@ + + +{@render children()} diff --git a/src/routes/admin/+page.server.ts b/src/routes/admin/+page.server.ts new file mode 100644 index 0000000..c4e8636 --- /dev/null +++ b/src/routes/admin/+page.server.ts @@ -0,0 +1,36 @@ +import type { PageServerLoad } from './$types'; +import { getWeekDateRange } from '$lib/utils/date'; + +export const load: PageServerLoad = async ({ parent }) => { + // Get parent data - no additional queries needed! + // The layout already fetched all month data, we just filter for this week + const parentData = await parent(); + + // Get current week's date range for filtering + const { start, end } = getWeekDateRange(); + + // Filter services and projects for this week from parent data + const isInWeek = (date: string) => date >= start && date <= end; + + const weekServices = { + scheduled: (parentData.services?.scheduled ?? []).filter((s) => isInWeek(s.date)), + inProgress: (parentData.services?.inProgress ?? []).filter((s) => isInWeek(s.date)) + }; + + const weekProjects = { + scheduled: (parentData.projects?.scheduled ?? []).filter((p) => isInWeek(p.date)), + inProgress: (parentData.projects?.inProgress ?? []).filter((p) => isInWeek(p.date)) + }; + + // Filter invoices for SENT status (awaiting payment) + const pendingInvoices = (parentData.invoices ?? []).filter((i) => i.status === 'SENT'); + + return { + ...parentData, + dashboard: { + services: weekServices, + projects: weekProjects, + invoices: pendingInvoices + } + }; +}; diff --git a/src/routes/admin/+page.svelte b/src/routes/admin/+page.svelte new file mode 100644 index 0000000..01f905f --- /dev/null +++ b/src/routes/admin/+page.svelte @@ -0,0 +1,629 @@ + + + + Admin Dashboard - Nexus + + +
+ + + + + + + + +
+ +
+
+
+

+ Services +

+ View all +
+
+ +
+

This Week

+ {#if thisWeekServices.length === 0} +

No services scheduled this week

+ {:else} + + {#if thisWeekServices.length > 5} +

+ +{thisWeekServices.length - 5} more services +

+ {/if} + {/if} +
+
+ + +
+
+
+

Projects

+ View all +
+
+ +
+

This Week

+ {#if thisWeekProjects.length === 0} +

No projects scheduled this week

+ {:else} + + {#if thisWeekProjects.length > 5} +

+ +{thisWeekProjects.length - 5} more projects +

+ {/if} + {/if} +
+
+ + +
+
+
+

Invoices

+ View all +
+
+ +
+

Awaiting Payment

+ {#if pendingInvoices.length === 0} +

No outstanding invoices

+ {:else} + + {#if pendingInvoices.length > 5} +

+ +{pendingInvoices.length - 5} more outstanding invoices +

+ {/if} + {/if} +
+
+ + +
+
+
+

Calendar

+ View all +
+
+ +
+

Upcoming Events

+ {#if calendarLoading} +
+ + + + +
+ {:else if calendarError} +
+
+ + + +
+

{calendarError}

+ + Open Calendar + +
+ {:else if calendarEvents.length === 0} +
+
+ +
+

No upcoming events in the next 7 days

+ + Open Calendar + +
+ {:else} + + {/if} +
+
+
+
+
diff --git a/src/routes/admin/accounts/+page.svelte b/src/routes/admin/accounts/+page.svelte new file mode 100644 index 0000000..0034d63 --- /dev/null +++ b/src/routes/admin/accounts/+page.svelte @@ -0,0 +1,220 @@ + + +{#snippet createFormContent()} + +{/snippet} + + + Accounts - Admin - Nexus + + +
+ +
+
+
+ + + + + +
+

+ Accounts +

+

Manage service accounts and locations.

+
+
+ +
+
+ + +
+
+ + + + +
+
+ + {#if filteredAccounts.length > 0} + + {:else if searchQuery} +
+ + + +

No accounts found

+

+ No accounts match "{searchQuery}". Try a different search term. +

+
+ {:else} +
+ + + +

No accounts yet

+

Get started by adding your first service account.

+ +
+ {/if} +
+
+ + + diff --git a/src/routes/admin/accounts/[account]/+page.server.ts b/src/routes/admin/accounts/[account]/+page.server.ts new file mode 100644 index 0000000..64b063c --- /dev/null +++ b/src/routes/admin/accounts/[account]/+page.server.ts @@ -0,0 +1,60 @@ +import { GetAccountStore } from '$houdini'; +import { GetScopeTemplatesStore } from '$lib/utils/scopes'; +import { WAVE_ACCESS_TOKEN } from '$env/static/private'; +import { PUBLIC_WAVE_BUSINESS_ID } from '$env/static/public'; +import { createWaveServerClient } from '$lib/graphql/wave/client'; +import { GetProductsDocument, type ProductEdge, type Product } from '$lib/graphql/wave/generated'; +import type { PageServerLoad } from './$types'; + +export interface WaveProduct { + id: string; + name: string; + description: string | null; + unitPrice: number; + isArchived: boolean; +} + +async function fetchWaveProducts(): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.query(GetProductsDocument, { + businessId: PUBLIC_WAVE_BUSINESS_ID, + pageSize: 500, + isSold: true, + isArchived: false + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return []; + } + + const edges = result.data?.business?.products?.edges ?? []; + return edges + .filter((edge: ProductEdge): edge is ProductEdge & { node: Product } => edge.node != null) + .map((edge: ProductEdge & { node: Product }) => ({ + id: edge.node.id, + name: edge.node.name, + description: edge.node.description ?? null, + unitPrice: edge.node.unitPrice, + isArchived: edge.node.isArchived + })); +} + +export const load: PageServerLoad = async (event) => { + const fetchParams = { + event, + metadata: { cookie: event.locals.cookie } + }; + + const [account, scopeTemplates, waveProducts] = await Promise.all([ + new GetAccountStore().fetch({ + ...fetchParams, + variables: { id: event.params.account } + }), + new GetScopeTemplatesStore().fetch(fetchParams), + fetchWaveProducts() + ]); + + return { account, scopeTemplates, waveProducts }; +}; diff --git a/src/routes/admin/accounts/[account]/+page.svelte b/src/routes/admin/accounts/[account]/+page.svelte new file mode 100644 index 0000000..e4bb4e7 --- /dev/null +++ b/src/routes/admin/accounts/[account]/+page.svelte @@ -0,0 +1,833 @@ + + +{#snippet editFormContent()} + {#if account} + + {/if} +{/snippet} + +{#snippet addressFormContent()} + {#if account} + + {/if} +{/snippet} + +{#snippet contactFormContent()} + {#if account} + + {/if} +{/snippet} + +{#snippet revenueFormContent()} + {#if account} + + {/if} +{/snippet} + +{#snippet laborFormContent()} + {#if editingLabor} + + {/if} +{/snippet} + +{#snippet scheduleFormContent()} + {#if editingSchedule} + + {/if} +{/snippet} + +{#snippet scopeFormContent()} + {#if editingScope && account} + + {/if} +{/snippet} + +{#snippet areaFormContent()} + {#if editingArea} + + {/if} +{/snippet} + +{#snippet taskFormContent()} + {#if editingTask} + + {/if} +{/snippet} + + + {account?.name ?? 'Account'} - Accounts - Admin - Nexus + + +
+ + {#if account} + + +
+ +
+ + +
+
+

Name

+

{account.name}

+
+
+

Status

+ + {account.status} + +
+
+

Customer

+

{getCustomerName(account.customerId)}

+
+
+

Start Date

+

{formatDate(account.startDate) || '—'}

+
+
+

End Date

+

{formatDate(account.endDate) || '—'}

+
+
+
+ + + (deleteRevenueId = id)} + /> + + +
+ + + {#if addresses.length > 0} +
+ {#each addresses as address (address.id)} + toggleAddressExpanded(address.id)} + onEdit={() => showEditAddressForm(address)} + onDelete={() => (deleteAddressId = address.id)} + onAddSchedule={() => showAddScheduleForm(address.id)} + onEditSchedule={(schedule) => showEditScheduleForm(address.id, schedule)} + onDeleteSchedule={(scheduleId) => (deleteScheduleId = scheduleId)} + onToggleScheduleHistory={() => toggleScheduleHistory(address.id)} + onAddLabor={() => showAddLaborForm(address.id)} + onEditLabor={(labor) => showEditLaborForm(address.id, labor)} + onDeleteLabor={(laborId) => (deleteLaborId = laborId)} + onToggleLaborHistory={() => toggleLaborHistory(address.id)} + onToggleScope={toggleScopeExpanded} + onToggleArea={toggleAreaExpanded} + onAddScope={() => showAddScopeForm(address.id)} + onAddScopeFromTemplate={(templateId) => + handleAddScopeFromTemplate(address.id, templateId)} + onEditScope={(scope) => showEditScopeForm(address.id, scope)} + onDeleteScope={(scopeId) => (deleteScopeId = scopeId)} + onAddArea={(scopeId, nextOrder) => showAddAreaForm(scopeId, nextOrder)} + onEditArea={(scopeId, area) => showEditAreaForm(scopeId, area)} + onDeleteArea={(areaId) => (deleteAreaId = areaId)} + onAddTask={(areaId, nextOrder) => showAddTaskForm(areaId, nextOrder)} + onEditTask={(areaId, task) => showEditTaskForm(areaId, task)} + onDeleteTask={(taskId) => (deleteTaskId = taskId)} + onGenerateServices={openGenerateServicesModal} + onClearScopeError={() => (scopeErrors = { ...scopeErrors, [address.id]: '' })} + /> + {/each} +
+ {:else} +

No addresses added yet.

+ {/if} +
+ + +
+ + + {#if contacts.length > 0} +
+ {#each contacts as contact (contact.id)} + showEditContactForm(contact)} + onDelete={() => (deleteContactId = contact.id)} + /> + {/each} +
+ {:else} +

No contacts added yet.

+ {/if} +
+ + +
+

Danger Zone

+

+ Deleting this account will remove all associated addresses, contacts, and schedules. + This action cannot be undone. +

+ +
+
+ {:else} +
+ + + +

Account not found

+

+ The account you're looking for doesn't exist or has been deleted. +

+ Back to Accounts +
+ {/if} +
+
+ + + (deleteModalOpen = false)} +/> + + + (deleteContactId = null)} +/> + + + (deleteAddressId = null)} +/> + + + (deleteRevenueId = null)} +/> + + + (deleteLaborId = null)} +/> + + + (deleteScheduleId = null)} +/> + + + (deleteScopeId = null)} +/> + + + (deleteAreaId = null)} +/> + + + (deleteTaskId = null)} +/> + + +{#if account} + ({ + id: addr.id, + name: addr.name, + streetAddress: addr.streetAddress, + city: addr.city, + state: addr.state, + zipCode: addr.zipCode, + isActive: addr.isActive, + schedules: addr.schedules + })) + } + ]} + preselectedAddressId={generatePreselectedAddressId} + preselectedScheduleId={generatePreselectedScheduleId} + onClose={closeGenerateServicesModal} + onSuccess={async () => { + await invalidateAll(); + }} + /> +{/if} diff --git a/src/routes/admin/calendar/+page.server.ts b/src/routes/admin/calendar/+page.server.ts new file mode 100644 index 0000000..d236200 --- /dev/null +++ b/src/routes/admin/calendar/+page.server.ts @@ -0,0 +1,47 @@ +import { calendarService } from '$lib/services/calendar'; +import type { PageServerLoad } from './$types'; + +// Format date as YYYY-MM +function formatMonth(date: Date): string { + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + return `${year}-${month}`; +} + +export const load: PageServerLoad = async ({ parent, url }) => { + // Get team profiles from parent layout + const parentData = await parent(); + + // Read month from URL params, default to current month + const monthParam = url.searchParams.get('month'); + const now = new Date(); + let currentMonth: string; + let timeMin: Date; + let timeMax: Date; + + if (monthParam && /^\d{4}-\d{2}$/.test(monthParam)) { + // Parse month param and calculate boundaries + currentMonth = monthParam; + const [year, month] = monthParam.split('-').map(Number); + timeMin = new Date(year, month - 1, 1); + timeMax = new Date(year, month, 0, 23, 59, 59, 999); // Last day of month + } else { + // Default: current month + currentMonth = formatMonth(now); + timeMin = new Date(now.getFullYear(), now.getMonth(), 1); + timeMax = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999); + } + + const eventsResult = await calendarService.listEvents({ + timeMin: timeMin.toISOString(), + timeMax: timeMax.toISOString(), + maxResults: 100 + }); + + return { + events: eventsResult.data ?? [], + eventsError: eventsResult.error ?? null, + teamProfiles: parentData.team?.data?.teamProfiles ?? [], + currentMonth + }; +}; diff --git a/src/routes/admin/calendar/+page.svelte b/src/routes/admin/calendar/+page.svelte new file mode 100644 index 0000000..0388b36 --- /dev/null +++ b/src/routes/admin/calendar/+page.svelte @@ -0,0 +1,786 @@ + + +{#snippet createFormContent()} + +{/snippet} + + + Calendar - Admin - Nexus + + +
+ + +
+
+
+ + + + + +
+

+ Calendar +

+

View and manage scheduled events.

+
+
+ +
+
+ + +
+ + + + +
+ + + {#if eventsError} +
+
+ + + +
+

+ Failed to load calendar events +

+

+ {eventsError.message} +

+
+
+
+ {/if} + + +
+ +
+ + + + +
+ + +
+ + + +
+ + + + + + {#if hasSelection} +
+
+ + {selectedEvents.size} event{selectedEvents.size !== 1 ? 's' : ''} selected + + + +
+ +
+ {/if} +
+ + + {#if viewMode === 'week'} + + {:else} + + {#if eventsByDate.length > 0} +
+ {#each eventsByDate as [dateKey, dateEvents] (dateKey)} +
+ +
+
+

+ {formatDateHeader(dateKey)} +

+
+
+ + +
+ {#each dateEvents as event (event.id)} + {@const eventColor = getEventColor(event.colorId)} + {@const selected = isSelected(event.id)} +
+ + + +
+
+
+ +
+ {#if eventColor} + + {/if} +

+ {event.summary} +

+ {#if event.start.date} + + All Day + + {/if} +
+ + +
+ + + + + + {formatEventTime(event)} + + + + {#if event.location} + + + + + + {event.location} + + {/if} + + + {#if event.attendees && event.attendees.length > 0} + + {/if} +
+ + + {#if event.description} + + {/if} +
+ + + + + +
+
+
+
+ {/each} +
+
+ {/each} +
+ {:else if searchQuery} +
+ + + +

No Events Found

+

+ No events match "{searchQuery}". Try a different search term. +

+
+ {:else} +
+ + + +

No Upcoming Events

+

+ Get started by creating your first calendar event. Schedule meetings, reminders, and more. +

+ +
+ {/if} + {/if} +
+
+ + + diff --git a/src/routes/admin/calendar/[event]/+page.server.ts b/src/routes/admin/calendar/[event]/+page.server.ts new file mode 100644 index 0000000..8d2ccc3 --- /dev/null +++ b/src/routes/admin/calendar/[event]/+page.server.ts @@ -0,0 +1,27 @@ +import { calendarService } from '$lib/services/calendar'; +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ params, parent }) => { + const parentData = await parent(); + const eventId = params.event; + + const eventResult = await calendarService.getEvent(eventId); + + if (eventResult.error) { + throw error(404, { + message: eventResult.error.message || 'Event not found' + }); + } + + if (!eventResult.data) { + throw error(404, { + message: 'Event not found' + }); + } + + return { + event: eventResult.data, + teamProfiles: parentData.team?.data?.teamProfiles ?? [] + }; +}; diff --git a/src/routes/admin/calendar/[event]/+page.svelte b/src/routes/admin/calendar/[event]/+page.svelte new file mode 100644 index 0000000..ac81d0c --- /dev/null +++ b/src/routes/admin/calendar/[event]/+page.svelte @@ -0,0 +1,470 @@ + + +{#snippet editFormContent()} + +{/snippet} + + + {event.summary} - Calendar - Admin - Nexus + + +
+ + + + +
+

Event Details

+ +
+ +
+
+
+ + + +
+
+

Start

+

{formatDateTime(event.start)}

+
+
+
+ + +
+
+
+ + + +
+
+

End

+

{formatDateTime(event.end)}

+
+
+ {#if eventDuration} + + {eventDuration} + + {/if} +
+ + + {#if event.location} +
+
+
+ + + + +
+
+

Location

+

{event.location}

+
+
+
+ {/if} + + + {#if event.htmlLink} +
+
+
+ + + +
+
+

View in Google Calendar

+ + Open in new tab + +
+
+
+ {/if} +
+
+ + + {#if event.description} +
+

Description

+

{event.description}

+
+ {/if} + + + {#if event.attendees && event.attendees.length > 0} +
+

+ Attendees ({event.attendees.length}) +

+
+ {#each event.attendees as attendee (attendee.email)} +
+
+ + + +
+
+ {#if attendee.displayName} +

{attendee.displayName}

+

{attendee.email}

+ {:else} +

{attendee.email}

+ {/if} +
+ {#if attendee.optional} + + Optional + + {/if} +
+ {/each} +
+
+ {/if} + + + {#if event.reminders} +
+

Reminders

+ {#if event.reminders.useDefault} +
+
+ + + +
+
+

Default reminders

+

Using calendar's default reminder settings

+
+
+ {:else if event.reminders.overrides && event.reminders.overrides.length > 0} +
+ {#each event.reminders.overrides as reminder, index (index)} + {@const methodLabel = reminder.method === 'email' ? 'Email' : 'Popup'} + {@const timeLabel = formatReminderTime(reminder.minutes)} +
+
+ {#if reminder.method === 'email'} + + + + {:else} + + + + {/if} +
+
+

{methodLabel} notification

+

{timeLabel} before event

+
+
+ {/each} +
+ {:else} +

No reminders configured for this event.

+ {/if} +
+ {/if} + + +
+

Metadata

+
+
+ Event ID + {event.id} +
+ {#if event.created} +
+ Created + + {new Date(event.created).toLocaleString()} + +
+ {/if} + {#if event.updated} +
+ Last Updated + + {new Date(event.updated).toLocaleString()} + +
+ {/if} +
+
+ + +
+

Danger Zone

+

+ Deleting this event will remove it from Google Calendar. This action cannot be undone. +

+ +
+
+
+ + {}} + loading={isDeleting} +/> diff --git a/src/routes/admin/customers/+page.svelte b/src/routes/admin/customers/+page.svelte new file mode 100644 index 0000000..68ccc78 --- /dev/null +++ b/src/routes/admin/customers/+page.svelte @@ -0,0 +1,215 @@ + + +{#snippet createFormContent()} + +{/snippet} + + + Customers - Admin - Nexus + + +
+ +
+
+
+ + + + + +
+

+ Customers +

+

+ Manage customer profiles, contacts, and billing information. +

+
+
+ +
+
+ + +
+
+ + + + +
+
+ + {#if filteredCustomers.length > 0} + + {:else if searchQuery} +
+ + + +

No customers found

+

+ No customers match "{searchQuery}". Try a different search term. +

+
+ {:else} +
+ + + +

No customers yet

+

+ Get started by adding your first customer. Customers can be linked to service accounts. +

+ +
+ {/if} +
+
+ + + diff --git a/src/routes/admin/customers/[customer]/+page.server.ts b/src/routes/admin/customers/[customer]/+page.server.ts new file mode 100644 index 0000000..dbc6ec9 --- /dev/null +++ b/src/routes/admin/customers/[customer]/+page.server.ts @@ -0,0 +1,69 @@ +import { GetCustomerStore } from '$houdini'; +import { WAVE_ACCESS_TOKEN } from '$env/static/private'; +import { PUBLIC_WAVE_BUSINESS_ID } from '$env/static/public'; +import { createWaveServerClient } from '$lib/graphql/wave/client'; +import { + GetCustomersDocument, + type CustomerEdge, + type Customer +} from '$lib/graphql/wave/generated'; +import type { PageServerLoad } from './$types'; + +export interface WaveCustomer { + id: string; + name: string; + email: string | null; + address: { + city: string | null; + province: { code: string } | null; + } | null; + isArchived: boolean; +} + +async function fetchWaveCustomers(): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.query(GetCustomersDocument, { + businessId: PUBLIC_WAVE_BUSINESS_ID, + pageSize: 500 + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return []; + } + + const edges = result.data?.business?.customers?.edges ?? []; + return edges + .filter((edge: CustomerEdge): edge is CustomerEdge & { node: Customer } => edge.node != null) + .map((edge: CustomerEdge & { node: Customer }) => ({ + id: edge.node.id, + name: edge.node.name, + email: edge.node.email ?? null, + address: edge.node.address + ? { + city: edge.node.address.city ?? null, + province: edge.node.address.province ? { code: edge.node.address.province.code } : null + } + : null, + isArchived: edge.node.isArchived + })); +} + +export const load: PageServerLoad = async (event) => { + const fetchParams = { + event, + metadata: { cookie: event.locals.cookie } + }; + + // Fetch customer and Wave customers in parallel + const [customer, waveCustomers] = await Promise.all([ + new GetCustomerStore().fetch({ + ...fetchParams, + variables: { id: event.params.customer } + }), + fetchWaveCustomers() + ]); + + return { customer, waveCustomers }; +}; diff --git a/src/routes/admin/customers/[customer]/+page.svelte b/src/routes/admin/customers/[customer]/+page.svelte new file mode 100644 index 0000000..8356620 --- /dev/null +++ b/src/routes/admin/customers/[customer]/+page.svelte @@ -0,0 +1,441 @@ + + +{#snippet editFormContent()} + {#if customer} + + {/if} +{/snippet} + +{#snippet contactFormContent()} + {#if customer} + + {/if} +{/snippet} + +{#snippet addressFormContent()} + {#if customer} + + {/if} +{/snippet} + +{#snippet waveLinkFormContent()} + {#if customer} + + {/if} +{/snippet} + + + {customer?.name ?? 'Customer'} - Customers - Admin - Nexus + + +
+ + {#if customer} + + +
+ +
+ + +
+
+

Name

+

{customer.name}

+
+
+

Status

+ + {customer.status} + +
+
+

Billing Email

+

{customer.billingEmail || '—'}

+
+
+

Billing Terms

+

{customer.billingTerms || '—'}

+
+
+

Start Date

+

{formatDate(customer.startDate)}

+
+
+

End Date

+

{formatDate(customer.endDate)}

+
+
+
+ + + + + +
+ + + {#if contacts.length > 0} +
+ {#each contacts as contact (contact.id)} +
+
+
+ {contact.fullName} + {#if contact.isPrimary} + Primary + {/if} +
+ {#if contact.email} +

{contact.email}

+ {/if} + {#if contact.phone} +

{contact.phone}

+ {/if} +
+
+ + +
+
+ {/each} +
+ {:else} +

No contacts added yet.

+ {/if} +
+ + +
+ + + {#if addresses.length > 0} +
+ {#each addresses as address (address.id)} +
+
+
+ {address.streetAddress} + {#if address.isPrimary} + Primary + {/if} + + {address.addressType} + +
+

+ {address.city}, {address.state} + {address.zipCode} +

+
+
+ + +
+
+ {/each} +
+ {:else} +

No addresses added yet.

+ {/if} +
+ + + {#if accounts.length > 0} +
+

Linked Accounts

+
+ {#each accounts as account (account.id)} + + {account.name} + + + + + {/each} +
+
+ {/if} + + +
+

Danger Zone

+

+ Deleting this customer will remove all associated contacts and addresses. This action + cannot be undone. +

+ +
+
+ {:else} +
+ + + +

Customer not found

+

+ The customer you're looking for doesn't exist or has been deleted. +

+ Back to Customers +
+ {/if} +
+
+ + + (deleteModalOpen = false)} +/> + + + (deleteContactId = null)} +/> + + + (deleteAddressId = null)} +/> diff --git a/src/routes/admin/events/+page.server.ts b/src/routes/admin/events/+page.server.ts new file mode 100644 index 0000000..5d318d3 --- /dev/null +++ b/src/routes/admin/events/+page.server.ts @@ -0,0 +1,28 @@ +import { AdminEventsStore } from '$houdini'; +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async (event) => { + const { parent } = event; + const parentData = await parent(); + const me = parentData.user?.data?.me; + + // Gate to ADMIN only + if (me?.__typename !== 'TeamProfileType' || me.role !== 'ADMIN') { + throw error(403, 'Admin access required'); + } + + const fetchParams = { + event, + metadata: { cookie: event.locals.cookie } + }; + + const events = await new AdminEventsStore().fetch({ + ...fetchParams, + variables: { limit: 100, offset: 0 } + }); + + return { + events + }; +}; diff --git a/src/routes/admin/events/+page.svelte b/src/routes/admin/events/+page.svelte new file mode 100644 index 0000000..c7579cd --- /dev/null +++ b/src/routes/admin/events/+page.svelte @@ -0,0 +1,147 @@ + + + + Event Log - Admin - Nexus + + +
+ +
+
+ + + + + +
+

+ Event Log +

+

+ View system events that may trigger notifications. +

+
+
+
+ +
+

+ Showing the last {events.length} events. +

+
+ + {#if events.length > 0} +
+ + + + + + + + + + + {#each events as event (event.id)} + handleRowClick(event.id)} + onkeydown={(e) => e.key === 'Enter' && handleRowClick(event.id)} + tabindex="0" + role="button" + > + + + + + + {/each} + +
+ Event Type + + Entity + + Time +
+ + {getEventTypeLabel(event.eventType)} + + + {event.entityType} + + {event.entityId.substring(0, 8)}... + + + + {formatEventTime(event.createdAt)} + +
+
+ {:else} +
+ + + +

No events yet

+

Events will appear here as they occur in the system.

+
+ {/if} +
+
diff --git a/src/routes/admin/events/[event]/+page.server.ts b/src/routes/admin/events/[event]/+page.server.ts new file mode 100644 index 0000000..93782a2 --- /dev/null +++ b/src/routes/admin/events/[event]/+page.server.ts @@ -0,0 +1,32 @@ +import { AdminEventStore } from '$houdini'; +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async (event) => { + const { parent } = event; + const parentData = await parent(); + const me = parentData.user?.data?.me; + + // Gate to ADMIN only + if (me?.__typename !== 'TeamProfileType' || me.role !== 'ADMIN') { + throw error(403, 'Admin access required'); + } + + const fetchParams = { + event, + metadata: { cookie: event.locals.cookie } + }; + + const eventResult = await new AdminEventStore().fetch({ + ...fetchParams, + variables: { id: event.params.event } + }); + + if (!eventResult.data?.event) { + throw error(404, 'Event not found'); + } + + return { + event: eventResult + }; +}; diff --git a/src/routes/admin/events/[event]/+page.svelte b/src/routes/admin/events/[event]/+page.svelte new file mode 100644 index 0000000..6264c75 --- /dev/null +++ b/src/routes/admin/events/[event]/+page.svelte @@ -0,0 +1,156 @@ + + + + Event Details - Admin - Nexus + + +
+ +
+
+ + + + + +
+

+ Event Details +

+

View details for a system event.

+
+
+
+ + {#if event} +
+ +
+

Event Type

+
+ + {getEventTypeLabel(event.eventType)} + + + {event.eventType} + +
+
+ + +
+

Entity

+
+
+
Entity Type
+
{event.entityType}
+
+
+
Entity ID
+
{event.entityId}
+
+
+
+ + +
+

Triggered By

+ {#if event.triggeredById} +
+
+
Profile Type
+
+ {event.triggeredByType === 'teamprofile' ? 'Team Member' : 'Customer'} +
+
+
+
Profile ID
+
{event.triggeredById}
+
+
+ {:else} +

System-generated event

+ {/if} +
+ + +
+

Metadata

+
{formatJson(
+							event.metadata
+						)}
+
+ + +
+

Timestamps

+
+
+
Created At
+
{formatDateTime(event.createdAt)}
+
+
+
Updated At
+
{formatDateTime(event.updatedAt)}
+
+
+
+ + +
+ Event ID: + {event.id} +
+
+ {:else} +
+ + + +

Event not found

+

The requested event could not be found.

+
+ {/if} +
+
diff --git a/src/routes/admin/invoices/+page.svelte b/src/routes/admin/invoices/+page.svelte new file mode 100644 index 0000000..18c9811 --- /dev/null +++ b/src/routes/admin/invoices/+page.svelte @@ -0,0 +1,458 @@ + + +{#snippet createFormContent()} + +{/snippet} + + + Invoices - Admin - Nexus + + +
+ + +
+
+
+ + + + + +
+

+ Invoices +

+

Create and manage customer invoices.

+
+
+ +
+ + +
+ + + + + Wave + + +
+
+ + +
+ +
+ + + + +
+ + + + + + +
+ + + {#if filteredInvoices.length > 0} + + {:else if searchQuery || selectedStatus || selectedCustomerId} +
+ + + +

No Invoices Found

+

+ No invoices match your search criteria. Try adjusting your filters. +

+
+ {:else} +
+ + + +

No Invoices Yet

+

+ Get started by creating your first invoice. Track payments and billing for all your + customers. +

+ +
+ {/if} +
+
diff --git a/src/routes/admin/invoices/[invoice]/+page.server.ts b/src/routes/admin/invoices/[invoice]/+page.server.ts new file mode 100644 index 0000000..684af72 --- /dev/null +++ b/src/routes/admin/invoices/[invoice]/+page.server.ts @@ -0,0 +1,497 @@ +import { GetInvoiceStore, UpdateInvoiceStore } from '$houdini'; +import { WAVE_ACCESS_TOKEN } from '$env/static/private'; +import { PUBLIC_WAVE_BUSINESS_ID } from '$env/static/public'; +import { createWaveServerClient } from '$lib/graphql/wave/client'; +import { + GetCustomersDocument, + GetProductsDocument, + GetInvoiceDocument, + type CustomerEdge, + type Customer, + type ProductEdge, + type Product +} from '$lib/graphql/wave/generated'; +import { + createWaveInvoice, + deleteWaveInvoice, + sendWaveInvoice, + updateWaveInvoice, + approveWaveInvoice +} from '$lib/graphql/wave/mutations'; +import { fromGlobalId } from '$lib/utils/relay'; +import type { PageServerLoad, Actions } from './$types'; +import { error, fail } from '@sveltejs/kit'; + +export interface WaveCustomer { + id: string; + name: string; + email: string | null; + isArchived: boolean; +} + +export interface WaveProduct { + id: string; + name: string; + description: string | null; + unitPrice: number; + isArchived: boolean; +} + +export interface WaveInvoiceItem { + product: { id: string; name: string }; + description: string | null; + quantity: number; + unitPrice: number; + subtotal: { value: string }; + total: { value: string }; +} + +export interface WaveInvoice { + id: string; + invoiceNumber: string; + invoiceDate: string; + dueDate: string; + status: string; + title: string; + subhead: string | null; + poNumber: string | null; + memo: string | null; + footer: string | null; + pdfUrl: string; + viewUrl: string; + customer: { id: string; name: string; email: string | null }; + items: WaveInvoiceItem[]; + subtotal: { value: string; currency: { code: string; symbol: string } }; + taxTotal: { value: string; currency: { code: string; symbol: string } }; + discountTotal: { value: string; currency: { code: string; symbol: string } }; + total: { value: string; currency: { code: string; symbol: string } }; + amountDue: { value: string; currency: { code: string; symbol: string } }; + amountPaid: { value: string; currency: { code: string; symbol: string } }; + lastSentAt: string | null; + lastSentVia: string | null; + lastViewedAt: string | null; +} + +async function fetchWaveCustomers(): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.query(GetCustomersDocument, { + businessId: PUBLIC_WAVE_BUSINESS_ID, + pageSize: 500 + }); + + if (result.error) { + console.error('Wave API error (customers):', result.error); + return []; + } + + const edges = result.data?.business?.customers?.edges ?? []; + return edges + .filter((edge: CustomerEdge): edge is CustomerEdge & { node: Customer } => edge.node != null) + .map((edge: CustomerEdge & { node: Customer }) => ({ + id: edge.node.id, + name: edge.node.name, + email: edge.node.email ?? null, + isArchived: edge.node.isArchived ?? false + })); +} + +async function fetchWaveProducts(): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.query(GetProductsDocument, { + businessId: PUBLIC_WAVE_BUSINESS_ID, + pageSize: 500, + isSold: true, + isArchived: false + }); + + if (result.error) { + console.error('Wave API error (products):', result.error); + return []; + } + + const edges = result.data?.business?.products?.edges ?? []; + return edges + .filter((edge: ProductEdge): edge is ProductEdge & { node: Product } => edge.node != null) + .map((edge: ProductEdge & { node: Product }) => ({ + id: edge.node.id, + name: edge.node.name, + description: edge.node.description ?? null, + unitPrice: parseFloat(edge.node.unitPrice), + isArchived: edge.node.isArchived + })); +} + +async function fetchWaveInvoice(waveInvoiceId: string): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + // The stored waveInvoiceId is decoded (e.g., "businessId;Invoice:invoiceId") + // Wave API expects base64-encoded Global ID with "Business:" prefix + const waveGlobalId = btoa(`Business:${waveInvoiceId}`); + + const result = await client.query(GetInvoiceDocument, { + businessId: PUBLIC_WAVE_BUSINESS_ID, + invoiceId: waveGlobalId + }); + + if (result.error) { + console.error('Wave API error (invoice):', result.error); + return null; + } + + const invoice = result.data?.business?.invoice; + if (!invoice) return null; + + return { + id: invoice.id, + invoiceNumber: invoice.invoiceNumber, + invoiceDate: invoice.invoiceDate, + dueDate: invoice.dueDate, + status: invoice.status, + title: invoice.title, + subhead: invoice.subhead ?? null, + poNumber: invoice.poNumber ?? null, + memo: invoice.memo ?? null, + footer: invoice.footer ?? null, + pdfUrl: invoice.pdfUrl, + viewUrl: invoice.viewUrl, + customer: { + id: invoice.customer.id, + name: invoice.customer.name, + email: invoice.customer.email ?? null + }, + items: (invoice.items ?? []).map( + (item: { + product: { id: string; name: string }; + description: string | null; + quantity: string; + unitPrice: string; + subtotal: { value: string }; + total: { value: string }; + }) => ({ + product: { id: item.product.id, name: item.product.name }, + description: item.description ?? null, + quantity: parseFloat(item.quantity), + unitPrice: parseFloat(item.unitPrice), + subtotal: { value: item.subtotal.value }, + total: { value: item.total.value } + }) + ), + subtotal: { + value: invoice.subtotal.value, + currency: { code: invoice.subtotal.currency.code, symbol: invoice.subtotal.currency.symbol } + }, + taxTotal: { + value: invoice.taxTotal.value, + currency: { code: invoice.taxTotal.currency.code, symbol: invoice.taxTotal.currency.symbol } + }, + discountTotal: { + value: invoice.discountTotal.value, + currency: { + code: invoice.discountTotal.currency.code, + symbol: invoice.discountTotal.currency.symbol + } + }, + total: { + value: invoice.total.value, + currency: { code: invoice.total.currency.code, symbol: invoice.total.currency.symbol } + }, + amountDue: { + value: invoice.amountDue.value, + currency: { code: invoice.amountDue.currency.code, symbol: invoice.amountDue.currency.symbol } + }, + amountPaid: { + value: invoice.amountPaid.value, + currency: { + code: invoice.amountPaid.currency.code, + symbol: invoice.amountPaid.currency.symbol + } + }, + lastSentAt: invoice.lastSentAt ?? null, + lastSentVia: invoice.lastSentVia ?? null, + lastViewedAt: invoice.lastViewedAt ?? null + }; +} + +export const load: PageServerLoad = async (event) => { + const { invoice: invoiceId } = event.params; + const { parent } = event; + + const fetchParams = { + event, + metadata: { cookie: event.locals.cookie } + }; + + // Fetch the invoice, parent data, and Wave data in parallel + const [invoiceResult, parentData, waveCustomers, waveProducts] = await Promise.all([ + new GetInvoiceStore().fetch({ + ...fetchParams, + variables: { + id: invoiceId + } + }), + parent(), + fetchWaveCustomers(), + fetchWaveProducts() + ]); + + if (!invoiceResult.data?.invoice) { + throw error(404, 'Invoice not found'); + } + + const invoice = invoiceResult.data.invoice; + + // Fetch Wave invoice if linked + let waveInvoice: WaveInvoice | null = null; + if (invoice.waveInvoiceId) { + waveInvoice = await fetchWaveInvoice(invoice.waveInvoiceId); + } + + return { + invoice, + customers: parentData.customers, + customerLookup: parentData.customerLookup, + accounts: parentData.accounts, + accountLookup: parentData.accountLookup, + waveCustomers, + waveProducts, + waveInvoice + }; +}; + +export const actions: Actions = { + createWaveInvoice: async (event) => { + const { request, params, locals } = event; + const formData = await request.formData(); + const invoiceId = params.invoice; + const customerId = formData.get('customerId') as string; + const invoiceDate = formData.get('invoiceDate') as string; + const dueDate = formData.get('dueDate') as string; + const memo = formData.get('memo') as string | null; + const footer = formData.get('footer') as string | null; + const itemsJson = formData.get('items') as string; + const discountJson = formData.get('discount') as string | null; + + if (!customerId || !itemsJson) { + return fail(400, { error: 'Missing required fields' }); + } + + let items; + try { + items = JSON.parse(itemsJson); + } catch { + return fail(400, { error: 'Invalid items data' }); + } + + // Parse discount if provided + let discounts; + if (discountJson) { + try { + const discount = JSON.parse(discountJson); + if (discount && discount.value > 0) { + discounts = [ + { + discountType: discount.type as 'PERCENTAGE' | 'FIXED', + name: discount.name || 'Discount', + ...(discount.type === 'PERCENTAGE' + ? { percentage: discount.value.toString() } + : { amount: discount.value.toString() }) + } + ]; + } + } catch { + // Ignore invalid discount data + } + } + + const result = await createWaveInvoice({ + businessId: PUBLIC_WAVE_BUSINESS_ID, + customerId, + status: 'DRAFT', + invoiceDate, + dueDate, + items, + discounts, + memo: memo || undefined, + footer: footer || undefined + }); + + if (!result.success || !result.invoice) { + return fail(400, { error: result.error || 'Failed to create Wave invoice' }); + } + + // Update Nexus invoice with the Wave invoice ID + const waveInvoiceId = fromGlobalId(result.invoice.id); + console.log('[createWaveInvoice] Wave invoice created:', { + waveGlobalId: result.invoice.id, + waveInvoiceId, + nexusInvoiceId: invoiceId + }); + + try { + const updateResult = await new UpdateInvoiceStore().mutate( + { input: { id: invoiceId, waveInvoiceId } }, + { event, metadata: { cookie: locals.cookie } } + ); + + console.log('[createWaveInvoice] Update result:', { + data: updateResult.data, + errors: updateResult.errors + }); + + if (updateResult.errors?.length) { + return fail(400, { + error: `Wave invoice created but failed to link: ${updateResult.errors.map((e) => e.message).join(', ')}` + }); + } + + return { success: true, waveInvoiceId }; + } catch (err) { + console.error('[createWaveInvoice] Exception during update:', err); + return fail(500, { + error: `Wave invoice created (ID: ${waveInvoiceId}) but failed to save to Nexus: ${err instanceof Error ? err.message : 'Unknown error'}` + }); + } + }, + + sendWaveInvoice: async (event) => { + const { request, locals } = event; + const formData = await request.formData(); + const invoiceId = formData.get('invoiceId') as string; + const nexusInvoiceId = formData.get('nexusInvoiceId') as string; + const customerEmail = formData.get('customerEmail') as string; + + if (!invoiceId) { + return fail(400, { error: 'Missing invoice ID' }); + } + + if (!customerEmail) { + return fail(400, { error: 'Customer email is required to send invoice' }); + } + + const result = await sendWaveInvoice(invoiceId, [customerEmail]); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to send Wave invoice' }); + } + + // Update Nexus invoice status to SENT + if (nexusInvoiceId) { + await new UpdateInvoiceStore().mutate( + { input: { id: nexusInvoiceId, status: 'SENT' } }, + { event, metadata: { cookie: locals.cookie } } + ); + } + + return { success: true }; + }, + + approveWaveInvoice: async ({ request }) => { + const formData = await request.formData(); + const invoiceId = formData.get('invoiceId') as string; + + if (!invoiceId) { + return fail(400, { error: 'Missing invoice ID' }); + } + + const result = await approveWaveInvoice(invoiceId); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to approve Wave invoice' }); + } + + return { success: true }; + }, + + updateWaveInvoice: async ({ request }) => { + const formData = await request.formData(); + const invoiceId = formData.get('invoiceId') as string; + const invoiceDate = formData.get('invoiceDate') as string; + const dueDate = formData.get('dueDate') as string; + const memo = formData.get('memo') as string | null; + const footer = formData.get('footer') as string | null; + const itemsJson = formData.get('items') as string; + const discountJson = formData.get('discount') as string | null; + + if (!invoiceId || !itemsJson) { + return fail(400, { error: 'Missing required fields' }); + } + + let items; + try { + items = JSON.parse(itemsJson); + } catch { + return fail(400, { error: 'Invalid items data' }); + } + + // Parse discount if provided + let discounts; + if (discountJson) { + try { + const discount = JSON.parse(discountJson); + if (discount && discount.value > 0) { + discounts = [ + { + discountType: discount.type as 'PERCENTAGE' | 'FIXED', + name: discount.name || 'Discount', + ...(discount.type === 'PERCENTAGE' + ? { percentage: discount.value.toString() } + : { amount: discount.value.toString() }) + } + ]; + } + } catch { + // Ignore invalid discount data + } + } + + const result = await updateWaveInvoice({ + id: invoiceId, + invoiceDate, + dueDate, + items, + discounts, + memo: memo || undefined, + footer: footer || undefined + }); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to update Wave invoice' }); + } + + return { success: true }; + }, + + deleteWaveInvoice: async (event) => { + const { request, locals } = event; + const formData = await request.formData(); + const waveInvoiceId = formData.get('waveInvoiceId') as string; + const nexusInvoiceId = formData.get('nexusInvoiceId') as string; + + if (!waveInvoiceId || !nexusInvoiceId) { + return fail(400, { error: 'Missing required fields' }); + } + + // Delete from Wave + const result = await deleteWaveInvoice(waveInvoiceId); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to delete Wave invoice' }); + } + + // Clear waveInvoiceId on Nexus invoice + const updateResult = await new UpdateInvoiceStore().mutate( + { input: { id: nexusInvoiceId, waveInvoiceId: '' } }, + { event, metadata: { cookie: locals.cookie } } + ); + + if (updateResult.errors?.length) { + return fail(400, { + error: `Wave invoice deleted but failed to unlink from Nexus: ${updateResult.errors.map((e) => e.message).join(', ')}` + }); + } + + return { success: true }; + } +}; diff --git a/src/routes/admin/invoices/[invoice]/+page.svelte b/src/routes/admin/invoices/[invoice]/+page.svelte new file mode 100644 index 0000000..a689c2b --- /dev/null +++ b/src/routes/admin/invoices/[invoice]/+page.svelte @@ -0,0 +1,1017 @@ + + +{#snippet editFormContent()} + +{/snippet} + +{#snippet linkCustomerContent()} + {@const customer = customers.find((c) => fromGlobalId(c.id) === invoice.customerId)} + {#if customer} + + {/if} +{/snippet} + + + Invoice - {customerName} - Admin - Nexus + + +
+ + + + +
+
+
+ + {getStatusLabel(invoice.status)} + + {#if invoice.waveInvoiceId} + Linked + {/if} +
+ +
+ + + + + +
+
+
+ + + (activeTab = tab)} + /> + + {#if activeTab === 'details'} + +
+

Invoice Summary

+ +
+ +
+
+
+ + + +
+
+

Revenues

+

+ {invoice.revenues.length} item{invoice.revenues.length !== 1 ? 's' : ''} +

+
+
+

+ {formatCurrency(revenuesTotal)} +

+
+ + +
+
+
+ + + +
+
+

Projects

+

+ {invoice.projects.length} item{invoice.projects.length !== 1 ? 's' : ''} +

+
+
+

+ {formatCurrency(projectsTotal)} +

+
+ + +
+
+
+ + + +
+
+

Invoice Total

+

Combined total

+
+
+

+ {formatCurrency(invoiceTotal)} +

+
+
+
+ + +
+
+

Revenues

+ +
+ + {#if invoice.revenues.length === 0} +

No revenues added to this invoice.

+ {:else} +
+ {#each sortedRevenues as revenue (revenue.id)} +
+
+
+

+ {getAccountNameForRevenue(revenue.accountId)} +

+

+ {formatShortDate(revenue.startDate)} + {#if revenue.endDate} + - {formatShortDate(revenue.endDate)} + {:else} + - Ongoing + {/if} +

+
+
+ {#if revenue.waveServiceId} + Linked + {/if} + + {formatCurrency(revenue.amount)} + + +
+
+
+ {/each} +
+ {/if} +
+ + +
+
+

Projects

+ +
+ + {#if invoice.projects.length === 0} +

No projects added to this invoice.

+ {:else} +
+ {#each sortedProjects as project (project.id)} +
+
+
+

{getProjectDisplayName(project)}

+

{formatShortDate(project.date)}

+
+
+ {#if project.waveServiceId} + Linked + {/if} + + {formatCurrency(project.amount)} + + +
+
+
+ {/each} +
+ {/if} +
+ + +
+

Invoice Details

+ +
+ +
+
+
+ + + +
+
+

Customer

+

{customerName}

+
+
+
+ + +
+
+
+ + + +
+
+

Invoice Date

+

{formatDate(invoice.date)}

+
+
+
+ + + {#if invoice.status === 'PAID'} +
+
+
+ + + +
+
+

Date Paid

+

+ {formatDate(invoice.datePaid)} +

+
+
+
+ +
+
+
+ + + +
+
+

Payment Method

+

{getPaymentTypeLabel(invoice.paymentType)}

+
+
+
+ {/if} +
+
+ + +
+

Danger Zone

+

+ Deleting this invoice will permanently remove it. This action cannot be undone. +

+ +
+ {:else if activeTab === 'wave'} + + {#if hasWaveInvoice && waveInvoice} + +
{ + isApprovingWaveInvoice = true; + return async ({ result }) => { + isApprovingWaveInvoice = false; + if (result.type === 'failure') { + const errorMsg = result.data?.error; + alert(typeof errorMsg === 'string' ? errorMsg : 'Failed to approve invoice'); + } else if (result.type === 'success') { + await invalidateAll(); + } + }; + }} + class="hidden" + > + +
+ + +
{ + isSendingWaveInvoice = true; + return async ({ result }) => { + isSendingWaveInvoice = false; + if (result.type === 'failure') { + const errorMsg = result.data?.error; + alert(typeof errorMsg === 'string' ? errorMsg : 'Failed to send invoice'); + } else if (result.type === 'success') { + await invalidateAll(); + } + }; + }} + class="hidden" + > + + + +
+ + +
{ + isDeletingWaveInvoice = true; + return async ({ result }) => { + isDeletingWaveInvoice = false; + if (result.type === 'failure') { + const errorMsg = result.data?.error; + alert(typeof errorMsg === 'string' ? errorMsg : 'Failed to delete invoice'); + } else if (result.type === 'success') { + await invalidateAll(); + } + }; + }} + class="hidden" + > + + +
+ + {#if isEditingWaveInvoice} + + + {:else} + + + {/if} + {:else if showWaveInvoiceForm} + + + {:else} + + + {/if} + {/if} +
+
+ + {}} + loading={isDeleting} +/> + + + + + + +{#if linkingEntity} + +{/if} diff --git a/src/routes/admin/invoices/wave/+page.server.ts b/src/routes/admin/invoices/wave/+page.server.ts new file mode 100644 index 0000000..7484d58 --- /dev/null +++ b/src/routes/admin/invoices/wave/+page.server.ts @@ -0,0 +1,593 @@ +import { WAVE_ACCESS_TOKEN } from '$env/static/private'; +import { PUBLIC_WAVE_BUSINESS_ID } from '$env/static/public'; +import { fail } from '@sveltejs/kit'; +import { + createWaveProduct, + updateWaveProduct, + archiveWaveProduct, + createWaveCustomer, + updateWaveCustomer, + deleteWaveCustomer +} from '$lib/graphql/wave/mutations'; +import type { PageServerLoad, Actions } from './$types'; + +const WAVE_API_URL = 'https://gql.waveapps.com/graphql/public'; + +const GET_ALL_DATA_QUERY = ` + query GetAllWaveData($businessId: ID!, $invoicePage: Int, $invoicePageSize: Int, $productPageSize: Int, $customerPageSize: Int) { + business(id: $businessId) { + id + name + accounts(types: [INCOME], isArchived: false) { + edges { + node { + id + name + subtype { + value + } + } + } + } + invoices(page: $invoicePage, pageSize: $invoicePageSize, sort: [INVOICE_DATE_DESC]) { + edges { + node { + id + invoiceNumber + invoiceDate + dueDate + status + title + customer { + id + name + email + } + amountDue { + value + currency { + code + symbol + } + } + amountPaid { + value + currency { + code + symbol + } + } + total { + value + currency { + code + symbol + } + } + lastSentAt + lastViewedAt + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + products(pageSize: $productPageSize, sort: [NAME_ASC], isArchived: false) { + edges { + node { + id + name + description + unitPrice + isSold + isBought + isArchived + incomeAccount { + id + name + } + expenseAccount { + id + name + } + defaultSalesTaxes { + id + name + abbreviation + } + createdAt + modifiedAt + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + archivedProducts: products(pageSize: $productPageSize, sort: [NAME_ASC], isArchived: true) { + edges { + node { + id + name + description + unitPrice + isSold + isBought + isArchived + incomeAccount { + id + name + } + expenseAccount { + id + name + } + defaultSalesTaxes { + id + name + abbreviation + } + createdAt + modifiedAt + } + } + } + customers(pageSize: $customerPageSize, sort: [NAME_ASC]) { + edges { + node { + id + name + firstName + lastName + email + phone + mobile + currency { + code + symbol + } + address { + addressLine1 + addressLine2 + city + province { + code + name + } + country { + code + name + } + postalCode + } + internalNotes + outstandingAmount { + value + currency { + code + } + } + overdueAmount { + value + currency { + code + } + } + createdAt + modifiedAt + isArchived + } + } + pageInfo { + currentPage + totalPages + totalCount + } + } + } + } +`; + +interface WaveInvoice { + id: string; + invoiceNumber: string; + invoiceDate: string; + dueDate: string; + status: string; + title: string | null; + customer: { + id: string; + name: string; + email: string | null; + }; + amountDue: { + value: string; + currency: { + code: string; + symbol: string; + }; + }; + amountPaid: { + value: string; + currency: { + code: string; + symbol: string; + }; + }; + total: { + value: string; + currency: { + code: string; + symbol: string; + }; + }; + lastSentAt: string | null; + lastViewedAt: string | null; +} + +interface WaveProduct { + id: string; + name: string; + description: string | null; + unitPrice: string; + isSold: boolean; + isBought: boolean; + isArchived: boolean; + incomeAccount: { id: string; name: string } | null; + expenseAccount: { id: string; name: string } | null; + defaultSalesTaxes: Array<{ id: string; name: string; abbreviation: string }>; + createdAt: string; + modifiedAt: string; +} + +interface WaveCustomer { + id: string; + name: string; + firstName: string | null; + lastName: string | null; + email: string | null; + phone: string | null; + mobile: string | null; + currency: { code: string; symbol: string } | null; + address: { + addressLine1: string | null; + addressLine2: string | null; + city: string | null; + province: { code: string; name: string } | null; + country: { code: string; name: string } | null; + postalCode: string | null; + } | null; + internalNotes: string | null; + outstandingAmount: { value: string; currency: { code: string } }; + overdueAmount: { value: string; currency: { code: string } }; + createdAt: string; + modifiedAt: string; + isArchived: boolean | null; +} + +interface WaveAccount { + id: string; + name: string; + subtype: { value: string }; +} + +interface WaveResponse { + data?: { + business: { + id: string; + name: string; + accounts: { + edges: Array<{ node: WaveAccount }>; + }; + invoices: { + edges: Array<{ node: WaveInvoice }>; + pageInfo: { + currentPage: number; + totalPages: number; + totalCount: number; + }; + }; + products: { + edges: Array<{ node: WaveProduct }>; + pageInfo: { + currentPage: number; + totalPages: number; + totalCount: number; + }; + }; + archivedProducts: { + edges: Array<{ node: WaveProduct }>; + }; + customers: { + edges: Array<{ node: WaveCustomer }>; + pageInfo: { + currentPage: number; + totalPages: number; + totalCount: number; + }; + }; + }; + }; + errors?: Array<{ message: string }>; +} + +export const load: PageServerLoad = async ({ url }) => { + const page = parseInt(url.searchParams.get('page') ?? '1', 10); + const pageSize = 20; + + try { + const response = await fetch(WAVE_API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${WAVE_ACCESS_TOKEN}` + }, + body: JSON.stringify({ + query: GET_ALL_DATA_QUERY, + variables: { + businessId: PUBLIC_WAVE_BUSINESS_ID, + invoicePage: page, + invoicePageSize: pageSize, + productPageSize: 100, + customerPageSize: 100 + } + }) + }); + + const result: WaveResponse = await response.json(); + + if (result.errors) { + console.error('Wave API errors:', result.errors); + return { + invoices: [], + products: [], + archivedProducts: [], + customers: [], + incomeAccounts: [], + invoicePageInfo: { currentPage: 1, totalPages: 1, totalCount: 0 }, + productPageInfo: { currentPage: 1, totalPages: 1, totalCount: 0 }, + customerPageInfo: { currentPage: 1, totalPages: 1, totalCount: 0 }, + businessName: null, + error: result.errors[0]?.message ?? 'Unknown error' + }; + } + + const business = result.data?.business; + + // Extract income accounts + const incomeAccounts = business?.accounts.edges.map((e) => e.node) ?? []; + + // Transform products to have unitPrice as number + const products = (business?.products.edges.map((e) => e.node) ?? []).map((p) => ({ + ...p, + unitPrice: parseFloat(p.unitPrice) + })); + + // Transform archived products to have unitPrice as number + const archivedProducts = (business?.archivedProducts.edges.map((e) => e.node) ?? []).map( + (p) => ({ + ...p, + unitPrice: parseFloat(p.unitPrice) + }) + ); + + // Filter out archived customers + const customers = (business?.customers.edges.map((e) => e.node) ?? []).filter( + (c) => !c.isArchived + ); + + return { + invoices: business?.invoices.edges.map((e) => e.node) ?? [], + products, + archivedProducts, + customers, + incomeAccounts, + invoicePageInfo: business?.invoices.pageInfo ?? { + currentPage: 1, + totalPages: 1, + totalCount: 0 + }, + productPageInfo: business?.products.pageInfo ?? { + currentPage: 1, + totalPages: 1, + totalCount: 0 + }, + customerPageInfo: business?.customers.pageInfo ?? { + currentPage: 1, + totalPages: 1, + totalCount: 0 + }, + businessName: business?.name ?? null, + error: null + }; + } catch (err) { + console.error('Wave API fetch error:', err); + return { + invoices: [], + products: [], + archivedProducts: [], + customers: [], + incomeAccounts: [], + invoicePageInfo: { currentPage: 1, totalPages: 1, totalCount: 0 }, + productPageInfo: { currentPage: 1, totalPages: 1, totalCount: 0 }, + customerPageInfo: { currentPage: 1, totalPages: 1, totalCount: 0 }, + businessName: null, + error: err instanceof Error ? err.message : 'Failed to fetch data' + }; + } +}; + +export const actions: Actions = { + // Product actions + createProduct: async ({ request }) => { + const formData = await request.formData(); + const name = formData.get('name') as string; + const unitPrice = parseFloat(formData.get('unitPrice') as string); + const description = (formData.get('description') as string) || undefined; + const incomeAccountId = (formData.get('incomeAccountId') as string) || undefined; + + if (!name || isNaN(unitPrice)) { + return fail(400, { error: 'Name and unit price are required' }); + } + + const result = await createWaveProduct({ name, unitPrice, description, incomeAccountId }); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to create product' }); + } + + return { success: true, product: result.product }; + }, + + updateProduct: async ({ request }) => { + const formData = await request.formData(); + const id = formData.get('id') as string; + const name = (formData.get('name') as string) || undefined; + const unitPriceStr = formData.get('unitPrice') as string; + const unitPrice = unitPriceStr ? parseFloat(unitPriceStr) : undefined; + const description = (formData.get('description') as string) || undefined; + const incomeAccountId = (formData.get('incomeAccountId') as string) || undefined; + + if (!id) { + return fail(400, { error: 'Product ID is required' }); + } + + const result = await updateWaveProduct({ id, name, unitPrice, description, incomeAccountId }); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to update product' }); + } + + return { success: true, product: result.product }; + }, + + archiveProduct: async ({ request }) => { + const formData = await request.formData(); + const id = formData.get('id') as string; + + if (!id) { + return fail(400, { error: 'Product ID is required' }); + } + + const result = await archiveWaveProduct(id); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to archive product' }); + } + + return { success: true }; + }, + + // Customer actions + createCustomer: async ({ request }) => { + const formData = await request.formData(); + const name = formData.get('name') as string; + const firstName = (formData.get('firstName') as string) || undefined; + const lastName = (formData.get('lastName') as string) || undefined; + const email = (formData.get('email') as string) || undefined; + const phone = (formData.get('phone') as string) || undefined; + const internalNotes = (formData.get('internalNotes') as string) || undefined; + + // Address fields + const addressLine1 = (formData.get('addressLine1') as string) || undefined; + const addressLine2 = (formData.get('addressLine2') as string) || undefined; + const city = (formData.get('city') as string) || undefined; + const provinceCode = (formData.get('provinceCode') as string) || undefined; + const countryCode = (formData.get('countryCode') as string) || undefined; + const postalCode = (formData.get('postalCode') as string) || undefined; + + if (!name) { + return fail(400, { error: 'Name is required' }); + } + + // Build address object if any address fields are provided + const hasAddressFields = + addressLine1 || addressLine2 || city || provinceCode || countryCode || postalCode; + const address = hasAddressFields + ? { addressLine1, addressLine2, city, provinceCode, countryCode, postalCode } + : undefined; + + const result = await createWaveCustomer({ + name, + firstName, + lastName, + email, + phone, + address, + internalNotes + }); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to create customer' }); + } + + return { success: true, customer: result.customer }; + }, + + updateCustomer: async ({ request }) => { + const formData = await request.formData(); + const id = formData.get('id') as string; + const name = (formData.get('name') as string) || undefined; + const firstName = (formData.get('firstName') as string) || undefined; + const lastName = (formData.get('lastName') as string) || undefined; + const email = (formData.get('email') as string) || undefined; + const phone = (formData.get('phone') as string) || undefined; + const internalNotes = (formData.get('internalNotes') as string) || undefined; + + // Address fields + const addressLine1 = (formData.get('addressLine1') as string) || undefined; + const addressLine2 = (formData.get('addressLine2') as string) || undefined; + const city = (formData.get('city') as string) || undefined; + const provinceCode = (formData.get('provinceCode') as string) || undefined; + const countryCode = (formData.get('countryCode') as string) || undefined; + const postalCode = (formData.get('postalCode') as string) || undefined; + + if (!id) { + return fail(400, { error: 'Customer ID is required' }); + } + + // Build address object if any address fields are provided + const hasAddressFields = + addressLine1 || addressLine2 || city || provinceCode || countryCode || postalCode; + const address = hasAddressFields + ? { addressLine1, addressLine2, city, provinceCode, countryCode, postalCode } + : undefined; + + const result = await updateWaveCustomer({ + id, + name, + firstName, + lastName, + email, + phone, + address, + internalNotes + }); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to update customer' }); + } + + return { success: true, customer: result.customer }; + }, + + deleteCustomer: async ({ request }) => { + const formData = await request.formData(); + const id = formData.get('id') as string; + + if (!id) { + return fail(400, { error: 'Customer ID is required' }); + } + + const result = await deleteWaveCustomer(id); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to delete customer' }); + } + + return { success: true }; + } +}; diff --git a/src/routes/admin/invoices/wave/+page.svelte b/src/routes/admin/invoices/wave/+page.svelte new file mode 100644 index 0000000..f20f111 --- /dev/null +++ b/src/routes/admin/invoices/wave/+page.svelte @@ -0,0 +1,508 @@ + + + + Wave - Admin - Nexus + + + +
{ + isDeleting = true; + return async ({ result, update }) => { + isDeleting = false; + if (result.type === 'success') { + showDeleteModal = false; + deleteItem = null; + await update({ reset: false }); + } else if (result.type === 'failure') { + showDeleteModal = false; + deleteItem = null; + } + }; + }} + class="hidden" +> + +
+ + + + +
+ + +
+
+
+ + + + + +
+

+ Wave +

+

+ {#if data.businessName} + Managing {data.businessName} + {:else} + Manage your Wave accounting data + {/if} +

+
+
+
+
+ + + {#if data.error} +
+
+ + + +
+

Failed to load Wave data

+

{data.error}

+
+
+
+ {/if} + + + (activeTab = tab)} + /> + + + {#if activeTab === 'invoices'} + + {#if invoices.length > 0 || searchQuery || selectedStatus} +
+
+ + + + +
+ + +
+ {/if} + + + {#if filteredInvoices.length > 0} + + + + {#if invoicePageInfo.totalPages > 1} +
+ {#if invoicePageInfo.currentPage > 1} + + Previous + + {/if} + + + Page {invoicePageInfo.currentPage} of {invoicePageInfo.totalPages} + + + {#if invoicePageInfo.currentPage < invoicePageInfo.totalPages} + + Next + + {/if} +
+ {/if} + {:else if searchQuery || selectedStatus} +
+ + + +

No Invoices Found

+

+ No invoices match your search criteria. Try adjusting your filters. +

+
+ {:else if !data.error} +
+ + + +

No Wave Invoices

+

+ There are no invoices in your Wave account yet. Create invoices in Wave to see them + here. +

+
+ {/if} + {:else if activeTab === 'products'} + + {:else if activeTab === 'customers'} + + {/if} +
+
diff --git a/src/routes/admin/invoices/wave/[invoice]/+page.server.ts b/src/routes/admin/invoices/wave/[invoice]/+page.server.ts new file mode 100644 index 0000000..e1824f0 --- /dev/null +++ b/src/routes/admin/invoices/wave/[invoice]/+page.server.ts @@ -0,0 +1,468 @@ +import { WAVE_ACCESS_TOKEN } from '$env/static/private'; +import { PUBLIC_WAVE_BUSINESS_ID } from '$env/static/public'; +import { error, fail } from '@sveltejs/kit'; +import { + deleteWaveInvoice, + sendWaveInvoice, + updateWaveInvoice, + approveWaveInvoice +} from '$lib/graphql/wave/mutations'; +import type { PageServerLoad, Actions } from './$types'; + +const WAVE_API_URL = 'https://gql.waveapps.com/graphql/public'; + +const GET_PRODUCTS_QUERY = ` + query GetProducts($businessId: ID!, $pageSize: Int!) { + business(id: $businessId) { + products(page: 1, pageSize: $pageSize, isSold: true, isArchived: false) { + edges { + node { + id + name + description + unitPrice + isArchived + } + } + } + } + } +`; + +interface WaveProduct { + id: string; + name: string; + description: string | null; + unitPrice: number; + isArchived: boolean; +} + +const GET_INVOICE_QUERY = ` + query GetInvoice($businessId: ID!, $invoiceId: ID!) { + business(id: $businessId) { + id + invoice(id: $invoiceId) { + id + invoiceNumber + invoiceDate + dueDate + status + title + subhead + poNumber + memo + footer + pdfUrl + viewUrl + customer { + id + name + firstName + lastName + email + address { + addressLine1 + addressLine2 + city + province { + code + name + } + country { + code + name + } + postalCode + } + } + items { + product { + id + name + } + description + quantity + unitPrice + subtotal { + value + currency { + code + } + } + total { + value + currency { + code + } + } + taxes { + salesTax { + id + name + abbreviation + } + amount { + value + currency { + code + } + } + } + } + discounts { + ... on FixedInvoiceDiscount { + name + amount + } + ... on PercentageInvoiceDiscount { + name + percentage + } + } + subtotal { + value + currency { + code + symbol + } + } + taxTotal { + value + currency { + code + symbol + } + } + discountTotal { + value + currency { + code + symbol + } + } + total { + value + currency { + code + symbol + } + } + amountDue { + value + currency { + code + symbol + } + } + amountPaid { + value + currency { + code + symbol + } + } + currency { + code + symbol + name + } + exchangeRate + createdAt + modifiedAt + lastSentAt + lastSentVia + lastViewedAt + } + } + } +`; + +interface WaveInvoiceItem { + product: { + id: string; + name: string; + } | null; + description: string | null; + quantity: string; + unitPrice: string; + subtotal: { + value: string; + currency: { code: string }; + }; + total: { + value: string; + currency: { code: string }; + }; + taxes: Array<{ + salesTax: { + id: string; + name: string; + abbreviation: string; + }; + amount: { + value: string; + currency: { code: string }; + }; + }>; +} + +interface WaveInvoiceDiscount { + name: string; + amount?: string; + percentage?: number; +} + +interface WaveInvoice { + id: string; + invoiceNumber: string; + invoiceDate: string; + dueDate: string; + status: string; + title: string | null; + subhead: string | null; + poNumber: string | null; + memo: string | null; + footer: string | null; + pdfUrl: string | null; + viewUrl: string | null; + customer: { + id: string; + name: string; + firstName: string | null; + lastName: string | null; + email: string | null; + address: { + addressLine1: string | null; + addressLine2: string | null; + city: string | null; + province: { code: string; name: string } | null; + country: { code: string; name: string } | null; + postalCode: string | null; + } | null; + }; + items: WaveInvoiceItem[]; + discounts: WaveInvoiceDiscount[]; + subtotal: { value: string; currency: { code: string; symbol: string } }; + taxTotal: { value: string; currency: { code: string; symbol: string } }; + discountTotal: { value: string; currency: { code: string; symbol: string } }; + total: { value: string; currency: { code: string; symbol: string } }; + amountDue: { value: string; currency: { code: string; symbol: string } }; + amountPaid: { value: string; currency: { code: string; symbol: string } }; + currency: { code: string; symbol: string; name: string }; + exchangeRate: number | null; + createdAt: string; + modifiedAt: string; + lastSentAt: string | null; + lastSentVia: string | null; + lastViewedAt: string | null; +} + +interface WaveResponse { + data?: { + business: { + id: string; + invoice: WaveInvoice | null; + }; + }; + errors?: Array<{ message: string }>; +} + +async function fetchWaveProducts(): Promise { + try { + const response = await fetch(WAVE_API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${WAVE_ACCESS_TOKEN}` + }, + body: JSON.stringify({ + query: GET_PRODUCTS_QUERY, + variables: { + businessId: PUBLIC_WAVE_BUSINESS_ID, + pageSize: 500 + } + }) + }); + + const result = await response.json(); + if (result.errors) { + console.error('Wave API errors (products):', result.errors); + return []; + } + + const edges = result.data?.business?.products?.edges ?? []; + return edges + .filter((edge: { node: WaveProduct | null }) => edge.node != null) + .map((edge: { node: WaveProduct }) => ({ + id: edge.node.id, + name: edge.node.name, + description: edge.node.description, + unitPrice: parseFloat(String(edge.node.unitPrice)), + isArchived: edge.node.isArchived + })); + } catch (err) { + console.error('Wave API fetch error (products):', err); + return []; + } +} + +export const load: PageServerLoad = async ({ params }) => { + const invoiceId = params.invoice; + + // Fetch invoice and products in parallel + const [invoiceResponse, waveProducts] = await Promise.all([ + fetch(WAVE_API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${WAVE_ACCESS_TOKEN}` + }, + body: JSON.stringify({ + query: GET_INVOICE_QUERY, + variables: { + businessId: PUBLIC_WAVE_BUSINESS_ID, + invoiceId + } + }) + }).catch((err) => { + console.error('Wave API fetch error:', err); + throw error(500, err instanceof Error ? err.message : 'Failed to fetch invoice'); + }), + fetchWaveProducts() + ]); + + const result: WaveResponse = await invoiceResponse.json(); + + if (result.errors) { + console.error('Wave API errors:', result.errors); + throw error(500, result.errors[0]?.message ?? 'Failed to fetch invoice'); + } + + const invoice = result.data?.business?.invoice; + + if (!invoice) { + throw error(404, 'Invoice not found'); + } + + return { + invoice, + waveProducts + }; +}; + +export const actions: Actions = { + sendWaveInvoice: async ({ request }) => { + const formData = await request.formData(); + const invoiceId = formData.get('invoiceId') as string; + const customerEmail = formData.get('customerEmail') as string; + + if (!invoiceId) { + return fail(400, { error: 'Missing invoice ID' }); + } + + if (!customerEmail) { + return fail(400, { error: 'Customer email is required to send invoice' }); + } + + const result = await sendWaveInvoice(invoiceId, [customerEmail]); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to send Wave invoice' }); + } + + return { success: true }; + }, + + approveWaveInvoice: async ({ request }) => { + const formData = await request.formData(); + const invoiceId = formData.get('invoiceId') as string; + + if (!invoiceId) { + return fail(400, { error: 'Missing invoice ID' }); + } + + const result = await approveWaveInvoice(invoiceId); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to approve Wave invoice' }); + } + + return { success: true }; + }, + + updateWaveInvoice: async ({ request }) => { + const formData = await request.formData(); + const invoiceId = formData.get('invoiceId') as string; + const invoiceDate = formData.get('invoiceDate') as string; + const dueDate = formData.get('dueDate') as string; + const memo = formData.get('memo') as string | null; + const footer = formData.get('footer') as string | null; + const itemsJson = formData.get('items') as string; + const discountJson = formData.get('discount') as string | null; + + if (!invoiceId || !itemsJson) { + return fail(400, { error: 'Missing required fields' }); + } + + let items; + try { + items = JSON.parse(itemsJson); + } catch { + return fail(400, { error: 'Invalid items data' }); + } + + // Parse discount if provided + let discounts; + if (discountJson) { + try { + const discount = JSON.parse(discountJson); + if (discount && discount.value > 0) { + discounts = [ + { + discountType: discount.type as 'PERCENTAGE' | 'FIXED', + name: discount.name || 'Discount', + ...(discount.type === 'PERCENTAGE' + ? { percentage: discount.value.toString() } + : { amount: discount.value.toString() }) + } + ]; + } + } catch { + // Ignore invalid discount data + } + } + + const result = await updateWaveInvoice({ + id: invoiceId, + invoiceDate, + dueDate, + items, + discounts, + memo: memo || undefined, + footer: footer || undefined + }); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to update Wave invoice' }); + } + + return { success: true }; + }, + + deleteWaveInvoice: async ({ request }) => { + const formData = await request.formData(); + const invoiceId = formData.get('invoiceId') as string; + + if (!invoiceId) { + return fail(400, { error: 'Missing invoice ID' }); + } + + const result = await deleteWaveInvoice(invoiceId); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to delete Wave invoice' }); + } + + return { success: true, deleted: true }; + } +}; diff --git a/src/routes/admin/invoices/wave/[invoice]/+page.svelte b/src/routes/admin/invoices/wave/[invoice]/+page.svelte new file mode 100644 index 0000000..1ed401c --- /dev/null +++ b/src/routes/admin/invoices/wave/[invoice]/+page.svelte @@ -0,0 +1,958 @@ + + + + Invoice #{invoice.invoiceNumber} - Wave - Admin - Nexus + + +
+ + + + +
{ + isApproving = true; + return async ({ result }) => { + isApproving = false; + if (result.type === 'failure') { + const errorMsg = result.data?.error; + alert(typeof errorMsg === 'string' ? errorMsg : 'Failed to approve invoice'); + } else if (result.type === 'success') { + await invalidateAll(); + } + }; + }} + class="hidden" + > + +
+ + +
{ + isSending = true; + return async ({ result }) => { + isSending = false; + if (result.type === 'failure') { + const errorMsg = result.data?.error; + alert(typeof errorMsg === 'string' ? errorMsg : 'Failed to send invoice'); + } else if (result.type === 'success') { + await invalidateAll(); + } + }; + }} + class="hidden" + > + + +
+ + +
{ + isDeleting = true; + return async ({ result }) => { + isDeleting = false; + if (result.type === 'failure') { + const errorMsg = result.data?.error; + alert(typeof errorMsg === 'string' ? errorMsg : 'Failed to delete invoice'); + } else if (result.type === 'success') { + await goto('/admin/invoices/wave'); + } + }; + }} + class="hidden" + > + +
+ + {#if isEditing} + + + {:else} + +
+
+
+ + {overdue && invoice.status !== 'PAID' ? 'Overdue' : getStatusLabel(invoice.status)} + + + {#if invoice.lastViewedAt} + + + + + + Viewed + + {/if} +
+ +
+ {#if invoice.pdfUrl} + + + + + Download PDF + + {/if} + + {#if canEdit} + + {/if} + + {#if canApprove} + + {/if} + + {#if canSend} + + {/if} + + {#if canDelete} + + {/if} +
+
+
+ + +
+

Invoice Summary

+ +
+ +
+
+
+ + + +
+
+

Subtotal

+

+ {invoice.items.length} line item{invoice.items.length !== 1 ? 's' : ''} +

+
+
+

+ {formatCurrency(invoice.subtotal.value, invoice.subtotal.currency.symbol)} +

+
+ + + {#if parseFloat(invoice.discountTotal.value) > 0} +
+
+
+ + + +
+
+

Discounts

+

+ {invoice.discounts.length} discount{invoice.discounts.length !== 1 ? 's' : ''} +

+
+
+

+ -{formatCurrency( + invoice.discountTotal.value, + invoice.discountTotal.currency.symbol + )} +

+
+ {/if} + + + {#if parseFloat(invoice.taxTotal.value) > 0} +
+
+
+ + + +
+
+

Tax

+

Sales tax

+
+
+

+ {formatCurrency(invoice.taxTotal.value, invoice.taxTotal.currency.symbol)} +

+
+ {/if} + + +
+
+
+ + + +
+
+

Total

+

{invoice.currency.name}

+
+
+

+ {formatCurrency(invoice.total.value, invoice.total.currency.symbol)} +

+
+ + + {#if parseFloat(invoice.amountPaid.value) > 0} +
+
+
+ + + +
+
+

Amount Paid

+

Received payments

+
+
+

+ {formatCurrency(invoice.amountPaid.value, invoice.amountPaid.currency.symbol)} +

+
+ {/if} + + + {#if parseFloat(invoice.amountDue.value) > 0} +
+
+
+ + + +
+
+

Amount Due

+

+ {overdue ? 'Overdue' : `Due ${formatShortDate(invoice.dueDate)}`} +

+
+
+

+ {formatCurrency(invoice.amountDue.value, invoice.amountDue.currency.symbol)} +

+
+ {/if} +
+
+ + + {#if invoice.items.length > 0} +
+

Line Items

+ +
+ {#each invoice.items as item, index (index)} +
+
+
+

+ {item.product?.name ?? item.description ?? 'Item'} +

+ {#if item.description && item.product?.name} +

{item.description}

+ {/if} +

+ {parseFloat(item.quantity)} x {formatCurrency( + item.unitPrice, + invoice.currency.symbol + )} +

+ {#if item.taxes.length > 0} +

+ Tax: {item.taxes.map((t) => t.salesTax.abbreviation).join(', ')} +

+ {/if} +
+
+ + {formatCurrency(item.total.value, invoice.currency.symbol)} + +
+
+
+ {/each} +
+
+ {/if} + + + {#if invoice.discounts.length > 0} +
+

Discounts

+ +
+ {#each invoice.discounts as discount, index (index)} +
+
+

{discount.name}

+ + {#if discount.amount} + -{formatCurrency(discount.amount, invoice.currency.symbol)} + {:else if discount.percentage} + -{discount.percentage}% + {/if} + +
+
+ {/each} +
+
+ {/if} + + +
+

Invoice Details

+ +
+ +
+
+
+ + + +
+
+

Customer

+

{invoice.customer.name}

+ {#if invoice.customer.email} +

{invoice.customer.email}

+ {/if} + {#if addressLines.length > 0} +
+ {#each addressLines as line (line)} +

{line}

+ {/each} +
+ {/if} +
+
+
+ + +
+
+
+ + + +
+
+

Invoice Date

+

{formatDate(invoice.invoiceDate)}

+
+
+
+ + +
+
+
+ + + +
+
+

Due Date

+

+ {formatDate(invoice.dueDate)} +

+
+
+
+ + + {#if invoice.poNumber} +
+
+
+ + + +
+
+

PO Number

+

{invoice.poNumber}

+
+
+
+ {/if} + + + {#if invoice.lastSentAt} +
+
+
+ + + +
+
+

Last Sent

+

+ {formatDateTime(invoice.lastSentAt)} + {#if invoice.lastSentVia} + via {invoice.lastSentVia} + {/if} +

+
+
+
+ {/if} + + + {#if invoice.lastViewedAt} +
+
+
+ + + + +
+
+

Last Viewed

+

+ {formatDateTime(invoice.lastViewedAt)} +

+
+
+
+ {/if} +
+
+ + + {#if invoice.memo || invoice.footer} +
+

Notes

+ + {#if invoice.memo} +
+

Memo

+

{invoice.memo}

+
+ {/if} + + {#if invoice.footer} +
+

Footer

+

{invoice.footer}

+
+ {/if} +
+ {/if} + {/if} +
+
diff --git a/src/routes/admin/notifications/+page.server.ts b/src/routes/admin/notifications/+page.server.ts new file mode 100644 index 0000000..3999ea3 --- /dev/null +++ b/src/routes/admin/notifications/+page.server.ts @@ -0,0 +1,30 @@ +import { AdminNotificationRulesStore, AdminProfilesStore } from '$houdini'; +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async (event) => { + const { parent } = event; + const parentData = await parent(); + const me = parentData.user?.data?.me; + + // Gate to ADMIN only + if (me?.__typename !== 'TeamProfileType' || me.role !== 'ADMIN') { + throw error(403, 'Admin access required'); + } + + const fetchParams = { + event, + metadata: { cookie: event.locals.cookie } + }; + + // Fetch notification rules and profiles in parallel + const [rules, profiles] = await Promise.all([ + new AdminNotificationRulesStore().fetch(fetchParams), + new AdminProfilesStore().fetch(fetchParams) + ]); + + return { + rules, + profiles + }; +}; diff --git a/src/routes/admin/notifications/+page.svelte b/src/routes/admin/notifications/+page.svelte new file mode 100644 index 0000000..b3d2674 --- /dev/null +++ b/src/routes/admin/notifications/+page.svelte @@ -0,0 +1,580 @@ + + +{#snippet createFormContent()} + +{/snippet} + +{#snippet editFormContent()} + {#if editingRule} + + {/if} +{/snippet} + +{#snippet cloneFormContent()} + {#if editingRule} + + {/if} +{/snippet} + + + Notification Rules - Admin - Nexus + + +
+ +
+
+
+ + + + + +
+

+ Notification Rules +

+

+ Configure when and how notifications are sent to users. +

+
+
+ +
+
+ + + +
+ + + +
+ + +
+
+ + + + +
+
+ + + {#if filteredRules.length > 0} +
+ {#each filteredRules as rule (rule.id)} +
+ +
+
+
+

+ {rule.name} +

+ + {rule.isActive ? 'Active' : 'Inactive'} + +
+ + {#if rule.description} +

{rule.description}

+ {/if} + + +
+ + + + + + {rule.eventTypes.length} event{rule.eventTypes.length !== 1 ? 's' : ''} + + + + {#each rule.channels as channel (channel)} + + {#if channel === 'IN_APP'} + + + + {:else if channel === 'EMAIL'} + + + + {:else} + + + + {/if} + {getChannelLabel(channel)} + + {/each} + + + {#if rule.targetRoles.length > 0} + + + + + {rule.targetRoles + .map((r) => + r === 'ADMIN' ? 'Admin' : r === 'TEAM_LEADER' ? 'Leader' : 'Member' + ) + .join(', ')} + + {/if} +
+
+ + +
+ + + + + + + + + + + +
+
+
+ {/each} +
+ {:else if searchQuery} +
+ + + +

No rules found

+

+ No notification rules match "{searchQuery}". Try a different search term. +

+
+ {:else} +
+ + + +

No notification rules yet

+

+ Get started by creating your first notification rule to automate alerts. +

+ +
+ {/if} +
+
+ + + + + {}} + loading={isDeleting} +/> diff --git a/src/routes/admin/notifications/[rule]/+page.server.ts b/src/routes/admin/notifications/[rule]/+page.server.ts new file mode 100644 index 0000000..9ac0a3e --- /dev/null +++ b/src/routes/admin/notifications/[rule]/+page.server.ts @@ -0,0 +1,36 @@ +import { AdminNotificationRuleStore, AdminProfilesStore } from '$houdini'; +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async (event) => { + const { parent } = event; + const parentData = await parent(); + const me = parentData.user?.data?.me; + + // Gate to ADMIN only + if (me?.__typename !== 'TeamProfileType' || me.role !== 'ADMIN') { + throw error(403, 'Admin access required'); + } + + const fetchParams = { + event, + metadata: { cookie: event.locals.cookie } + }; + + const [ruleResult, profiles] = await Promise.all([ + new AdminNotificationRuleStore().fetch({ + ...fetchParams, + variables: { id: event.params.rule } + }), + new AdminProfilesStore().fetch(fetchParams) + ]); + + if (!ruleResult.data?.notificationRule) { + throw error(404, 'Notification rule not found'); + } + + return { + rule: ruleResult, + profiles + }; +}; diff --git a/src/routes/admin/notifications/[rule]/+page.svelte b/src/routes/admin/notifications/[rule]/+page.svelte new file mode 100644 index 0000000..83730b7 --- /dev/null +++ b/src/routes/admin/notifications/[rule]/+page.svelte @@ -0,0 +1,481 @@ + + + + {rule?.name ?? 'Notification Rule'} - Admin - Nexus + + +
+ + {#if rule} + +
+
+
+ + + + + +
+
+

+ {rule.name} +

+ + {rule.isActive ? 'Active' : 'Inactive'} + +
+

+ Created {formatDate(rule.createdAt) || '—'} · Last updated {formatDate( + rule.updatedAt + ) || '—'} +

+
+
+ +
+
+ + + {#if error} +
{error}
+ {/if} + + {#if successMessage} +
+ {successMessage} +
+ {/if} + + +
+ +
+

Basic Information

+
+
+ + +
+ +
+ + +
+ +
+ +
+
+
+ + +
+

Trigger Events

+

+ Select which events will trigger this notification rule. +

+ +
+ + +
+

Delivery Channels

+

+ Choose how notifications will be delivered to recipients. +

+ +
+ + +
+

Recipients

+

+ Define who will receive notifications when this rule triggers. +

+ +
+ +
+ Target by Role +

+ Leave empty to notify all roles. Select specific roles to limit recipients. +

+
+ {#each roleOptions as role (role.value)} + + {/each} +
+ {#if targetRoles.length === 0} +

+ All team members will receive notifications +

+ {/if} +
+ + + + + + +
+
+ + +
+

Message Template

+

+ Customize the notification message. Use variables to include dynamic content. +

+ +
+ + +
+

Advanced Options

+
+ +

+ Add JSON conditions to filter events. Example: {conditionsExample} +

+ + {#if conditionsError} +

{conditionsError}

+ {/if} +
+
+ + +
+ + + Cancel + +
+
+ {:else} +
+

Rule not found

+
+ {/if} +
+
+ + {}} + loading={isDeleting} +/> diff --git a/src/routes/admin/profiles/+page.server.ts b/src/routes/admin/profiles/+page.server.ts new file mode 100644 index 0000000..66c11ba --- /dev/null +++ b/src/routes/admin/profiles/+page.server.ts @@ -0,0 +1,21 @@ +import { AdminProfilesStore } from '$houdini'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async (event) => { + const { parent } = event; + const fetchParams = { + event, + metadata: { cookie: event.locals.cookie } + }; + + // Fetch profiles and parent data in parallel + const [profiles, parentData] = await Promise.all([ + new AdminProfilesStore().fetch(fetchParams), + parent() + ]); + + return { + profiles, + customers: parentData.customers + }; +}; diff --git a/src/routes/admin/profiles/+page.svelte b/src/routes/admin/profiles/+page.svelte new file mode 100644 index 0000000..3772830 --- /dev/null +++ b/src/routes/admin/profiles/+page.svelte @@ -0,0 +1,530 @@ + + +{#snippet createTeamFormContent()} + +{/snippet} + +{#snippet createCustomerFormContent()} + +{/snippet} + +{#snippet editTeamFormContent()} + {#if editingTeamProfile} + + {/if} +{/snippet} + +{#snippet editCustomerFormContent()} + {#if editingCustomerProfile} + + {/if} +{/snippet} + + + Profiles - Admin - Nexus + + +
+ +
+
+
+ + + + + +
+

+ Profiles +

+

Manage team members and customer profiles.

+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + +
+
+ + + {#if activeTab === 'team'} + {#if filteredTeamProfiles.length > 0} +
+ {#each filteredTeamProfiles as profile (profile.id)} +
+
+
+

{profile.fullName}

+ + {getStatusLabel(profile.status)} + +
+

{getRoleLabel(profile.role)}

+ {#if profile.email} +

{profile.email}

+ {/if} + {#if profile.phone} +

{profile.phone}

+ {/if} +
+
+ + +
+
+ {/each} +
+ {:else if searchQuery} +
+ + + +

No team members found

+

+ No team members match "{searchQuery}". Try a different search term. +

+
+ {:else} +
+ + + +

No team members yet

+

Get started by adding your first team member.

+ +
+ {/if} + {:else} + + {#if filteredCustomerProfiles.length > 0} +
+ {#each filteredCustomerProfiles as profile (profile.id)} +
+
+
+

{profile.fullName}

+ + {getStatusLabel(profile.status)} + +
+ {#if profile.customers.length > 0} +

+ {profile.customers.map((c) => c.name).join(', ')} +

+ {:else} +

No linked customers

+ {/if} + {#if profile.email} +

{profile.email}

+ {/if} + {#if profile.phone} +

{profile.phone}

+ {/if} +
+
+ + +
+
+ {/each} +
+ {:else if searchQuery} +
+ + + +

No customer profiles found

+

+ No customer profiles match "{searchQuery}". Try a different search term. +

+
+ {:else} +
+ + + +

No customer profiles yet

+

Get started by adding your first customer profile.

+ +
+ {/if} + {/if} +
+
+ + + + + {}} + loading={isDeleting} +/> diff --git a/src/routes/admin/projects/+page.svelte b/src/routes/admin/projects/+page.svelte new file mode 100644 index 0000000..effa4b4 --- /dev/null +++ b/src/routes/admin/projects/+page.svelte @@ -0,0 +1,309 @@ + + +{#snippet createFormContent()} + +{/snippet} + +{#snippet emptyIcon()} + + + +{/snippet} + +{#snippet projectItem(project: Project)} + {@const displayInfo = getProjectDisplayInfo(project)} + {@const teamCount = countNonAdminTeamMembers(project.teamMembers)} + +
+
+
+ +
+ + {formatDate(project.date)} + + + {project.status.replace('_', ' ')} + + {#if isDispatched(project.teamMembers)} + + Dispatched + + {/if} + {#if hasNonAdminTeamMembers(project.teamMembers)} + + Assigned + + {/if} + {#if project.waveServiceId} + Linked + {/if} +
+ + {#if displayInfo.accountName} +

+ {displayInfo.accountName} +

+ {:else if displayInfo.customerName} +

+ {displayInfo.customerName} +

+ {:else} +

+ No account/customer linked +

+ {/if} + +

{project.name}

+ + {#if displayInfo.address} +

{displayInfo.address}

+ {/if} + +
+ {formatCurrency(project.amount)} + {teamCount} team member{teamCount !== 1 ? 's' : ''} +
+ + {#if project.notes} +

{project.notes}

+ {/if} +
+ + + +
+
+
+{/snippet} + + (searchQuery = q)} + searchPlaceholder="Search projects..." + createButtonText="New Project" + onCreateClick={showCreateForm} + {emptyIcon} + item={projectItem} +/> diff --git a/src/routes/admin/projects/[project]/+page.server.ts b/src/routes/admin/projects/[project]/+page.server.ts new file mode 100644 index 0000000..c96d11c --- /dev/null +++ b/src/routes/admin/projects/[project]/+page.server.ts @@ -0,0 +1,136 @@ +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { + GetProjectStore, + ProjectSessionByProjectStore, + ProjectScopeStore, + ProjectSessionPhotosStore, + ProjectSessionVideosStore +} from '$houdini'; +import type { ProjectSessionPhotos$result, ProjectSessionVideos$result } from '$houdini'; +import { fromGlobalId, toGlobalId } from '$lib/utils/relay'; + +type SessionPhoto = ProjectSessionPhotos$result['projectSessionImages'][number]; +type SessionVideo = ProjectSessionVideos$result['projectSessionVideos'][number]; +import { WAVE_ACCESS_TOKEN } from '$env/static/private'; +import { PUBLIC_WAVE_BUSINESS_ID } from '$env/static/public'; +import { createWaveServerClient } from '$lib/graphql/wave/client'; +import { GetProductsDocument, type ProductEdge, type Product } from '$lib/graphql/wave/generated'; + +export interface WaveProduct { + id: string; + name: string; + description: string | null; + unitPrice: number; + isArchived: boolean; +} + +async function fetchWaveProducts(): Promise { + const client = createWaveServerClient(WAVE_ACCESS_TOKEN); + + const result = await client.query(GetProductsDocument, { + businessId: PUBLIC_WAVE_BUSINESS_ID, + pageSize: 500, + isSold: true, + isArchived: false + }); + + if (result.error) { + console.error('Wave API error:', result.error); + return []; + } + + const edges = result.data?.business?.products?.edges ?? []; + return edges + .filter((edge: ProductEdge): edge is ProductEdge & { node: Product } => edge.node != null) + .map((edge: ProductEdge & { node: Product }) => ({ + id: edge.node.id, + name: edge.node.name, + description: edge.node.description ?? null, + unitPrice: edge.node.unitPrice, + isArchived: edge.node.isArchived + })); +} + +export const load: PageServerLoad = async (event) => { + const { params, locals, parent } = event; + const projectId = params.project; + const projectUuid = fromGlobalId(projectId); + + // Fetch params for Houdini stores + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + // Fetch project and Wave products in parallel (Wave is slow, start it early) + const [projectData, waveProducts] = await Promise.all([ + new GetProjectStore().fetch({ ...fetchParams, variables: { id: projectId } }), + fetchWaveProducts() + ]); + + const project = projectData?.data?.project; + if (!project) { + throw error(404, 'Project not found'); + } + + // Build parallel fetch promises based on project state + const needsSession = project.status === 'IN_PROGRESS' || project.status === 'COMPLETED'; + + // Fetch session and scope in parallel + const [sessionResult, scopeResult] = await Promise.all([ + // Session fetch (only if needed) + needsSession + ? new ProjectSessionByProjectStore() + .fetch({ ...fetchParams, variables: { projectId: projectUuid } }) + .catch(() => ({ data: { projectSessions: [] } })) + : Promise.resolve(null), + // Scope fetch (only if has scopeId) + project.scopeId + ? new ProjectScopeStore() + .fetch({ + ...fetchParams, + variables: { id: toGlobalId('ProjectScopeType', project.scopeId) } + }) + .catch(() => ({ data: { projectScope: null } })) + : Promise.resolve(null) + ]); + + const baseSession = sessionResult?.data?.projectSessions?.[0] ?? null; + let photos: SessionPhoto[] = []; + let videos: SessionVideo[] = []; + + // If we have a session, fetch photos and videos in parallel + if (baseSession) { + const sessionUuid = fromGlobalId(baseSession.id); + const [photosResult, videosResult] = await Promise.all([ + new ProjectSessionPhotosStore() + .fetch({ ...fetchParams, variables: { sessionId: sessionUuid } }) + .catch((err) => { + console.error('Failed to fetch session photos:', err); + return { data: { projectSessionImages: [] as SessionPhoto[] } }; + }), + new ProjectSessionVideosStore() + .fetch({ ...fetchParams, variables: { sessionId: sessionUuid } }) + .catch((err) => { + console.error('Failed to fetch session videos:', err); + return { data: { projectSessionVideos: [] as SessionVideo[] } }; + }) + ]); + + photos = photosResult?.data?.projectSessionImages ?? []; + videos = videosResult?.data?.projectSessionVideos ?? []; + } + + // Combine session with photos/videos + const session = baseSession ? { ...baseSession, photos, videos } : null; + + // Get project scope templates from parent layout (already fetched there) + const parentData = await parent(); + + return { + project, + projectUuid, + session, + scope: scopeResult?.data?.projectScope ?? null, + projectScopeTemplates: parentData.projectScopes, + waveProducts + }; +}; diff --git a/src/routes/admin/projects/[project]/+page.svelte b/src/routes/admin/projects/[project]/+page.svelte new file mode 100644 index 0000000..17276ea --- /dev/null +++ b/src/routes/admin/projects/[project]/+page.svelte @@ -0,0 +1,942 @@ + + +{#snippet editFormContent()} + +{/snippet} + +{#snippet scheduleEventContent()} + +{/snippet} + +{#snippet createScopeFormContent()} + +{/snippet} + +{#snippet editScopeFormContent()} + {#if formContext?.scope} + + {/if} +{/snippet} + +{#snippet addCategoryFormContent()} + {#if formContext?.scopeId} + + {/if} +{/snippet} + +{#snippet editCategoryFormContent()} + {#if formContext?.scopeId && formContext?.category} + + {/if} +{/snippet} + +{#snippet addTaskFormContent()} + {#if formContext?.categoryId} + + {/if} +{/snippet} + +{#snippet editTaskFormContent()} + {#if formContext?.categoryId && formContext?.task} + + {/if} +{/snippet} + + + {displayName} - Projects - Admin - Nexus + + +
+ + + + + + + +
+
+

Project Scope

+ {#if !scope && canEditScope && !scopeLoading} + (showScopeTemplateDropdown = !showScopeTemplateDropdown)} + onCreateBlank={showCreateScopeForm} + onCreateFromTemplate={handleCreateScopeFromTemplate} + /> + {/if} +
+ + + {#if !canEditScope && scope} +
+
+ + + +
+

Scope is locked

+

+ {#if project.status === 'IN_PROGRESS'} + The scope cannot be modified while the project session is active. Revert the + session first to make changes. + {:else} + The scope cannot be modified after the project has been completed. + {/if} +

+
+
+
+ {/if} + + {#if scopeLoading} +
+ + + + +

Creating scope from template...

+
+ {:else if scope} + + {:else} +
+ + + +

No Scope Defined

+

+ {#if canEditScope} + A scope with categories and tasks is required before starting a session. + {:else} + This project does not have a scope assigned. + {/if} +

+
+ {/if} +
+ + + {#if project.status === 'IN_PROGRESS' && session} +
+ + selectedTaskIds.clear()} + onAddNote={handleAddNote} + onUpdateNote={handleUpdateNote} + onDeleteNote={handleDeleteNote} + onUploadPhoto={handleUploadPhoto} + onUploadVideo={handleUploadVideo} + onUpdatePhoto={handleUpdatePhoto} + onUpdateVideo={handleUpdateVideo} + onDeletePhoto={handleDeletePhoto} + onDeleteVideo={handleDeleteVideo} + /> +
+ {:else if project.status === 'COMPLETED' && session} +
+ + +
+ {:else if project.status === 'SCHEDULED'} + {#if hasScopeWithTasks()} + goto('/admin/projects', { invalidateAll: true })} + /> + {:else} +
+
+ + + +
+

Scope Required

+

+ {#if !scope} + Create a project scope with categories and tasks before starting a session. + {:else} + Add at least one category with tasks to the scope before starting a session. + {/if} +

+
+
+ +
+ {/if} + {/if} +
+
+ + + (deleteScopeId = null)} +/> + + (deleteCategoryId = null)} +/> + + (deleteTaskId = null)} +/> diff --git a/src/routes/admin/reports/+page.svelte b/src/routes/admin/reports/+page.svelte new file mode 100644 index 0000000..5455c1b --- /dev/null +++ b/src/routes/admin/reports/+page.svelte @@ -0,0 +1,356 @@ + + +{#snippet createFormContent()} + +{/snippet} + + + Reports - Admin - Nexus + + +
+ + +
+
+
+ + + + + +
+

+ Reports +

+

+ Create and manage monthly labor reports for team members. +

+
+
+ +
+
+ + +
+ +
+ + + + +
+ + + +
+ + + {#if filteredReports.length > 0} + + {:else if searchQuery || selectedTeamMemberId} +
+ + + +

No Reports Found

+

+ No reports match your search criteria. Try adjusting your filters. +

+
+ {:else} +
+ + + +

No Reports Yet

+

+ Get started by creating your first labor report. Reports track team member earnings from + completed services and projects. +

+ +
+ {/if} +
+
+ + + diff --git a/src/routes/admin/reports/[report]/+page.server.ts b/src/routes/admin/reports/[report]/+page.server.ts new file mode 100644 index 0000000..161b3ab --- /dev/null +++ b/src/routes/admin/reports/[report]/+page.server.ts @@ -0,0 +1,30 @@ +import { GetReportStore } from '$houdini'; +import type { PageServerLoad } from './$types'; +import { error } from '@sveltejs/kit'; +import { fromGlobalId } from '$lib/utils/relay'; + +export const load: PageServerLoad = async (event) => { + const { report: reportId } = event.params; + + const fetchParams = { + event, + metadata: { cookie: event.locals.cookie } + }; + + // Fetch the report with full details + const report = await new GetReportStore().fetch({ + ...fetchParams, + variables: { + id: reportId + } + }); + + if (!report.data?.report) { + throw error(404, 'Report not found'); + } + + return { + report: report.data.report, + reportUuid: fromGlobalId(reportId) + }; +}; diff --git a/src/routes/admin/reports/[report]/+page.svelte b/src/routes/admin/reports/[report]/+page.svelte new file mode 100644 index 0000000..468fd75 --- /dev/null +++ b/src/routes/admin/reports/[report]/+page.svelte @@ -0,0 +1,502 @@ + + + + {reportTitle} Report - Admin - Nexus + + +
+ + + + +
+

Labor Summary

+ +
+ +
+
+
+ + + + +
+
+

Services

+

+ {report.services.length} service{report.services.length !== 1 ? 's' : ''} +

+
+
+

+ {formatCurrency(report.servicesLaborTotal)} +

+
+ + +
+
+
+ + + +
+
+

Projects

+

+ {report.projects.length} project{report.projects.length !== 1 ? 's' : ''} +

+
+
+

+ {formatCurrency(report.projectsLaborTotal)} +

+
+ + +
+
+
+ + + +
+
+

Total Labor

+

Combined earnings for the month

+
+
+

+ {formatCurrency(report.totalLaborValue)} +

+
+
+
+ + +
+
+

Services

+ +
+ + {#if report.services.length === 0} +

No services added to this report.

+ {:else} +
+ {#each report.services as service (service.id)} + {@const labor = serviceLaborMap.get(service.id)} +
+
+
+

{getServiceDisplayName(service)}

+ {#if labor} +
+ Total: {formatCurrency(labor.totalLaborRate)} + Team: {labor.teamMemberCount} + + Share: {formatCurrency(labor.laborShare)} + +
+ {/if} +
+ +
+
+ {/each} +
+ {/if} +
+ + +
+
+

Projects

+ +
+ + {#if report.projects.length === 0} +

No projects added to this report.

+ {:else} +
+ {#each report.projects as project (project.id)} + {@const labor = projectLaborMap.get(project.id)} +
+
+
+

{getProjectDisplayName(project)}

+ {#if labor} +
+ Total: {formatCurrency(labor.totalLaborAmount)} + Team: {labor.teamMemberCount} + + Share: {formatCurrency(labor.laborShare)} + +
+ {/if} +
+ +
+
+ {/each} +
+ {/if} +
+ + + {#if report.laborBreakdown && (report.laborBreakdown.services.length > 0 || report.laborBreakdown.projects.length > 0)} +
+

Labor Breakdown

+ + +
+
+
+ Services Total: + {formatCurrency(report.laborBreakdown.servicesTotal)} +
+
+ Projects Total: + {formatCurrency(report.laborBreakdown.projectsTotal)} +
+
+ Grand Total: + {formatCurrency(report.laborBreakdown.grandTotal)} +
+
+
+
+ {/if} + + +
+

Danger Zone

+

+ Deleting this report will permanently remove it. This action cannot be undone. +

+ +
+
+
+ + {}} + loading={isDeleting} +/> + + + + diff --git a/src/routes/admin/scopes/+page.svelte b/src/routes/admin/scopes/+page.svelte new file mode 100644 index 0000000..0e69ffc --- /dev/null +++ b/src/routes/admin/scopes/+page.svelte @@ -0,0 +1,1646 @@ + + + + + + Scope Templates - Admin - Nexus + + +
+ +
+
+
+ + + + + +
+

Scope Templates

+

Manage service and project scope templates

+
+
+ + + +
+
+ + + {#if error} +
+ {error} + +
+ {/if} + + +
+ +
+ + + {activeTab === 'service' ? 'Service Scopes' : 'Project Scopes'} + + + {activeTab === 'service' ? serviceTemplates.length : projectTemplates.length} + +
+ +
+ + +
+ {#if mobileView === 'editor' && (selectedServiceTemplateId || selectedProjectTemplateId)} + + {:else} + Select a template + {/if} +
+ + +
+
+ + + {#if isLoading} +
+
+ + + + + Loading templates... +
+
+ {:else} + + + + +
+ {#if mobileView === 'list'} + +
+ + {#if showNewTemplateInput} +
+ +
+ + +
+
+ {:else} + + {/if} + + {#if activeTab === 'service'} + {#each serviceTemplates as template (template.id)} + + {/each} + {#if serviceTemplates.length === 0 && !showNewTemplateInput} +
+

No service templates yet

+
+ {/if} + {:else} + {#each projectTemplates as template (template.id)} + + {/each} + {#if projectTemplates.length === 0 && !showNewTemplateInput} +
+

No project templates yet

+
+ {/if} + {/if} +
+ {:else} + +
+ {#if activeTab === 'service' && selectedServiceTemplate} + (editingTemplateName = selectedServiceTemplate.id)} + onCancelEditTemplateName={() => (editingTemplateName = null)} + onUpdateTemplateName={(name) => + handleUpdateServiceTemplate(selectedServiceTemplate.id, name)} + onUpdateTemplateDescription={(description) => + handleUpdateServiceTemplateDescription(selectedServiceTemplate.id, description)} + onDeleteTemplate={() => handleDeleteServiceTemplate(selectedServiceTemplate.id)} + onStartEditAreaName={(areaId) => (editingAreaName = areaId)} + onCancelEditAreaName={() => (editingAreaName = null)} + onUpdateAreaName={(areaId, name) => handleUpdateServiceArea(areaId, name)} + onDeleteArea={handleDeleteServiceArea} + onShowNewArea={() => (showNewAreaInput = selectedServiceTemplate.id)} + onHideNewArea={() => (showNewAreaInput = null)} + onNewAreaNameChange={(name) => (newAreaName = name)} + onCreateArea={() => handleCreateServiceArea(selectedServiceTemplate.id)} + onShowNewTask={(areaId) => (showNewTaskInput = areaId)} + onHideNewTask={() => (showNewTaskInput = null)} + onNewTaskDescriptionChange={(desc) => (newTaskDescription = desc)} + onCreateTask={handleCreateServiceTask} + onStartEditTask={(taskId) => (editingTaskId = taskId)} + onCancelEditTask={() => (editingTaskId = null)} + onUpdateTask={handleUpdateServiceTask} + onDeleteTask={handleDeleteServiceTask} + onMoveAreaUp={handleMoveServiceAreaUp} + onMoveAreaDown={handleMoveServiceAreaDown} + onMoveTaskUp={handleMoveServiceTaskUp} + onMoveTaskDown={handleMoveServiceTaskDown} + /> + {:else if activeTab === 'project' && selectedProjectTemplate} + (editingTemplateName = selectedProjectTemplate.id)} + onCancelEditTemplateName={() => (editingTemplateName = null)} + onUpdateTemplateName={(name) => + handleUpdateProjectTemplate(selectedProjectTemplate.id, name)} + onUpdateTemplateDescription={(description) => + handleUpdateProjectTemplateDescription(selectedProjectTemplate.id, description)} + onDeleteTemplate={() => handleDeleteProjectTemplate(selectedProjectTemplate.id)} + onStartEditAreaName={(areaId) => (editingAreaName = areaId)} + onCancelEditAreaName={() => (editingAreaName = null)} + onUpdateAreaName={(areaId, name) => handleUpdateProjectCategory(areaId, name)} + onDeleteArea={handleDeleteProjectCategory} + onShowNewArea={() => (showNewAreaInput = selectedProjectTemplate.id)} + onHideNewArea={() => (showNewAreaInput = null)} + onNewAreaNameChange={(name) => (newAreaName = name)} + onCreateArea={() => handleCreateProjectCategory(selectedProjectTemplate.id)} + onShowNewTask={(areaId) => (showNewTaskInput = areaId)} + onHideNewTask={() => (showNewTaskInput = null)} + onNewTaskDescriptionChange={(desc) => (newTaskDescription = desc)} + onCreateTask={handleCreateProjectTask} + onStartEditTask={(taskId) => (editingTaskId = taskId)} + onCancelEditTask={() => (editingTaskId = null)} + onUpdateTask={handleUpdateProjectTask} + onDeleteTask={handleDeleteProjectTask} + onMoveAreaUp={handleMoveProjectCategoryUp} + onMoveAreaDown={handleMoveProjectCategoryDown} + onMoveTaskUp={handleMoveProjectTaskUp} + onMoveTaskDown={handleMoveProjectTaskDown} + /> + {/if} +
+ {/if} +
+ {/if} +
+ + +{#if showJsonImportModal} + + + +{/if} + + +{#if showJsonExportModal} + + + +{/if} diff --git a/src/routes/admin/services/+page.svelte b/src/routes/admin/services/+page.svelte new file mode 100644 index 0000000..bb29489 --- /dev/null +++ b/src/routes/admin/services/+page.svelte @@ -0,0 +1,329 @@ + + +{#snippet createFormContent()} + +{/snippet} + +{#snippet headerActions()} + + + + + + Bulk Assign + +{/snippet} + +{#snippet emptyIcon()} + + + +{/snippet} + +{#snippet serviceItem(service: Service)} + {@const displayInfo = getServiceDisplayInfo(service)} + {@const teamCount = countNonAdminTeamMembers(service.teamMembers)} + +
+
+
+ +
+ + {formatDate(service.date)} + + + {service.status.replace('_', ' ')} + + {#if isDispatched(service.teamMembers)} + + Dispatched + + {/if} + {#if hasNonAdminTeamMembers(service.teamMembers)} + + Assigned + + {/if} +
+ + {#if displayInfo.accountName} +

+ {displayInfo.accountName} +

+ {:else} +

No account linked

+ {/if} + + {#if displayInfo.addressName} +

{displayInfo.addressName}

+ {/if} + + {#if displayInfo.address} +

{displayInfo.address}

+ {/if} + +
+ {teamCount} team member{teamCount !== 1 ? 's' : ''} +
+ + {#if service.notes} +

{service.notes}

+ {/if} +
+ + + +
+
+
+{/snippet} + + (searchQuery = q)} + searchPlaceholder="Search services..." + createButtonText="New Service" + onCreateClick={showCreateForm} + {emptyIcon} + item={serviceItem} + {headerActions} +/> + + (showGenerateModal = false)} + onSuccess={async () => { + await invalidateDashboard(); + }} +/> diff --git a/src/routes/admin/services/[service]/+page.server.ts b/src/routes/admin/services/[service]/+page.server.ts new file mode 100644 index 0000000..db19c22 --- /dev/null +++ b/src/routes/admin/services/[service]/+page.server.ts @@ -0,0 +1,89 @@ +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { + GetServiceStore, + ServiceSessionByServiceStore, + ScopeByAddressStore, + ServiceSessionPhotosStore, + ServiceSessionVideosStore +} from '$houdini'; +import type { ServiceSessionPhotos$result, ServiceSessionVideos$result } from '$houdini'; +import { fromGlobalId } from '$lib/utils/relay'; + +type SessionPhoto = ServiceSessionPhotos$result['serviceSessionImages'][number]; +type SessionVideo = ServiceSessionVideos$result['serviceSessionVideos'][number]; + +export const load: PageServerLoad = async (event) => { + const { params, locals } = event; + const serviceId = params.service; + const serviceUuid = fromGlobalId(serviceId); + + // Fetch params for Houdini stores + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + // Fetch the service first (needed to check status and get accountAddressId) + const serviceData = await new GetServiceStore().fetch({ + ...fetchParams, + variables: { id: serviceId } + }); + + const service = serviceData.data?.service; + if (!service) { + throw error(404, 'Service not found'); + } + + // Build parallel fetch promises based on service state + const needsSession = service.status === 'IN_PROGRESS' || service.status === 'COMPLETED'; + + // Fetch session and scope in parallel + const [sessionResult, scopeResult] = await Promise.all([ + // Session fetch (only if needed) + needsSession + ? new ServiceSessionByServiceStore() + .fetch({ ...fetchParams, variables: { serviceId: serviceUuid } }) + .catch(() => ({ data: { serviceSessions: [] } })) + : Promise.resolve(null), + // Scope fetch (only if has address) + service.accountAddressId + ? new ScopeByAddressStore() + .fetch({ ...fetchParams, variables: { accountAddressId: service.accountAddressId } }) + .catch(() => ({ data: { scopes: [] } })) + : Promise.resolve(null) + ]); + + const baseSession = sessionResult?.data?.serviceSessions?.[0] ?? null; + let photos: SessionPhoto[] = []; + let videos: SessionVideo[] = []; + + // If we have a session, fetch photos and videos in parallel + if (baseSession) { + const sessionUuid = fromGlobalId(baseSession.id); + const [photosResult, videosResult] = await Promise.all([ + new ServiceSessionPhotosStore() + .fetch({ ...fetchParams, variables: { sessionId: sessionUuid } }) + .catch((err) => { + console.error('Failed to fetch session photos:', err); + return { data: { serviceSessionImages: [] as SessionPhoto[] } }; + }), + new ServiceSessionVideosStore() + .fetch({ ...fetchParams, variables: { sessionId: sessionUuid } }) + .catch((err) => { + console.error('Failed to fetch session videos:', err); + return { data: { serviceSessionVideos: [] as SessionVideo[] } }; + }) + ]); + + photos = photosResult?.data?.serviceSessionImages ?? []; + videos = videosResult?.data?.serviceSessionVideos ?? []; + } + + // Combine session with photos/videos + const session = baseSession ? { ...baseSession, photos, videos } : null; + + return { + service, + serviceUuid, + session, + scope: scopeResult?.data?.scopes?.[0] ?? null + }; +}; diff --git a/src/routes/admin/services/[service]/+page.svelte b/src/routes/admin/services/[service]/+page.svelte new file mode 100644 index 0000000..d44397b --- /dev/null +++ b/src/routes/admin/services/[service]/+page.svelte @@ -0,0 +1,751 @@ + + +{#snippet editFormContent()} + +{/snippet} + +{#snippet scheduleEventContent()} + +{/snippet} + + + {displayName} - Services - Admin - Nexus + + +
+ + + + +
+
+

Service Details

+ {#if service.calendarEventId} + + + + + View Event + + {:else} + + {/if} +
+
+ +
+

Status

+
+ + {service.status.replace('_', ' ')} + + {#if isDispatched} + + Dispatched + + {/if} +
+
+ + +
+

Date

+

{formatDate(service.date)}

+
+
+ + + {#if address} +
+

Address

+

{address}

+
+ {/if} + + + {#if teamMembers().length > 0} +
+
+

Team

+ {#if messagableMembers.length >= 2} + + {/if} +
+
+ {#each teamMembers() as member (member.id)} +
+ + {member.role === 'TEAM_LEADER' ? 'TL' : 'TM'} + + {#if member.role !== 'ADMIN'} + + {:else} + + + {/if} + {member.fullName} +
+ {/each} +
+
+ {/if} + + + {#if service.notes} +
+

Notes

+

{service.notes}

+
+ {/if} +
+ + + {#if scope} +
+
+

Service Scope

+ + Manage at Account + +
+
+
+ {scope.name} + {#if scope.isActive} + + Active + + {/if} +
+ {#if scope.description} +

{scope.description}

+ {/if} +

+ {scope.areas?.length ?? 0} + {(scope.areas?.length ?? 0) === 1 ? 'area' : 'areas'}, + {scope.areas?.reduce((acc, a) => acc + (a.tasks?.length ?? 0), 0) ?? 0} tasks +

+
+
+ {:else} +
+
+ + + +
+

No Scope Available

+

+ This service's address does not have an active scope defined. + + Configure scope at the account level. + +

+
+
+
+ {/if} + + + {#if service.status === 'IN_PROGRESS' && session} +
+ + selectedTaskIds.clear()} + onAddNote={handleAddNote} + onUpdateNote={handleUpdateNote} + onDeleteNote={handleDeleteNote} + onUploadPhoto={handleUploadPhoto} + onUploadVideo={handleUploadVideo} + onUpdatePhoto={handleUpdatePhoto} + onUpdateVideo={handleUpdateVideo} + onDeletePhoto={handleDeletePhoto} + onDeleteVideo={handleDeleteVideo} + /> +
+ {:else if service.status === 'COMPLETED' && session} +
+ + +
+ {:else if service.status === 'SCHEDULED'} + {#if scope && (scope.areas?.length ?? 0) > 0} + goto('/admin/services', { invalidateAll: true })} + /> + {:else} + + {/if} + {/if} +
+
diff --git a/src/routes/admin/services/assign/+page.server.ts b/src/routes/admin/services/assign/+page.server.ts new file mode 100644 index 0000000..4761935 --- /dev/null +++ b/src/routes/admin/services/assign/+page.server.ts @@ -0,0 +1,6 @@ +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ parent }) => { + // Inherit data from admin layout (accounts, team, lookups, etc.) + return await parent(); +}; diff --git a/src/routes/admin/services/assign/+page.svelte b/src/routes/admin/services/assign/+page.svelte new file mode 100644 index 0000000..8a2013c --- /dev/null +++ b/src/routes/admin/services/assign/+page.svelte @@ -0,0 +1,718 @@ + + + + + + Bulk Assign - Services - Admin - Nexus + + +
+ +
+ +
+ + Back + +
+

Bulk Assign

+

Assign team members to scheduled services

+
+ + +
+ + +
+ +
+ {#each viewModes as mode (mode)} + + {/each} +
+
+ + +
+ +
+ {store.monthDisplay} + +
+ +
+
+ + + {#if store.error} +
+ {store.error} + +
+ {/if} + + +
+ +
+ + {getTabLabel(store.activeTab)} + + {getTabCount(store.activeTab)} + +
+ +
+ + + {#if getTabCount(store.activeTab) > 0} +
+
+ +
+ + {#if mobileHasSelection} + + {/if} +
+ + + {#if mobileHasSelection} +
+ {#if store.activeTab === 'readyToAssign'} + +
+ + {#if store.showBulkTeamMemberDropdown} + +
e.stopPropagation()} + class="absolute top-full right-0 z-30 mt-1 max-h-48 w-48 overflow-y-auto rounded-lg border border-theme bg-theme-card py-1 shadow-lg" + > + {#each store.nonAdminTeamMembers as member (member.id)} + {@const memberPk = atob(member.id).split(':')[1]} + + {/each} +
+ {/if} +
+ {/if} + +
+ {/if} +
+
+ {/if} + + + {#if store.isLoading} +
+
+ + + + + Loading services... +
+
+ {:else} + + + + +
+ {#each [...getMobileGroupedServices().entries()] as [groupKey, groupServices] (groupKey)} +
+ store.toggleGroup(store.activeTab, groupKey)} + getTeamMemberNames={store.getTeamMemberNames} + getNonDispatchTeamMemberNames={store.getNonDispatchTeamMemberNames} + getAvailableTeamMembers={store.getAvailableTeamMembers} + getStagedTeamMemberDetails={store.getStagedTeamMemberDetails} + hasStagedMembers={store.hasStagedMembers} + onAddDispatch={store.addDispatchToService} + onRemoveDispatch={store.removeDispatchFromService} + onSubmitStaged={store.submitStagedTeamMembers} + onRemoveNonDispatch={store.removeNonDispatchMembers} + onStageTeamMember={store.stageTeamMember} + onUnstageTeamMember={store.unstageTeamMember} + onToggleDropdown={store.setOpenTeamMemberDropdown} + onToggleSelection={(id) => store.toggleServiceSelection(store.activeTab, id)} + onUpdateDate={store.activeTab === 'unassigned' ? store.updateServiceDate : undefined} + onDeleteService={store.activeTab === 'unassigned' + ? store.promptDeleteService + : undefined} + onStartEditDate={store.activeTab === 'unassigned' ? store.startEditDate : undefined} + onCancelEditDate={store.activeTab === 'unassigned' ? store.cancelEditDate : undefined} + /> +
+ {/each} + {#if getTabCount(store.activeTab) === 0} +
+ + + + {getMobileEmptyMessage()} +
+ {/if} +
+ {/if} +
+ + (showGenerateModal = false)} + onSuccess={async () => { + await store.fetchServices(); + }} +/> + + diff --git a/src/routes/contact/+page.server.ts b/src/routes/contact/+page.server.ts new file mode 100644 index 0000000..7015031 --- /dev/null +++ b/src/routes/contact/+page.server.ts @@ -0,0 +1,41 @@ +import { fail } from '@sveltejs/kit'; +import { emailService } from '$lib/services/email'; +import type { Actions } from './$types'; + +export const actions = { + default: async ({ request }) => { + const formData = await request.formData(); + const name = formData.get('name') as string; + const email = formData.get('email') as string; + const subject = formData.get('subject') as string; + const message = formData.get('message') as string; + + if (!name || !email || !subject || !message) { + return fail(400, { error: 'All fields are required' }); + } + + const htmlBody = ` +

From: ${name} <${email}>

+

Subject: ${subject}

+
+

${message.replace(/\n/g, '
')}

+ `.trim(); + + const { error } = await emailService.sendEmail( + { + to: ['service@nexusclean.net'], + subject: `Contact Form: ${subject}`, + body: htmlBody, + fromName: 'Nexus Dispatch' + }, + 'dispatch@nexusclean.net' + ); + + if (error) { + console.error('Email API error:', error); + return fail(500, { error: 'Failed to send message. Please try again.' }); + } + + return { success: true }; + } +} satisfies Actions; diff --git a/src/routes/contact/+page.svelte b/src/routes/contact/+page.svelte new file mode 100644 index 0000000..8847c20 --- /dev/null +++ b/src/routes/contact/+page.svelte @@ -0,0 +1,168 @@ + + + + Contact Us - Nexus + + + + +
+ + +
+

Get in Touch

+

+ Have a question, feedback, or just want to say hello? We'd love to hear from you. Fill out + the form below and we'll get back to you as soon as possible. +

+
+
+
+ + +
+ +
+ +
+ + + + +
+

Hours

+
+
+

Office Hours

+

9:00 AM - 5:00 PM
By appointment only

+
+
+

Overnight Dispatch

+

+ 6:00 PM - 2:00 AM
Emergency service available +

+
+
+
+
+ + +
+
+
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + + {#if submitStatus === 'success'} +

Thank you! Your message has been sent.

+ {:else if submitStatus === 'error'} +

+ {errorMessage} +

+ {/if} +
+
+
+
+
+
diff --git a/src/routes/customer/+layout.server.ts b/src/routes/customer/+layout.server.ts new file mode 100644 index 0000000..5afd64f --- /dev/null +++ b/src/routes/customer/+layout.server.ts @@ -0,0 +1,158 @@ +import type { LayoutServerLoad } from './$types'; +import { redirect, error } from '@sveltejs/kit'; +import { CustomerAccountsStore, CustomerDashboardStore } from '$houdini'; +import { fromGlobalId } from '$lib/utils/relay'; +import { getCurrentMonth } from '$lib/utils/date'; + +export interface CustomerAccountInfo { + name: string; + addressName?: string; + streetAddress?: string; + city?: string; + state?: string; +} + +export const load: LayoutServerLoad = async (event) => { + const { url, parent, locals, depends } = event; + depends('app:dashboard'); + + const parentData = await parent(); + + // Get month from URL params or default to current month + const currentMonth = url.searchParams.get('month') ?? getCurrentMonth(); + const me = parentData.user?.data?.me; + + // Not authenticated + if (!me) { + const returnTo = encodeURIComponent(url.pathname + url.search); + throw redirect(307, `/login?return_to=${returnTo}`); + } + + // Customer routes require customer profile + if (me.__typename !== 'CustomerProfileType') { + throw error(403, 'This area is only accessible to customers'); + } + + // Get customer UUIDs from profile + const customerIds = me.customers?.map((c) => fromGlobalId(c.id)) ?? []; + + if (customerIds.length === 0) { + // Customer profile has no linked customers - show empty state + return { + ...parentData, + currentMonth, + customerIds: [], + customerNames: {} as Record, + customerAccountLookup: {} as Record, + accounts: [], + services: { + upcoming: [], + completed: [] + }, + projects: { + upcoming: [], + completed: [] + }, + invoices: [] + }; + } + + // Build customer name lookup + const customerNames = new Map(); + me.customers?.forEach((c) => { + customerNames.set(fromGlobalId(c.id), c.name); + }); + + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + // Fetch accounts and dashboard data in parallel + // Need accounts for display lookup, dashboard for services/projects/invoices + const [accountsResults, dashboardResults] = await Promise.all([ + // Fetch accounts for all linked customers + Promise.all( + customerIds.map((customerId) => + new CustomerAccountsStore().fetch({ + ...fetchParams, + variables: { filters: { customerId } } + }) + ) + ), + // Fetch consolidated dashboard data for each customer + Promise.all( + customerIds.map((customerId) => + new CustomerDashboardStore().fetch({ + ...fetchParams, + variables: { customerId } + }) + ) + ) + ]); + + // Combine all accounts from all customers + const allAccounts = accountsResults.flatMap((result) => result.data?.accounts ?? []); + + // Combine dashboard data from all customers + const allServices = dashboardResults.flatMap( + (result) => result.data?.customerDashboard?.services ?? [] + ); + const allProjects = dashboardResults.flatMap( + (result) => result.data?.customerDashboard?.projects ?? [] + ); + const allInvoices = dashboardResults.flatMap( + (result) => result.data?.customerDashboard?.invoices ?? [] + ); + + // Build account lookup for display + const customerAccountLookup = new Map(); + + allAccounts.forEach((account) => { + const accountUuid = fromGlobalId(account.id); + customerAccountLookup.set(accountUuid, { + name: account.name, + streetAddress: account.primaryAddress?.streetAddress, + city: account.primaryAddress?.city, + state: account.primaryAddress?.state + }); + // Also map addresses + account.addresses?.forEach((addr) => { + const addrUuid = fromGlobalId(addr.id); + customerAccountLookup.set(addrUuid, { + name: account.name, + addressName: addr.name, + streetAddress: addr.streetAddress, + city: addr.city, + state: addr.state + }); + }); + }); + + // Filter services by status + const upcomingServices = allServices.filter( + (s) => s.status === 'SCHEDULED' || s.status === 'IN_PROGRESS' + ); + const completedServices = allServices.filter((s) => s.status === 'COMPLETED'); + + // Filter projects by status + const upcomingProjects = allProjects.filter( + (p) => p.status === 'SCHEDULED' || p.status === 'IN_PROGRESS' + ); + const completedProjects = allProjects.filter((p) => p.status === 'COMPLETED'); + + return { + ...parentData, + currentMonth, + customerIds, + customerNames: Object.fromEntries(customerNames), + customerAccountLookup: Object.fromEntries(customerAccountLookup), + accounts: allAccounts, + services: { + upcoming: upcomingServices, + completed: completedServices + }, + projects: { + upcoming: upcomingProjects, + completed: completedProjects + }, + invoices: allInvoices + }; +}; diff --git a/src/routes/customer/+layout.svelte b/src/routes/customer/+layout.svelte new file mode 100644 index 0000000..ae9c9d0 --- /dev/null +++ b/src/routes/customer/+layout.svelte @@ -0,0 +1,7 @@ + + +{@render children()} diff --git a/src/routes/customer/+page.svelte b/src/routes/customer/+page.svelte new file mode 100644 index 0000000..e3b491e --- /dev/null +++ b/src/routes/customer/+page.svelte @@ -0,0 +1,400 @@ + + + + Dashboard - Nexus + + +
+ + + + + +
+ +
+ + +
+ {#each stats as stat (stat.label)} +
+
+ {stat.value} +
+
{stat.label}
+
+ {/each} +
+ + +
+ +
+
+

+ Upcoming Schedule +

+ View all +
+
+ {#each upcomingItems as item (item.id)} + +
+
+ {#if item.type === 'service'} +

Service

+

+ {getAccountName(item.accountId, item.accountAddressId)} +

+ {:else} +

{item.name}

+

+ {getProjectLocation(item)} +

+ {/if} +
+ + {formatDate(item.date)} + +
+
+ {/each} + {#if upcomingItems.length === 0} +
+ No upcoming services or projects scheduled +
+ {/if} +
+
+ + +
+
+

Pending Invoices

+ View all +
+
+ {#each pendingInvoices.slice(0, 3) as invoice (invoice.id)} + +
+
+

Invoice - {formatDate(invoice.date)}

+

+ {invoice.status === 'OVERDUE' ? 'Payment overdue' : 'Payment pending'} +

+
+ + {invoice.status === 'OVERDUE' ? 'Overdue' : 'Pending'} + +
+
+ {/each} + {#if pendingInvoices.length === 0} +
No pending invoices
+ {/if} +
+
+
+ + + +
+
diff --git a/src/routes/customer/accounts/+page.svelte b/src/routes/customer/accounts/+page.svelte new file mode 100644 index 0000000..d81f5ac --- /dev/null +++ b/src/routes/customer/accounts/+page.svelte @@ -0,0 +1,164 @@ + + + + Accounts - Nexus + + +
+ + + + +
+
+ + + + +
+
+ + + {#if filteredAccounts.length > 0} + + {:else if searchQuery} +
+ + + +

No Results

+

No accounts match your search.

+
+ {:else} +
+ + + +

No Accounts

+

You don't have any service accounts yet.

+
+ {/if} +
+
diff --git a/src/routes/customer/accounts/[account]/+page.server.ts b/src/routes/customer/accounts/[account]/+page.server.ts new file mode 100644 index 0000000..0779c79 --- /dev/null +++ b/src/routes/customer/accounts/[account]/+page.server.ts @@ -0,0 +1,23 @@ +import type { PageServerLoad } from './$types'; +import { error } from '@sveltejs/kit'; +import { GetAccountStore } from '$houdini'; + +export const load: PageServerLoad = async (event) => { + const { params, locals, parent } = event; + const parentData = await parent(); + + const accountResult = await new GetAccountStore().fetch({ + event, + metadata: { cookie: locals.cookie }, + variables: { id: params.account } + }); + + if (!accountResult.data?.account) { + throw error(404, 'Account not found'); + } + + return { + ...parentData, + account: accountResult + }; +}; diff --git a/src/routes/customer/accounts/[account]/+page.svelte b/src/routes/customer/accounts/[account]/+page.svelte new file mode 100644 index 0000000..5285a2a --- /dev/null +++ b/src/routes/customer/accounts/[account]/+page.svelte @@ -0,0 +1,378 @@ + + +{#snippet contactFormContent()} + {#if account} + + {/if} +{/snippet} + + + {account?.name ?? 'Account'} - Nexus + + +
+ + + + {#if account} +
+ +
+
+

Service Locations

+
+
+ {#each addresses as address (address.id)} +
+
+
+

+ {address.name || address.streetAddress} + {#if address.isPrimary} + Primary + {/if} +

+

+ {address.streetAddress} +

+

+ {address.city}, {address.state} + {address.zipCode} +

+
+
+ + + {#if address.schedules && address.schedules.length > 0} +
+

+ Service Schedule +

+ {#each address.schedules as schedule (schedule.id)} +
+ {schedule.name || 'Regular Schedule'}: + {getScheduleDays(schedule)} +
+ {/each} +
+ {/if} + + + {#if getActiveScope(address)} + {@const activeScope = getActiveScope(address)} +
+

Service Scope

+ {#if activeScope?.description} +

{activeScope.description}

+ {/if} + + {#if activeScope?.areas && activeScope.areas.length > 0} +
+ {#each [...activeScope.areas].sort((a, b) => a.order - b.order) as area (area.id)} +
+ + + {#if expandedAreas.has(area.id)} +
+ {#if area.tasks && area.tasks.length > 0} +
    + {#each [...area.tasks].sort((a, b) => a.order - b.order) as task (task.id)} +
  • + {task.description} + + {getFrequencyLabel(task.frequency)} + +
  • + {/each} +
+ {:else} +

No tasks in this area.

+ {/if} +
+ {/if} +
+ {/each} +
+ {:else} +

No areas defined in this scope.

+ {/if} +
+ {/if} +
+ {/each} + {#if addresses.length === 0} +
+ No addresses on file. +
+ {/if} +
+
+ + +
+
+

Contacts

+ +
+ {#if contacts.length > 0} +
+ {#each contacts as contact (contact.id)} +
+
+
+
+ {contact.fullName} + {#if contact.isPrimary} + Primary + {/if} +
+ {#if contact.email || contact.phone} +
+ {#if contact.email} +

{contact.email}

+ {/if} + {#if contact.phone} +

{contact.phone}

+ {/if} +
+ {/if} +
+
+ + +
+
+
+ {/each} +
+ {:else} +
+ No contacts on file. Add a contact to get started. +
+ {/if} +
+ + + {#if upcomingServices.length > 0} + + {/if} +
+ {:else} +
+

Account not found.

+
+ {/if} +
+
+ + + (deleteContactId = null)} +/> diff --git a/src/routes/customer/history/+page.svelte b/src/routes/customer/history/+page.svelte new file mode 100644 index 0000000..f994bd9 --- /dev/null +++ b/src/routes/customer/history/+page.svelte @@ -0,0 +1,363 @@ + + + + History - Nexus + + +
+ + + + +
+
+ + + + +
+
+ + +
+ +
+ {#each filterTabs as tab (tab.key)} + + {/each} +
+
+ + + {#if filteredItems.length > 0} + + {:else if searchQuery} +
+ + + +

No Results

+

No items match your search.

+
+ {:else} +
+ + + +

No History

+

+ {#if activeFilter === 'services'} + No completed services yet. + {:else if activeFilter === 'projects'} + No completed projects yet. + {:else} + No completed services or projects yet. + {/if} +

+
+ {/if} +
+
diff --git a/src/routes/customer/history/project/[project]/+page.server.ts b/src/routes/customer/history/project/[project]/+page.server.ts new file mode 100644 index 0000000..801106b --- /dev/null +++ b/src/routes/customer/history/project/[project]/+page.server.ts @@ -0,0 +1,87 @@ +import type { PageServerLoad } from './$types'; +import { error } from '@sveltejs/kit'; +import { + GetProjectStore, + ProjectSessionByProjectStore, + ProjectScopeStore, + TeamProfilesStore, + ProjectSessionPhotosStore, + ProjectSessionVideosStore +} from '$houdini'; +import { fromGlobalId, toGlobalId } from '$lib/utils/relay'; + +export const load: PageServerLoad = async (event) => { + const { params, locals, parent } = event; + const parentData = await parent(); + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + const projectResult = await new GetProjectStore().fetch({ + ...fetchParams, + variables: { id: params.project } + }); + + if (!projectResult.data?.project) { + throw error(404, 'Project not found'); + } + + const project = projectResult.data.project; + const projectUuid = fromGlobalId(params.project); + + // Fetch session data for completed projects + let sessionResult = null; + let sessionPhotos: Awaited>['data'] | null = null; + let sessionVideos: Awaited>['data'] | null = null; + + if (project.status === 'COMPLETED') { + sessionResult = await new ProjectSessionByProjectStore().fetch({ + ...fetchParams, + variables: { projectId: projectUuid } + }); + + // Fetch photos and videos separately to handle missing file errors gracefully + const session = sessionResult?.data?.projectSessions?.[0]; + if (session) { + const sessionUuid = fromGlobalId(session.id); + const [photosResult, videosResult] = await Promise.all([ + new ProjectSessionPhotosStore() + .fetch({ ...fetchParams, variables: { sessionId: sessionUuid } }) + .catch((err) => { + console.error('Failed to fetch session photos:', err); + return null; + }), + new ProjectSessionVideosStore() + .fetch({ ...fetchParams, variables: { sessionId: sessionUuid } }) + .catch((err) => { + console.error('Failed to fetch session videos:', err); + return null; + }) + ]); + + sessionPhotos = photosResult?.data ?? null; + sessionVideos = videosResult?.data ?? null; + } + } + + // Fetch the project scope for task name lookups + let scopeResult = null; + if (project.scopeId) { + const scopeGlobalId = toGlobalId('ProjectScopeType', project.scopeId); + scopeResult = await new ProjectScopeStore().fetch({ + ...fetchParams, + variables: { id: scopeGlobalId } + }); + } + + // Fetch team profiles for attribution + const teamProfilesResult = await new TeamProfilesStore().fetch(fetchParams); + + return { + ...parentData, + project: projectResult, + session: sessionResult, + sessionPhotos, + sessionVideos, + scope: scopeResult, + teamProfiles: teamProfilesResult + }; +}; diff --git a/src/routes/customer/history/project/[project]/+page.svelte b/src/routes/customer/history/project/[project]/+page.svelte new file mode 100644 index 0000000..aadaa46 --- /dev/null +++ b/src/routes/customer/history/project/[project]/+page.svelte @@ -0,0 +1,526 @@ + + + + {project?.name ?? 'Completed Project'} - Nexus + + +
+ + + + {#if project} +
+ +
+
+
+

Project Date

+

{formatDate(project.date)}

+
+ + Completed + +
+
+ + + + + +
+
+

Location

+
+
+ {#if accountInfo} +

Account

+

{accountInfo.name}

+

{accountInfo.streetAddress}

+

+ {accountInfo.city}, {accountInfo.state} +

+ {:else} +

{project.streetAddress}

+

+ {project.city}, {project.state} + {project.zipCode} +

+ {/if} +
+
+ + + {#if project.notes} +
+
+

Notes

+
+
+

{project.notes}

+
+
+ {/if} + + + {#if session} + +
+
+

Project Summary

+
+
+
+ {#if session.durationSeconds} +
+

Duration

+

+ {formatDuration(session.durationSeconds)} +

+
+ {/if} +
+

Tasks Completed

+

+ {session.completedTasks?.length ?? 0} +

+
+ {#if publicPhotos.length > 0} +
+

Photos

+

{publicPhotos.length}

+
+ {/if} +
+
+
+ + + {#if session.completedTasks && session.completedTasks.length > 0} +
+
+

Completed Tasks

+
+
+
+ {#each [...tasksByCategory.entries()] as [categoryName, tasks] (categoryName)} +
+ + + {#if expandedCategories.has(categoryName)} +
+
    + {#each tasks as { task, taskInfo } (task.id)} +
  • + + + +
    + {taskInfo?.description ?? 'Task'} +

    + {getTeamMemberName(task.completedById)} · {formatDateTime( + task.completedAt + )} +

    + {#if task.notes} +

    + "{task.notes}" +

    + {/if} +
    +
  • + {/each} +
+
+ {/if} +
+ {/each} +
+
+
+ {/if} + + + {#if publicPhotos.length > 0} +
+
+

Photos

+
+
+
+ {#each publicPhotos as photo (photo.id)} + + {/each} +
+
+
+ {/if} + + + {#if publicNotes.length > 0} +
+
+

Project Notes

+
+
+ {#each publicNotes as note (note.id)} +
+

{note.content}

+

+ {getTeamMemberName(note.authorId)} · {formatDateTime(note.createdAt)} +

+
+ {/each} +
+
+ {/if} + + + {#if publicVideos.length > 0} +
+
+

Videos

+
+
+
+ {#each publicVideos as video (video.id)} +
+ {#if video.video?.url} + + {/if} + {#if video.title || video.notes} +
+ {#if video.title} +

{video.title}

+ {/if} + {#if video.notes} +

{video.notes}

+ {/if} +
+ {/if} +
+ {/each} +
+
+
+ {/if} + {/if} +
+ {:else} +
+

Project not found.

+
+ {/if} +
+
+ + +{#if selectedPhoto} + +{/if} diff --git a/src/routes/customer/history/service/[service]/+page.server.ts b/src/routes/customer/history/service/[service]/+page.server.ts new file mode 100644 index 0000000..ee50c65 --- /dev/null +++ b/src/routes/customer/history/service/[service]/+page.server.ts @@ -0,0 +1,86 @@ +import type { PageServerLoad } from './$types'; +import { error } from '@sveltejs/kit'; +import { + GetServiceStore, + ServiceSessionByServiceStore, + ScopeByAddressStore, + TeamProfilesStore, + ServiceSessionPhotosStore, + ServiceSessionVideosStore +} from '$houdini'; +import { fromGlobalId } from '$lib/utils/relay'; + +export const load: PageServerLoad = async (event) => { + const { params, locals, parent } = event; + const parentData = await parent(); + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + const serviceResult = await new GetServiceStore().fetch({ + ...fetchParams, + variables: { id: params.service } + }); + + if (!serviceResult.data?.service) { + throw error(404, 'Service not found'); + } + + const service = serviceResult.data.service; + const serviceUuid = fromGlobalId(params.service); + + // Fetch session data for completed services + let sessionResult = null; + let sessionPhotos: Awaited>['data'] | null = null; + let sessionVideos: Awaited>['data'] | null = null; + + if (service.status === 'COMPLETED') { + sessionResult = await new ServiceSessionByServiceStore().fetch({ + ...fetchParams, + variables: { serviceId: serviceUuid } + }); + + // Fetch photos and videos separately to handle missing file errors gracefully + const session = sessionResult?.data?.serviceSessions?.[0]; + if (session) { + const sessionUuid = fromGlobalId(session.id); + const [photosResult, videosResult] = await Promise.all([ + new ServiceSessionPhotosStore() + .fetch({ ...fetchParams, variables: { sessionId: sessionUuid } }) + .catch((err) => { + console.error('Failed to fetch session photos:', err); + return null; + }), + new ServiceSessionVideosStore() + .fetch({ ...fetchParams, variables: { sessionId: sessionUuid } }) + .catch((err) => { + console.error('Failed to fetch session videos:', err); + return null; + }) + ]); + + sessionPhotos = photosResult?.data ?? null; + sessionVideos = videosResult?.data ?? null; + } + } + + // Fetch the active scope for task name lookups + let scopeResult = null; + if (service.accountAddressId) { + scopeResult = await new ScopeByAddressStore().fetch({ + ...fetchParams, + variables: { accountAddressId: service.accountAddressId } + }); + } + + // Fetch team profiles for attribution + const teamProfilesResult = await new TeamProfilesStore().fetch(fetchParams); + + return { + ...parentData, + service: serviceResult, + session: sessionResult, + sessionPhotos, + sessionVideos, + scope: scopeResult, + teamProfiles: teamProfilesResult + }; +}; diff --git a/src/routes/customer/history/service/[service]/+page.svelte b/src/routes/customer/history/service/[service]/+page.svelte new file mode 100644 index 0000000..02d34d5 --- /dev/null +++ b/src/routes/customer/history/service/[service]/+page.svelte @@ -0,0 +1,532 @@ + + + + Completed Service - Nexus + + +
+ + + + {#if service} +
+ +
+
+
+

Service Date

+

{formatDate(service.date)}

+
+ + Completed + +
+
+ + + + + + {#if accountInfo} +
+
+

Location

+
+
+

{accountInfo.name}

+

{accountInfo.address}

+
+
+ {/if} + + + {#if service.notes} +
+
+

Notes

+
+
+

{service.notes}

+
+
+ {/if} + + + {#if session} + +
+
+

Service Summary

+
+
+
+ {#if session.durationSeconds} +
+

Duration

+

+ {formatDuration(session.durationSeconds)} +

+
+ {/if} +
+

Tasks Completed

+

+ {session.completedTasks?.length ?? 0} +

+
+ {#if publicPhotos.length > 0} +
+

Photos

+

{publicPhotos.length}

+
+ {/if} +
+
+
+ + + {#if session.completedTasks && session.completedTasks.length > 0} +
+
+

Completed Tasks

+
+
+
+ {#each [...tasksByArea.entries()] as [areaName, tasks] (areaName)} +
+ + + {#if expandedAreas.has(areaName)} +
+
    + {#each tasks as { task, taskInfo } (task.id)} +
  • + + + +
    + {taskInfo?.description ?? 'Task'} +

    + {getTeamMemberName(task.completedById)} · {formatDateTime( + task.completedAt + )} +

    + {#if task.notes} +

    + "{task.notes}" +

    + {/if} +
    +
  • + {/each} +
+
+ {/if} +
+ {/each} +
+
+
+ {/if} + + + {#if publicPhotos.length > 0} +
+
+

Photos

+
+
+
+ {#each publicPhotos as photo (photo.id)} + + {/each} +
+
+
+ {/if} + + + {#if publicNotes.length > 0} +
+
+

Service Notes

+
+
+ {#each publicNotes as note (note.id)} +
+

{note.content}

+

+ {getTeamMemberName(note.authorId)} · {formatDateTime(note.createdAt)} +

+
+ {/each} +
+
+ {/if} + + + {#if publicVideos.length > 0} +
+
+

Videos

+
+
+
+ {#each publicVideos as video (video.id)} +
+ {#if video.video?.url} + + {/if} + {#if video.title || video.notes} +
+ {#if video.title} +

{video.title}

+ {/if} + {#if video.notes} +

{video.notes}

+ {/if} +
+ {/if} +
+ {/each} +
+
+
+ {/if} + {/if} +
+ {:else} +
+

Service not found.

+
+ {/if} +
+
+ + +{#if selectedPhoto} + +{/if} diff --git a/src/routes/customer/invoices/+page.svelte b/src/routes/customer/invoices/+page.svelte new file mode 100644 index 0000000..c5d7903 --- /dev/null +++ b/src/routes/customer/invoices/+page.svelte @@ -0,0 +1,194 @@ + + + + Invoices - Nexus + + +
+ + + + +
+ +
+ {#each filterTabs as tab (tab.key)} + + {/each} +
+
+ + + {#if sortedInvoices.length > 0} + + {:else} +
+ + + +

No Invoices

+

+ {#if activeFilter === 'sent'} + No pending invoices. + {:else if activeFilter === 'paid'} + No paid invoices yet. + {:else} + No invoices found. + {/if} +

+
+ {/if} +
+
diff --git a/src/routes/customer/invoices/[invoice]/+page.server.ts b/src/routes/customer/invoices/[invoice]/+page.server.ts new file mode 100644 index 0000000..4e59c94 --- /dev/null +++ b/src/routes/customer/invoices/[invoice]/+page.server.ts @@ -0,0 +1,23 @@ +import type { PageServerLoad } from './$types'; +import { error } from '@sveltejs/kit'; +import { GetCustomerInvoiceStore } from '$houdini'; + +export const load: PageServerLoad = async (event) => { + const { params, locals, parent } = event; + const parentData = await parent(); + + const invoiceResult = await new GetCustomerInvoiceStore().fetch({ + event, + metadata: { cookie: locals.cookie }, + variables: { id: params.invoice } + }); + + if (!invoiceResult.data?.invoice) { + throw error(404, 'Invoice not found'); + } + + return { + ...parentData, + invoice: invoiceResult + }; +}; diff --git a/src/routes/customer/invoices/[invoice]/+page.svelte b/src/routes/customer/invoices/[invoice]/+page.svelte new file mode 100644 index 0000000..6385c93 --- /dev/null +++ b/src/routes/customer/invoices/[invoice]/+page.svelte @@ -0,0 +1,192 @@ + + + + Invoice - Nexus + + +
+ + + + {#if invoice} + {@const statusBadge = getStatusBadge(invoice.status)} +
+ +
+
+
+
+

Invoice Date

+

{formatDate(invoice.date)}

+
+ + {statusBadge.text} + +
+
+
+
+ Customer + {getCustomerName(invoice.customerId)} +
+ {#if invoice.datePaid} +
+ Date Paid + {formatDate(invoice.datePaid)} +
+ {/if} + {#if invoice.paymentType} +
+ Payment Method + {invoice.paymentType} +
+ {/if} +
+ Total + + {formatCurrency(grandTotal)} + +
+
+
+ + + {#if invoice.revenues && invoice.revenues.length > 0} +
+
+

Service Charges

+
+
+ {#each invoice.revenues as revenue (revenue.id)} +
+
+

{getAccountName(revenue.accountId)}

+

+ {formatDate(revenue.startDate)} - {formatDate(revenue.endDate)} +

+
+ + {formatCurrency(Number(revenue.amount) || 0)} + +
+ {/each} +
+
+
+ Subtotal + {formatCurrency(revenueTotal)} +
+
+
+ {/if} + + + {#if invoice.projects && invoice.projects.length > 0} +
+
+

Projects

+
+
+ {#each invoice.projects as project (project.id)} +
+
+

{project.name}

+

{formatDate(project.date)}

+
+ + {formatCurrency(Number(project.amount) || 0)} + +
+ {/each} +
+
+
+ Subtotal + {formatCurrency(projectTotal)} +
+
+
+ {/if} +
+ {:else} +
+

Invoice not found.

+
+ {/if} +
+
diff --git a/src/routes/customer/profile/+page.svelte b/src/routes/customer/profile/+page.svelte new file mode 100644 index 0000000..70a6117 --- /dev/null +++ b/src/routes/customer/profile/+page.svelte @@ -0,0 +1,183 @@ + + + + My Profile - Nexus + + +
+ + +
+

My Profile

+

View and manage your profile information.

+
+ +
+ +
+
+
+ + + +
+

Nexus Account

+

Your authentication and identity information

+
+
+
+ +
+ {#if kratosTraits} +
+
+
First Name
+
{kratosTraits.name?.first ?? 'Not set'}
+
+
+
Last Name
+
{kratosTraits.name?.last ?? 'Not set'}
+
+
+
Email
+
{kratosTraits.email ?? 'Not set'}
+
+
+
Phone
+
{kratosTraits.phone ?? 'Not set'}
+
+
+ {:else} +

Loading account information...

+ {/if} +
+ + +
+ + + {#if user} +
+
+
+ +
+ {user.fullName?.charAt(0).toUpperCase() ?? '?'} +
+
+
+

Profile Information

+ + Customer + +
+

Your customer profile data

+
+
+
+ +
+
+
+
Full Name
+
{user.fullName ?? 'Not set'}
+
+
+
Email
+
{user.email ?? 'Not set'}
+
+
+
Phone
+
{user.phone ?? 'Not set'}
+
+
+
Customers
+
+ {#if customers && customers.length > 0} +
+ {#each customers as customer (customer.id)} + + {customer.name} + + {/each} +
+ {:else} + No linked customers + {/if} +
+
+
+
+
+ {/if} +
+
+
diff --git a/src/routes/customer/schedule/+page.svelte b/src/routes/customer/schedule/+page.svelte new file mode 100644 index 0000000..63ca4d5 --- /dev/null +++ b/src/routes/customer/schedule/+page.svelte @@ -0,0 +1,313 @@ + + + + Schedule - Nexus + + +
+ + + + +
+ +
+ {#each filterTabs as tab (tab.key)} + + {/each} +
+
+ + + {#if filteredItems.length > 0} + + {:else} +
+ + + +

No Upcoming Items

+

+ {#if activeFilter === 'services'} + No upcoming services scheduled. + {:else if activeFilter === 'projects'} + No upcoming projects scheduled. + {:else} + No upcoming services or projects scheduled. + {/if} +

+
+ {/if} +
+
diff --git a/src/routes/customer/schedule/project/[project]/+page.server.ts b/src/routes/customer/schedule/project/[project]/+page.server.ts new file mode 100644 index 0000000..8d43ab5 --- /dev/null +++ b/src/routes/customer/schedule/project/[project]/+page.server.ts @@ -0,0 +1,37 @@ +import type { PageServerLoad } from './$types'; +import { error } from '@sveltejs/kit'; +import { GetProjectStore, ProjectScopeStore } from '$houdini'; +import { toGlobalId } from '$lib/utils/relay'; + +export const load: PageServerLoad = async (event) => { + const { params, locals, parent } = event; + const parentData = await parent(); + + const projectResult = await new GetProjectStore().fetch({ + event, + metadata: { cookie: locals.cookie }, + variables: { id: params.project } + }); + + if (!projectResult.data?.project) { + throw error(404, 'Project not found'); + } + + // Fetch the project scope if it exists + let scopeResult = null; + const scopeId = projectResult.data.project.scopeId; + if (scopeId) { + const scopeGlobalId = toGlobalId('ProjectScopeType', scopeId); + scopeResult = await new ProjectScopeStore().fetch({ + event, + metadata: { cookie: locals.cookie }, + variables: { id: scopeGlobalId } + }); + } + + return { + ...parentData, + project: projectResult, + scope: scopeResult + }; +}; diff --git a/src/routes/customer/schedule/project/[project]/+page.svelte b/src/routes/customer/schedule/project/[project]/+page.svelte new file mode 100644 index 0000000..acbc2e3 --- /dev/null +++ b/src/routes/customer/schedule/project/[project]/+page.svelte @@ -0,0 +1,257 @@ + + + + {project?.name ?? 'Project'} - Nexus + + +
+ + + + {#if project} + {@const statusBadge = getStatusBadge(project.status)} +
+ +
+
+
+

Project Date

+

{formatDate(project.date)}

+
+ + {statusBadge.text} + +
+
+ + + + + +
+
+

Location

+
+
+ {#if accountInfo} +

Account

+

{accountInfo.name}

+

{accountInfo.streetAddress}

+

+ {accountInfo.city}, {accountInfo.state} +

+ {:else} +

{project.streetAddress}

+

+ {project.city}, {project.state} + {project.zipCode} +

+ {/if} +
+
+ + + {#if project.notes} +
+
+

Notes

+
+
+

{project.notes}

+
+
+ {/if} + + + {#if scope} +
+
+

Project Scope

+ {#if scope.description} +

{scope.description}

+ {/if} +
+
+ {#if scope.projectAreas && scope.projectAreas.length > 0} +
+ {#each [...scope.projectAreas].sort((a, b) => a.order - b.order) as area (area.id)} +
+ + + {#if expandedAreas.has(area.id)} +
+ {#if area.projectTasks && area.projectTasks.length > 0} +
    + {#each [...area.projectTasks].sort((a, b) => a.order - b.order) as task (task.id)} +
  • + + {task.checklistDescription || task.description} + + {#if task.estimatedMinutes} + + ~{task.estimatedMinutes} min + + {/if} +
  • + {/each} +
+ {:else} +

No tasks in this category.

+ {/if} +
+ {/if} +
+ {/each} +
+ {:else} +

No scope items defined.

+ {/if} +
+
+ {/if} +
+ {:else} +
+

Project not found.

+
+ {/if} +
+
diff --git a/src/routes/customer/schedule/service/[service]/+page.server.ts b/src/routes/customer/schedule/service/[service]/+page.server.ts new file mode 100644 index 0000000..86e9d93 --- /dev/null +++ b/src/routes/customer/schedule/service/[service]/+page.server.ts @@ -0,0 +1,35 @@ +import type { PageServerLoad } from './$types'; +import { error } from '@sveltejs/kit'; +import { GetServiceStore, ScopeByAddressStore } from '$houdini'; + +export const load: PageServerLoad = async (event) => { + const { params, locals, parent } = event; + const parentData = await parent(); + + const serviceResult = await new GetServiceStore().fetch({ + event, + metadata: { cookie: locals.cookie }, + variables: { id: params.service } + }); + + if (!serviceResult.data?.service) { + throw error(404, 'Service not found'); + } + + // Fetch the active scope for this service's address + let scopeResult = null; + const accountAddressId = serviceResult.data.service.accountAddressId; + if (accountAddressId) { + scopeResult = await new ScopeByAddressStore().fetch({ + event, + metadata: { cookie: locals.cookie }, + variables: { accountAddressId } + }); + } + + return { + ...parentData, + service: serviceResult, + scope: scopeResult + }; +}; diff --git a/src/routes/customer/schedule/service/[service]/+page.svelte b/src/routes/customer/schedule/service/[service]/+page.svelte new file mode 100644 index 0000000..052aa87 --- /dev/null +++ b/src/routes/customer/schedule/service/[service]/+page.svelte @@ -0,0 +1,262 @@ + + + + Service Details - Nexus + + +
+ + + + {#if service} + {@const statusBadge = getStatusBadge(service.status)} +
+ +
+
+
+

Service Date

+

{formatDate(service.date)}

+
+ + {statusBadge.text} + +
+
+ + + + + + {#if accountInfo} +
+
+

Location

+
+
+

{accountInfo.name}

+

{accountInfo.address}

+
+
+ {/if} + + + {#if service.notes} +
+
+

Notes

+
+
+

{service.notes}

+
+
+ {/if} + + + {#if scope} +
+
+

Service Checklist

+ {#if scope.description} +

{scope.description}

+ {/if} +
+
+ {#if scope.areas && scope.areas.length > 0} +
+ {#each [...scope.areas].sort((a, b) => a.order - b.order) as area (area.id)} +
+ + + {#if expandedAreas.has(area.id)} +
+ {#if area.tasks && area.tasks.length > 0} +
    + {#each [...area.tasks].sort((a, b) => a.order - b.order) as task (task.id)} +
  • + + {task.checklistDescription || task.description} + + + {getFrequencyLabel(task.frequency)} + +
  • + {/each} +
+ {:else} +

No tasks in this area.

+ {/if} +
+ {/if} +
+ {/each} +
+ {:else} +

No checklist items defined.

+ {/if} +
+
+ {/if} +
+ {:else} +
+

Service not found.

+
+ {/if} +
+
diff --git a/src/routes/layout.css b/src/routes/layout.css new file mode 100644 index 0000000..da9b33a --- /dev/null +++ b/src/routes/layout.css @@ -0,0 +1,887 @@ +@import 'tailwindcss'; +@plugin '@tailwindcss/forms'; +@plugin '@tailwindcss/typography'; + +/* ============================================ + THEME COLOR SYSTEM + ============================================ + Primary: Blue + Secondary: Green + Primary Accent: Orange + Secondary Accent: Purple + Alert/Error: Red + Warning: Yellow + Success: Green (distinct from secondary) + ============================================ */ + +@theme { + /* Primary - Blue (muted/professional) */ + --color-primary-50: #f0f6fc; + --color-primary-100: #dbe8f7; + --color-primary-200: #bdd4f0; + --color-primary-300: #8fb8e5; + --color-primary-400: #5a94d6; + --color-primary-500: #3b78c4; + --color-primary-600: #2d5fa6; + --color-primary-700: #274d87; + --color-primary-800: #254270; + --color-primary-900: #23395e; + --color-primary-950: #18253f; + + /* Secondary - Green (muted/professional) */ + --color-secondary-50: #f2f8f4; + --color-secondary-100: #e0efe4; + --color-secondary-200: #c3dfcc; + --color-secondary-300: #96c7a6; + --color-secondary-400: #65a97b; + --color-secondary-500: #458c5e; + --color-secondary-600: #33714a; + --color-secondary-700: #2a5b3d; + --color-secondary-800: #244933; + --color-secondary-900: #1f3c2b; + --color-secondary-950: #102118; + + /* Accent Primary - Orange (muted/professional) */ + --color-accent-50: #fdf6f0; + --color-accent-100: #fbe9db; + --color-accent-200: #f6d0b6; + --color-accent-300: #f0b088; + --color-accent-400: #e88958; + --color-accent-500: #e16a36; + --color-accent-600: #d2522b; + --color-accent-700: #ae3f26; + --color-accent-800: #8b3425; + --color-accent-900: #712e22; + --color-accent-950: #3d1510; + + /* Accent Secondary - Purple (muted/professional) */ + --color-accent2-50: #f6f4fb; + --color-accent2-100: #ede9f7; + --color-accent2-200: #ddd5f0; + --color-accent2-300: #c5b6e4; + --color-accent2-400: #a78fd4; + --color-accent2-500: #8b6bc2; + --color-accent2-600: #7652ab; + --color-accent2-700: #634391; + --color-accent2-800: #533978; + --color-accent2-900: #463162; + --color-accent2-950: #2c1c42; + + /* Accent Tertiary - Teal (customers) */ + --color-accent3-50: #f0fdfa; + --color-accent3-100: #ccfbf1; + --color-accent3-200: #99f6e4; + --color-accent3-300: #5eead4; + --color-accent3-400: #2dd4bf; + --color-accent3-500: #14b8a6; + --color-accent3-600: #0d9488; + --color-accent3-700: #0f766e; + --color-accent3-800: #115e59; + --color-accent3-900: #134e4a; + --color-accent3-950: #042f2e; + + /* Accent Quaternary - Rose (profiles) */ + --color-accent4-50: #fff1f2; + --color-accent4-100: #ffe4e6; + --color-accent4-200: #fecdd3; + --color-accent4-300: #fda4af; + --color-accent4-400: #fb7185; + --color-accent4-500: #f43f5e; + --color-accent4-600: #e11d48; + --color-accent4-700: #be123c; + --color-accent4-800: #9f1239; + --color-accent4-900: #881337; + --color-accent4-950: #4c0519; + + /* Accent Quinary - Amber (specialty) */ + --color-accent5-50: #fffbeb; + --color-accent5-100: #fef3c7; + --color-accent5-200: #fde68a; + --color-accent5-300: #fcd34d; + --color-accent5-400: #fbbf24; + --color-accent5-500: #f59e0b; + --color-accent5-600: #d97706; + --color-accent5-700: #b45309; + --color-accent5-800: #92400e; + --color-accent5-900: #78350f; + --color-accent5-950: #451a03; + + /* Accent Senary - Indigo (invoices) */ + --color-accent6-50: #eef2ff; + --color-accent6-100: #e0e7ff; + --color-accent6-200: #c7d2fe; + --color-accent6-300: #a5b4fc; + --color-accent6-400: #818cf8; + --color-accent6-500: #6366f1; + --color-accent6-600: #4f46e5; + --color-accent6-700: #4338ca; + --color-accent6-800: #3730a3; + --color-accent6-900: #312e81; + --color-accent6-950: #1e1b4b; + + /* Accent Septenary - Cyan (calendar) */ + --color-accent7-50: #ecfeff; + --color-accent7-100: #cffafe; + --color-accent7-200: #a5f3fc; + --color-accent7-300: #67e8f9; + --color-accent7-400: #22d3ee; + --color-accent7-500: #06b6d4; + --color-accent7-600: #0891b2; + --color-accent7-700: #0e7490; + --color-accent7-800: #155e75; + --color-accent7-900: #164e63; + --color-accent7-950: #083344; + + /* Messages - Slate Blue (professional communication) */ + --color-message-50: #f1f5f9; + --color-message-100: #e2e8f0; + --color-message-200: #cbd5e1; + --color-message-300: #94a3b8; + --color-message-400: #64748b; + --color-message-500: #475569; + --color-message-600: #334155; + --color-message-700: #1e293b; + --color-message-800: #0f172a; + --color-message-900: #020617; + --color-message-950: #010313; + + /* Notifications - Coral/Salmon (attention-grabbing but warm) */ + --color-notification-50: #fff5f5; + --color-notification-100: #ffe4e4; + --color-notification-200: #fecaca; + --color-notification-300: #fca5a5; + --color-notification-400: #f87171; + --color-notification-500: #ef5350; + --color-notification-600: #dc2626; + --color-notification-700: #b91c1c; + --color-notification-800: #991b1b; + --color-notification-900: #7f1d1d; + --color-notification-950: #450a0a; + + /* Error/Alert - Red (muted/professional) */ + --color-error-50: #fdf3f3; + --color-error-100: #fce4e4; + --color-error-200: #fbcdcd; + --color-error-300: #f6a8a8; + --color-error-400: #ee7676; + --color-error-500: #e14a4a; + --color-error-600: #cd2d2d; + --color-error-700: #ac2323; + --color-error-800: #8e2121; + --color-error-900: #772222; + --color-error-950: #400d0d; + + /* Warning - Yellow (muted/professional) */ + --color-warning-50: #fdfaeb; + --color-warning-100: #faf2c9; + --color-warning-200: #f5e394; + --color-warning-300: #efd05b; + --color-warning-400: #e8bb30; + --color-warning-500: #d8a01d; + --color-warning-600: #ba7c16; + --color-warning-700: #955916; + --color-warning-800: #7b4619; + --color-warning-900: #693a1a; + --color-warning-950: #3d1e0a; + + /* Success - Green (distinct from secondary, muted) */ + --color-success-50: #f0fdf2; + --color-success-100: #dcfce2; + --color-success-200: #bbf7c6; + --color-success-300: #86ef9b; + --color-success-400: #4ade6a; + --color-success-500: #22c546; + --color-success-600: #16a336; + --color-success-700: #16802e; + --color-success-800: #176528; + --color-success-900: #155324; + --color-success-950: #052e10; + + /* Neutral/Surface colors for theming */ + --color-surface-50: #f8fafc; + --color-surface-100: #f1f5f9; + --color-surface-200: #e2e8f0; + --color-surface-300: #cbd5e1; + --color-surface-400: #94a3b8; + --color-surface-500: #64748b; + --color-surface-600: #475569; + --color-surface-700: #334155; + --color-surface-800: #1e293b; + --color-surface-900: #0f172a; + --color-surface-950: #020617; +} + +/* ============================================ + LIGHT THEME (default) + ============================================ */ +:root { + color-scheme: light; + + /* Background colors - subtle blue tint for softer appearance */ + --theme-bg: var(--color-primary-50); + --theme-bg-secondary: #e8f0f8; + --theme-bg-tertiary: var(--color-primary-100); + + /* Text colors */ + --theme-text: var(--color-surface-900); + --theme-text-secondary: var(--color-surface-600); + --theme-text-muted: var(--color-surface-400); + + /* Border colors */ + --theme-border: var(--color-surface-200); + --theme-border-hover: var(--color-surface-300); + + /* Interactive states */ + --theme-hover: var(--color-primary-100); + --theme-active: var(--color-primary-200); + + /* Shadows */ + --theme-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --theme-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --theme-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + + /* Card/Panel backgrounds - subtle blue tint to match overall theme */ + --theme-card: #f5f8fc; + --theme-card-hover: #edf2f9; +} + +/* ============================================ + DARK THEME + ============================================ */ +.dark { + color-scheme: dark; + + /* Background colors */ + --theme-bg: var(--color-surface-900); + --theme-bg-secondary: var(--color-surface-800); + --theme-bg-tertiary: var(--color-surface-700); + + /* Text colors */ + --theme-text: var(--color-surface-50); + --theme-text-secondary: var(--color-surface-300); + --theme-text-muted: var(--color-surface-500); + + /* Border colors */ + --theme-border: var(--color-surface-700); + --theme-border-hover: var(--color-surface-600); + + /* Interactive states */ + --theme-hover: var(--color-surface-800); + --theme-active: var(--color-surface-700); + + /* Shadows (more subtle in dark mode) */ + --theme-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.3), 0 1px 2px -1px rgb(0 0 0 / 0.3); + --theme-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.3), 0 2px 4px -2px rgb(0 0 0 / 0.3); + --theme-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.3), 0 4px 6px -4px rgb(0 0 0 / 0.3); + + /* Card/Panel backgrounds */ + --theme-card: var(--color-surface-800); + --theme-card-hover: var(--color-surface-700); +} + +/* ============================================ + BASE STYLES + ============================================ */ +html { + background-color: var(--theme-bg); + color: var(--theme-text); + transition: + background-color 0.2s ease, + color 0.2s ease; +} + +body { + background-color: var(--theme-bg); + min-height: 100vh; +} + +/* ============================================ + UTILITY CLASSES + ============================================ */ +@utility bg-theme { + background-color: var(--theme-bg); +} + +@utility bg-theme-secondary { + background-color: var(--theme-bg-secondary); +} + +@utility bg-theme-tertiary { + background-color: var(--theme-bg-tertiary); +} + +@utility bg-theme-card { + background-color: var(--theme-card); +} + +@utility text-theme { + color: var(--theme-text); +} + +@utility text-theme-secondary { + color: var(--theme-text-secondary); +} + +@utility text-theme-muted { + color: var(--theme-text-muted); +} + +@utility border-theme { + border-color: var(--theme-border); +} + +@utility border-theme-hover { + border-color: var(--theme-border-hover); +} + +@utility shadow-theme { + box-shadow: var(--theme-shadow); +} + +@utility shadow-theme-md { + box-shadow: var(--theme-shadow-md); +} + +@utility shadow-theme-lg { + box-shadow: var(--theme-shadow-lg); +} + +/* ============================================ + COMPONENT STYLES + ============================================ */ + +/* Page Titles */ +@utility page-title { + @apply text-2xl font-bold md:text-3xl; + color: var(--theme-text); +} + +@utility page-title-primary { + @apply text-3xl font-bold text-primary-500 sm:text-4xl; +} + +@utility page-subtitle { + @apply mt-2; + color: var(--theme-text-secondary); +} + +/* Section Headers */ +@utility section-title { + @apply text-xl font-semibold text-primary-500; +} + +@utility section-title-accent { + @apply text-xl font-semibold text-accent-500; +} + +/* Cards */ +@utility card { + @apply rounded-lg border; + border-color: var(--theme-border); + background-color: var(--theme-card); +} + +@utility card-interactive { + @apply rounded-lg border transition-colors hover:border-primary-500/30 hover:bg-black/5 dark:hover:bg-white/5; + border-color: var(--theme-border); + background-color: var(--theme-card); +} + +@utility card-padded { + @apply rounded-lg border p-6; + border-color: var(--theme-border); + background-color: var(--theme-card); +} + +/* Empty State */ +@utility empty-state { + @apply rounded-lg border p-8 text-center; + border-color: var(--theme-border); + background-color: var(--theme-card); +} + +@utility empty-state-icon { + @apply mx-auto mb-4 h-16 w-16; + color: var(--theme-text-muted); +} + +@utility empty-state-title { + @apply mb-2 text-lg font-semibold; + color: var(--theme-text); +} + +@utility empty-state-text { + @apply mx-auto max-w-md; + color: var(--theme-text-muted); +} + +/* Badges/Tags */ +@utility badge { + @apply rounded px-2 py-0.5 text-xs font-semibold; +} + +@utility badge-primary { + @apply rounded bg-primary-500/20 px-2 py-0.5 text-xs font-semibold text-primary-500; +} + +@utility badge-secondary { + @apply rounded bg-secondary-500/20 px-2 py-0.5 text-xs font-semibold text-secondary-500; +} + +@utility badge-accent { + @apply rounded bg-accent-500/20 px-2 py-0.5 text-xs font-semibold text-accent-500; +} + +@utility badge-warning { + @apply rounded bg-amber-500/20 px-2 py-0.5 text-xs font-semibold text-amber-600 dark:text-amber-400; +} + +@utility badge-success { + @apply rounded bg-success-500/20 px-2 py-0.5 text-xs font-semibold text-success-600 dark:text-success-400; +} + +/* Detail Labels (for info cards) */ +@utility detail-label { + @apply mb-1 text-xs font-medium tracking-wide uppercase; + color: var(--theme-text-muted); +} + +/* Back Links */ +@utility back-link { + @apply inline-flex items-center gap-2 text-primary-500 transition-colors hover:text-primary-600; +} + +/* Buttons */ +@utility btn { + @apply inline-block rounded-lg px-4 py-2 font-medium transition-colors; +} + +@utility btn-primary { + @apply inline-block rounded-lg bg-primary-500 px-4 py-2 font-medium text-white transition-colors hover:bg-primary-600 active:bg-primary-700; +} + +@utility btn-accent { + @apply inline-block rounded-lg bg-accent-500 px-4 py-2 font-medium text-white transition-colors hover:bg-accent-600 active:bg-accent-700; +} + +@utility btn-ghost { + @apply inline-block rounded-lg px-4 py-2 font-medium transition-colors hover:bg-black/5 dark:hover:bg-white/10; +} + +/* Icon Buttons */ +@utility btn-icon { + @apply inline-flex items-center justify-center rounded-lg p-2 transition-colors hover:bg-black/5 active:bg-black/10 dark:hover:bg-white/10 dark:active:bg-white/15; +} + +@utility btn-icon-sm { + @apply inline-flex items-center justify-center rounded-lg p-1.5 transition-colors hover:bg-black/5 active:bg-black/10 dark:hover:bg-white/10 dark:active:bg-white/15; +} + +@utility btn-icon-xs { + @apply inline-flex items-center justify-center rounded p-1 transition-colors hover:bg-black/5 active:bg-black/10 dark:hover:bg-white/10 dark:active:bg-white/15; +} + +/* Form Layout */ +@utility form-section { + @apply space-y-6; +} + +@utility form-fields { + @apply space-y-4; +} + +/* Expandable Sections (for use with
) */ +@utility expandable-header { + @apply flex w-full cursor-pointer list-none items-center justify-between transition-opacity hover:opacity-80; +} + +@utility expandable-chevron { + @apply h-5 w-5 flex-shrink-0 transition-transform group-open:rotate-180; +} + +/* Scope/Area Styles */ +@utility scope-container { + @apply rounded-lg border border-accent-500/20 bg-gradient-to-br from-accent-500/5 to-accent-500/10 p-5 text-sm shadow-sm dark:from-accent-500/10 dark:to-accent-500/5; +} + +@utility scope-title { + @apply text-base font-bold text-accent-700 dark:text-accent-300; +} + +@utility area-container { + @apply rounded-r-md border-l-4 border-primary-500 py-2 pr-2 pl-4; + background-color: color-mix(in srgb, var(--theme-card) 40%, transparent); +} + +@utility area-title { + @apply text-sm font-semibold text-primary-600 dark:text-primary-400; +} + +@utility task-container { + @apply rounded-md border-l-2 border-surface-400/20 bg-black/5 p-3 dark:bg-white/5; +} + +/* Schedule Day Badges */ +@utility schedule-day { + @apply rounded bg-primary-500/20 px-2 py-1 text-xs text-primary-500; +} + +@utility schedule-day-weekend { + @apply rounded bg-accent-500/20 px-2 py-1 text-xs text-accent-500; +} + +/* Contact Cards */ +@utility contact-name { + @apply font-medium; + color: var(--theme-text); +} + +@utility contact-email { + @apply text-sm hover:text-primary-500; + color: var(--theme-text-secondary); +} + +/* List Item Divider */ +@utility list-divider { + @apply border-b pb-4; + border-color: var(--theme-border); +} + +/* Subtle Backgrounds */ +@utility bg-subtle { + @apply bg-black/5 dark:bg-white/5; +} + +@utility bg-subtle-hover { + @apply hover:bg-black/5 dark:hover:bg-white/10; +} + +@utility bg-subtle-active { + @apply active:bg-black/10 dark:active:bg-white/15; +} + +/* Card Variants by Section */ +@utility card-account { + @apply card-padded card-interactive border-l-4 border-l-primary-500/50; +} + +@utility card-service { + @apply card-padded card-interactive border-l-4 border-l-secondary-500/50; +} + +@utility card-project { + @apply card-padded card-interactive border-l-4 border-l-accent-500/50; +} + +@utility card-report { + @apply card-padded card-interactive border-l-4 border-l-accent2-500/50; +} + +@utility card-customer { + @apply card-padded card-interactive border-l-4 border-l-accent3-500/50; +} + +@utility card-profile { + @apply overflow-hidden card-padded card-interactive border-l-4 border-l-accent4-500/50; +} + +@utility card-invoice { + @apply card-padded card-interactive border-l-4 border-l-accent6-500/50; +} + +@utility card-specialty { + @apply card-padded card-interactive border-l-4 border-l-accent5-500/50; +} + +@utility card-calendar { + @apply card-padded card-interactive border-l-4 border-l-accent7-500/50; +} + +@utility card-message { + @apply card-padded card-interactive border-l-4 border-l-message-400/50; +} + +@utility card-notification { + @apply card-padded card-interactive border-l-4 border-l-notification-500/50; +} + +/* Section Header Variants */ +@utility section-header-primary { + @apply border-b border-primary-500/20 bg-primary-500/5; +} + +@utility section-header-secondary { + @apply border-b border-secondary-500/20 bg-secondary-500/5; +} + +@utility section-header-accent { + @apply border-b border-accent-500/20 bg-accent-500/5; +} + +@utility section-header-accent2 { + @apply border-b border-accent2-500/20 bg-accent2-500/5; +} + +@utility section-header-accent3 { + @apply border-b border-accent3-500/20 bg-accent3-500/5; +} + +@utility section-header-accent4 { + @apply border-b border-accent4-500/20 bg-accent4-500/5; +} + +@utility section-header-accent5 { + @apply border-b border-accent5-500/20 bg-accent5-500/5; +} + +@utility section-header-accent6 { + @apply border-b border-accent6-500/20 bg-accent6-500/5; +} + +@utility section-header-accent7 { + @apply border-b border-accent7-500/20 bg-accent7-500/5; +} + +@utility section-header-message { + @apply border-b border-message-400/20 bg-message-400/5; +} + +@utility section-header-notification { + @apply border-b border-notification-500/20 bg-notification-500/5; +} + +/* ============================================ + SEMANTIC STATE UTILITIES + ============================================ */ + +/* Danger/Error States */ +@utility bg-danger { + @apply bg-error-50 dark:bg-error-900/20; +} + +@utility text-danger { + @apply text-error-700 dark:text-error-400; +} + +@utility border-danger { + @apply border-error-200 dark:border-error-800; +} + +@utility danger-zone { + @apply rounded-lg border border-danger bg-danger p-6; +} + +@utility danger-zone-title { + @apply mb-2 text-lg font-semibold text-danger; +} + +@utility danger-zone-text { + @apply mb-4 text-sm text-error-600 dark:text-error-300; +} + +@utility btn-danger { + @apply inline-block rounded-lg bg-error-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-error-700 active:bg-error-800; +} + +/* Warning States */ +@utility bg-warning { + @apply bg-warning-50 dark:bg-warning-900/20; +} + +@utility text-warning { + @apply text-warning-700 dark:text-warning-300; +} + +@utility border-warning { + @apply border-warning-200 dark:border-warning-800; +} + +@utility warning-box { + @apply rounded-xl border border-warning bg-warning p-6; +} + +@utility warning-box-title { + @apply font-medium text-warning-800 dark:text-warning-200; +} + +@utility warning-box-text { + @apply mt-1 text-sm text-warning-700 dark:text-warning-300; +} + +@utility warning-box-icon { + @apply h-6 w-6 flex-shrink-0 text-warning-500; +} + +/* Success States */ +@utility bg-success { + @apply bg-success-50 dark:bg-success-900/20; +} + +@utility text-success { + @apply text-success-700 dark:text-success-400; +} + +@utility border-success { + @apply border-success-200 dark:border-success-700; +} + +@utility success-box { + @apply rounded-lg border border-success bg-success p-3; +} + +@utility success-box-title { + @apply flex items-center gap-2 text-sm font-medium text-success-700 dark:text-success-400; +} + +@utility success-box-text { + @apply mt-1 text-sm text-success-600 dark:text-success-300; +} + +/* Info States */ +@utility bg-info { + @apply bg-primary-50 dark:bg-primary-900/20; +} + +@utility text-info { + @apply text-primary-700 dark:text-primary-400; +} + +@utility border-info { + @apply border-primary-200 dark:border-primary-800; +} + +@utility info-box { + @apply rounded-lg border border-info bg-info p-3; +} + +/* Neutral/Inactive States */ +@utility badge-neutral { + @apply rounded bg-surface-100 px-2 py-0.5 text-xs font-semibold text-surface-600 dark:bg-surface-700 dark:text-surface-300; +} + +@utility badge-error { + @apply rounded bg-error-500/20 px-2 py-0.5 text-xs font-semibold text-error-600 dark:text-error-400; +} + +/* Form Input Borders (for checkboxes, radios) */ +@utility border-input { + @apply border-surface-300 dark:border-surface-600; +} + +/* Overlay Backgrounds */ +@utility bg-overlay { + @apply bg-black/50 dark:bg-black/70; +} + +@utility bg-overlay-heavy { + @apply bg-black/80; +} + +/* ============================================ + FORM UTILITIES + ============================================ */ + +/* Form input base - unifies input styling across all forms */ +@utility input-base { + @apply w-full rounded-lg border border-theme bg-theme px-3 py-2 text-theme placeholder:text-theme-muted focus:border-primary-500 focus:ring-1 focus:ring-primary-500 disabled:cursor-not-allowed disabled:opacity-50; +} + +/* Textarea base - same as input but allows resize */ +@utility textarea-base { + @apply w-full rounded-lg border border-theme bg-theme px-3 py-2 text-theme placeholder:text-theme-muted focus:border-primary-500 focus:ring-1 focus:ring-primary-500 disabled:cursor-not-allowed disabled:opacity-50; +} + +/* Select base */ +@utility select-base { + @apply w-full rounded-lg border border-theme bg-theme px-3 py-2 text-theme focus:border-primary-500 focus:ring-1 focus:ring-primary-500 disabled:cursor-not-allowed disabled:opacity-50; +} + +/* Form label */ +@utility form-label { + @apply mb-1.5 block text-sm font-medium text-theme; +} + +/* Required indicator */ +@utility required-indicator { + @apply text-red-500; +} + +/* Form button row */ +@utility form-actions { + @apply flex items-center gap-3 pt-4; +} + +/* Submit button */ +@utility btn-submit { + @apply flex-1 rounded-lg bg-primary-500 px-4 py-2 font-medium text-white transition-colors hover:bg-primary-600 disabled:cursor-not-allowed disabled:opacity-50; +} + +/* Cancel button */ +@utility btn-cancel { + @apply rounded-lg border border-theme bg-theme px-4 py-2 font-medium text-theme transition-colors hover:bg-black/5 disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-white/10; +} + +/* ============================================ + ALERT UTILITIES + ============================================ */ + +/* Error alert - replaces hardcoded red error boxes */ +@utility alert-error { + @apply rounded-lg border border-red-400 bg-red-50 p-3 text-sm text-red-700 dark:border-red-600 dark:bg-red-900/20 dark:text-red-400; +} + +/* ============================================ + INTERACTIVE STATE UTILITIES + ============================================ */ + +/* Interactive hover/active - unifies hover states */ +@utility interactive { + @apply transition-colors hover:bg-black/5 active:bg-black/10 dark:hover:bg-white/10 dark:active:bg-white/15; +} + +/* Delete button hover state */ +@utility btn-delete-hover { + @apply transition-colors hover:bg-red-50 hover:text-red-500 dark:hover:bg-red-900/20; +} + +/* ============================================ + MOBILE UTILITIES + ============================================ */ + +/* Mobile FAB (Floating Action Button) */ +@utility fab-primary { + @apply fixed right-4 bottom-20 z-30 flex h-14 w-14 items-center justify-center rounded-full bg-primary-500 text-white shadow-lg transition-colors hover:bg-primary-600 sm:hidden; +} + +/* ============================================ + SEARCH UTILITIES + ============================================ */ + +/* Search container */ +@utility search-container { + @apply relative; +} + +/* Search icon positioning */ +@utility search-icon { + @apply pointer-events-none absolute top-1/2 left-3 h-5 w-5 -translate-y-1/2 text-theme-muted; +} + +/* Search input with icon space */ +@utility search-input { + @apply input-base bg-theme-card py-2 pr-4 pl-10; +} + +/* ============================================ + CHECKBOX/RADIO UTILITIES + ============================================ */ + +/* Checkbox base */ +@utility checkbox-base { + @apply h-4 w-4 rounded border-surface-300 text-primary-500 focus:ring-primary-500 dark:border-surface-600; +} + +/* Checkbox label */ +@utility checkbox-label { + @apply text-sm text-theme; +} diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte new file mode 100644 index 0000000..39ad29f --- /dev/null +++ b/src/routes/login/+page.svelte @@ -0,0 +1,81 @@ + + + + Sign In - Nexus + + +
+
+
Redirecting to login...
+
+
+
diff --git a/src/routes/logout/+page.svelte b/src/routes/logout/+page.svelte new file mode 100644 index 0000000..9682837 --- /dev/null +++ b/src/routes/logout/+page.svelte @@ -0,0 +1,23 @@ + + + + Signing Out - Nexus + + +
+
+
Signing out...
+
+
+
diff --git a/src/routes/messages/+page.server.ts b/src/routes/messages/+page.server.ts new file mode 100644 index 0000000..da24641 --- /dev/null +++ b/src/routes/messages/+page.server.ts @@ -0,0 +1,22 @@ +import type { PageServerLoad } from './$types'; +import { GetMyConversationsStore } from '$houdini'; + +export const load: PageServerLoad = async (event) => { + const { url, locals } = event; + const tab = url.searchParams.get('tab') ?? 'all'; + const includeArchived = tab === 'archived'; + + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + const result = await new GetMyConversationsStore().fetch({ + ...fetchParams, + variables: { + includeArchived, + first: 50 + } + }); + + return { + conversations: result + }; +}; diff --git a/src/routes/messages/+page.svelte b/src/routes/messages/+page.svelte new file mode 100644 index 0000000..d288448 --- /dev/null +++ b/src/routes/messages/+page.svelte @@ -0,0 +1,284 @@ + + + + Messages | Nexus + + +
+ +
+
+ + + + + +

Messages

+
+ + + + + + New + +
+ + +
+ +
+ + + {#if filteredConversations.length === 0} +
+ + + +

+ {#if currentTab === 'unread'} + No unread messages + {:else if currentTab === 'archived'} + No archived conversations + {:else} + No conversations yet + {/if} +

+ {#if currentTab !== 'archived'} + + Start a Conversation + + {/if} +
+ {:else} + + {/if} +
+ + + + + + + diff --git a/src/routes/messages/[conversation]/+page.server.ts b/src/routes/messages/[conversation]/+page.server.ts new file mode 100644 index 0000000..318b8e0 --- /dev/null +++ b/src/routes/messages/[conversation]/+page.server.ts @@ -0,0 +1,21 @@ +import type { PageServerLoad } from './$types'; +import { GetConversationStore } from '$houdini'; + +export const load: PageServerLoad = async (event) => { + const { params, locals } = event; + + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + const result = await new GetConversationStore().fetch({ + ...fetchParams, + variables: { + id: params.conversation, + messageLimit: 100, + messageOffset: 0 + } + }); + + return { + conversation: result + }; +}; diff --git a/src/routes/messages/[conversation]/+page.svelte b/src/routes/messages/[conversation]/+page.svelte new file mode 100644 index 0000000..c67738f --- /dev/null +++ b/src/routes/messages/[conversation]/+page.svelte @@ -0,0 +1,479 @@ + + + + {conversation?.subject ?? 'Conversation'} | Nexus + + +
+ +
+
+ +
+

+ {conversation?.subject || 'Conversation'} +

+

+ {getConversationTypeLabel(conversation?.conversationType)} + {#if entityDisplay} + · + {#if entityDisplay.link} + + {entityDisplay.type}: {entityDisplay.name} + + {:else} + {entityDisplay.type}: {entityDisplay.name} + {/if} + {/if} +

+
+
+ + +
+ + + {#if actionsOpen} + +
(actionsOpen = false)} + onkeydown={(e) => e.key === 'Escape' && (actionsOpen = false)} + >
+
+ + + {#if conversation?.canDelete} + + {/if} +
+ {/if} +
+
+ + +
+ {#if !conversation} +
+

Conversation not found

+
+ {:else if messages.length === 0} +
+

No messages yet. Start the conversation!

+
+ {:else} +
+ {#each messages as message, index (message.id)} + {@const senderId = getSenderId(message.sender)} + {@const isOwn = isOwnMessage(senderId)} + + + {#if shouldShowDateSeparator(index)} +
+ + {formatDateSeparator(message.createdAt)} + +
+ {/if} + + +
+
+ + {#if !isOwn && !message.isSystemMessage} +

+ {getSenderName(message.sender)} +

+ {/if} + +
+ +
+ {#if message.replyTo} +
+ {getSenderName(message.replyTo.sender)} +

{message.replyTo.body}

+
+ {/if} +

{message.body}

+
+ + + {#if message.canDelete} + + {/if} +
+ + +

+ {formatMessageTime(message.createdAt)} +

+
+
+ {/each} +
+ {/if} +
+ + +
+
+
+ + +
+
+
+
diff --git a/src/routes/messages/new/+page.server.ts b/src/routes/messages/new/+page.server.ts new file mode 100644 index 0000000..11a4303 --- /dev/null +++ b/src/routes/messages/new/+page.server.ts @@ -0,0 +1,27 @@ +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async (event) => { + const { url } = event; + + // Get entity info from query params if provided + const entityType = url.searchParams.get('entityType'); + const entityId = url.searchParams.get('entityId'); + + // Get pre-fill params for messaging from service/project pages + const participantIdsParam = url.searchParams.get('participantIds'); + const conversationType = url.searchParams.get('conversationType'); + const subject = url.searchParams.get('subject'); + + // Parse comma-separated participant IDs + const participantIds = participantIdsParam + ? participantIdsParam.split(',').filter((id) => id.trim()) + : []; + + return { + entityType, + entityId, + participantIds, + conversationType, + subject + }; +}; diff --git a/src/routes/messages/new/+page.svelte b/src/routes/messages/new/+page.svelte new file mode 100644 index 0000000..e67bc60 --- /dev/null +++ b/src/routes/messages/new/+page.svelte @@ -0,0 +1,556 @@ + + + + New Conversation | Nexus + + +
+ +
+ +

New Conversation

+
+ + +
{ + e.preventDefault(); + handleSubmit(); + }} + class="space-y-6" + > + + {#if error} +
+ {error} +
+ {/if} + + + {#if !isCustomer} +
+ + +
+ {:else} +
+
+ + + + Your message will be sent to {adminProfile?.fullName ?? 'Support'} +
+
+ + + {#if customerEntityOptions.length > 0} +
+ + +
+ {/if} + {/if} + + +
+ + {#if isEntityLinked} + +
+
+ {subjectPrefix} +
+ +
+ {:else} + + {/if} +
+ + + {#if !isCustomer} +
+ + + {#if !participantsLocked} +
+ + +
+ {/if} + + {#if participantIds.length > 0} +
+ {#each participantIds as id (id)} + + {getParticipantName(id)} + {#if !participantsLocked} + + {/if} + + {/each} +
+ {/if} +
+ {/if} + + + {#if isEntityLinked} +
+
+ + + + Linked to {data.entityType} +
+
+ {/if} + + +
+ + +
+ + +
+ + +
+
+
diff --git a/src/routes/notifications/+page.server.ts b/src/routes/notifications/+page.server.ts new file mode 100644 index 0000000..4d89e63 --- /dev/null +++ b/src/routes/notifications/+page.server.ts @@ -0,0 +1,22 @@ +import type { PageServerLoad } from './$types'; +import { GetMyNotificationsStore } from '$houdini'; + +export const load: PageServerLoad = async (event) => { + const { url, locals } = event; + const unreadOnly = url.searchParams.get('tab') === 'unread'; + + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + const result = await new GetMyNotificationsStore().fetch({ + ...fetchParams, + variables: { + unreadOnly, + limit: 50, + offset: 0 + } + }); + + return { + notifications: result + }; +}; diff --git a/src/routes/notifications/+page.svelte b/src/routes/notifications/+page.svelte new file mode 100644 index 0000000..a6063d0 --- /dev/null +++ b/src/routes/notifications/+page.svelte @@ -0,0 +1,206 @@ + + + + Notifications | Nexus + + + diff --git a/src/routes/notifications/[notification]/+page.server.ts b/src/routes/notifications/[notification]/+page.server.ts new file mode 100644 index 0000000..ceb1e22 --- /dev/null +++ b/src/routes/notifications/[notification]/+page.server.ts @@ -0,0 +1,19 @@ +import type { PageServerLoad } from './$types'; +import { GetNotificationStore } from '$houdini'; + +export const load: PageServerLoad = async (event) => { + const { params, locals } = event; + + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + const result = await new GetNotificationStore().fetch({ + ...fetchParams, + variables: { + id: params.notification + } + }); + + return { + notification: result + }; +}; diff --git a/src/routes/notifications/[notification]/+page.svelte b/src/routes/notifications/[notification]/+page.svelte new file mode 100644 index 0000000..f5ba200 --- /dev/null +++ b/src/routes/notifications/[notification]/+page.svelte @@ -0,0 +1,241 @@ + + + + {notification?.subject ?? 'Notification'} | Nexus + + +
+ +
+
+ +

Notification

+
+ + +
+ + {#if !notification} +
+

Notification not found

+
+ {:else} +
+ +
+ +

{notification.subject}

+ + +

+ {formatTime(notification.createdAt)} + · + {formatDateTime(notification.createdAt)} +

+ + + {#if notification.body} +
+

{notification.body}

+
+ {/if} + + + {#if actionUrl} + + {/if} +
+ + + {#if notification.event} +
+

+ Event Details +

+ +
+ {#if notification.event.eventType} +
+
Event Type
+
+ {getEventTypeLabel(notification.event.eventType)} +
+
+ {/if} + + {#if notification.event.entityType} +
+
Entity Type
+
+ {getEntityTypeLabel(notification.event.entityType)} +
+
+ {/if} + + {#if notification.event.entityId} +
+
Entity ID
+
+ {notification.event.entityId} +
+
+ {/if} + + {#if notification.event.createdAt} +
+
Event Time
+
+ {formatDateTime(notification.event.createdAt)} +
+
+ {/if} +
+
+ {/if} + + + {#if notification.rule} +
+

+ Notification Rule +

+ +
+
+
Rule Name
+
{notification.rule.name}
+
+ + {#if notification.rule.description} +
+
Description
+
{notification.rule.description}
+
+ {/if} +
+
+ {/if} + + + {#if notification.metadata && Object.keys(notification.metadata).length > 0} +
+

+ Additional Data +

+
{JSON.stringify(
+							notification.metadata,
+							null,
+							2
+						)}
+
+ {/if} +
+ {/if} +
diff --git a/src/routes/pricing/+page.svelte b/src/routes/pricing/+page.svelte new file mode 100644 index 0000000..4a31fac --- /dev/null +++ b/src/routes/pricing/+page.svelte @@ -0,0 +1,372 @@ + + + + Pricing - Nexus Cleaning Solutions + + + + +
+ + +
+

+ Honest Pricing, No Surprises +

+

+ Commercial cleaning isn't one-size-fits-all, and neither is pricing. Here's a transparent + look at how we determine costs and what you can expect when working with us. +

+
+
+
+ + +
+ +
+

The Reality of Cleaning Costs

+
+

+ Let's be upfront: quality commercial cleaning has real costs. Professional-grade + chemicals, reliable equipment, trained staff, insurance, and the simple reality of getting + to your location all factor into what you pay. +

+

+ We've seen the race to the bottom in this industry. Companies quoting impossibly low + prices, then cutting corners or disappearing entirely. That's not how we operate. Our + pricing reflects the true cost of doing the job right, consistently, with people who are + paid fairly for their work. +

+

+ What you get in return is peace of mind. A cleaning partner who shows up, does the work + properly, and stands behind it. +

+
+
+
+
+ + +
+ +
+
+
+ + + + Recurring Service +
+

Janitorial Service Accounts

+

+ Our routine janitorial service starts at $400/month. This baseline typically covers: +

+
    +
  • + + + + Smaller facilities up to approximately 5,000 sq ft +
  • +
  • + + + + Once-per-week service frequency +
  • +
  • + + + + Single-stall restroom facilities +
  • +
  • + + + + Standard office/retail cleaning scope +
  • +
+

+ This represents our sweet spot: facilities that aren't so large they need a dedicated + crew, but substantial enough to benefit from professional service. It's the kind of + building where the owner or manager has real work to do and shouldn't be spending their + evenings emptying trash cans. +

+
+ +
+

What Affects Your Price

+
+
+

Square Footage

+

+ More space means more time. A 15,000 sq ft facility costs more than a 3,000 sq ft + office. That's just physics. +

+
+
+

Service Frequency

+

+ Once a week is our baseline. Twice weekly, three times, or daily service scales the + price accordingly. +

+
+
+

Restroom Count & Complexity

+

+ A single-stall bathroom is quick. A facility with multiple multi-stall restrooms + requires significantly more time and supplies. +

+
+
+

Facility Type

+

+ Medical offices have different requirements than retail stores. Industrial spaces + differ from professional offices. We tailor our approach and pricing accordingly. +

+
+
+

Scope of Work

+

+ Basic cleaning vs. detailed cleaning. Standard tasks vs. specialized needs. Your scope + determines your price. +

+
+
+
+
+
+
+ + +
+ +
+
+

What Goes Into the Price

+
+
+
+

Minimum Project Cost

+ $300 +
+

+ For smaller, straightforward projects under ideal conditions. +

+
+
+

+ Every project has real costs before we even start cleaning: professional-grade + chemicals, equipment, transportation, and the expertise to use them properly. +

+

+ We price our work to reflect what it actually costs to do it right and to pay our team + fairly for skilled work. That's not an apology. It's a commitment to the standard we hold ourselves to. +

+
+
+
+ +
+
+ + + + One-Time Projects +
+

Project-Based Work

+

+ Floor care, deep cleans, post-construction cleanup, and other one-time projects start as + low as $300, depending on the scope and + circumstances. +

+

+ Project pricing varies more than recurring service because the variables are endless. A + small office floor strip-and-wax is very different from a restaurant kitchen deep clean or + a post-renovation cleanup. +

+

+ We provide detailed quotes after understanding exactly what you need. No ballpark figures + that balloon later. Just honest pricing based on the actual work involved. +

+
+
+
+
+ + +
+ +
+

+ Why We Don't Race to the Bottom +

+
+

+ You can find cheaper cleaning services. We know that. There's always someone willing to + quote less, pay their workers less, skip the insurance, or cut corners on supplies. +

+

+ Here's what we've learned in this industry: you get what you pay for. The company quoting + half our price often delivers half the service, if they show up at all. We've taken over + accounts from bargain providers more times than we can count, always hearing the same + story: inconsistent service, missed cleanings, poor communication, and eventually, just... + nothing. +

+

+ Our pricing reflects what it actually costs to run a professional operation. We pay our + team fairly. We carry proper insurance. We use quality products. We answer our phones. We + show up. +

+

+ This isn't just about business. It's about respect for the work itself. Cleaning is + skilled labor that keeps workplaces healthy, safe, and functional. It deserves to be done + well and compensated fairly. That belief is at the core of who we are. +

+
+
+
+
+ + +
+ +
+

Getting Your Quote

+

+ We don't do blind quotes. Here's our process for getting you accurate pricing. +

+
+ +
+
+
+ 1 +
+

Initial Conversation

+

+ Tell us about your facility and what you're looking for. We'll ask questions to understand + your needs. +

+
+
+
+ 2 +
+

Site Walkthrough

+

+ We visit your location to see the space firsthand. This is how we give you an accurate + quote, not a guess. +

+
+
+
+ 3 +
+

Detailed Proposal

+

+ You receive a clear proposal outlining exactly what's included, how often, and what it + costs. No hidden fees. +

+
+
+
+
+ + +
+ +
+

Let's talk numbers

+

+ Every facility is different. Contact us for a free walkthrough and honest quote. No + obligation, no pressure. +

+ + Request a Quote + +
+
+
diff --git a/src/routes/privacy/+page.svelte b/src/routes/privacy/+page.svelte new file mode 100644 index 0000000..03fe7ab --- /dev/null +++ b/src/routes/privacy/+page.svelte @@ -0,0 +1,256 @@ + + + + Privacy Policy - Nexus + + + + +
+ + +
+

Privacy Policy

+

Last updated: December 2024

+
+
+
+ + +
+ +
+

The Short Version

+

+ We collect only what we need to provide our cleaning services and run Nexus. We don't sell + your data. We use it to serve you better, and we protect it like it's our own. +

+ +

Who We Are

+

+ Nexus Cleaning Solutions is a commercial cleaning company based in Macomb County, + Michigan. Nexus is our business management platform that helps us coordinate services with + our customers and team members. The Nexus platform is developed and maintained by Corellon + Digital; Nexus Cleaning Solutions is the data controller responsible for your + information. +

+ +

Information We Collect

+ +

Information You Give Us

+

When you use Nexus, you may provide:

+
    +
  • + Account information: Name, email address, phone number +
  • +
  • + Business information: Company name, service addresses, billing details +
  • +
  • + Communications: Messages you send through Nexus, contact form submissions +
  • +
  • + Content you upload: Photos or documents related to your services +
  • +
+ +

Information We Collect Automatically

+

When you use Nexus, we automatically collect:

+
    +
  • + Usage data: How you interact with Nexus (pages visited, features used) +
  • +
  • + Device information: Browser type, operating system, device type +
  • +
  • + Log data: IP address, access times, referring pages +
  • +
+ +

Cookies

+

+ We use cookies to keep you logged in and remember your preferences (like dark mode). We + don't use tracking cookies for advertising. The only cookies we use are: +

+
    +
  • + Session cookies: Encrypted cookies that keep you logged in (expire after 24 + hours of inactivity) +
  • +
  • + Preference cookies: Remember your settings like theme preference (stored locally + on your device) +
  • +
+ +

How We Use Your Information

+

We use your information to:

+
    +
  • Provide and improve our cleaning services
  • +
  • Schedule and coordinate service appointments
  • +
  • Send you invoices and process payments
  • +
  • Communicate with you about your services
  • +
  • Respond to your questions and requests
  • +
  • Send service notifications and updates
  • +
  • Maintain and improve Nexus
  • +
  • Comply with legal obligations
  • +
+ +

Who We Share Information With

+

+ We don't sell your personal information. We share it only when necessary: +

+ +

Service Providers

+

+ We work with trusted companies that help us run our business: +

+
    +
  • + Wave: Our accounting software for invoicing and payments +
  • +
  • + Email services: Gmail SMTP for verification emails and notifications +
  • +
  • + Have I Been Pwned: We check passwords against known breach databases to protect + your account (only a secure hash is checked, not your actual password) +
  • +
+ +

Data Hosting

+

+ Your data is stored on dedicated servers that we fully control and manage. We use + InterServer for physical server infrastructure, but all software, databases, and your + information are managed exclusively by us. Access to our servers is restricted to VPN and + strict firewall rules—no third-party cloud provider has access to your data. +

+ +

Our Team

+

+ Our team members access information they need to do their jobs—like your service address so + they know where to go, or your contact info so they can reach you about your appointment. +

+ +

Legal Requirements

+

+ We may share information if required by law, court order, or to protect the rights and + safety of Nexus, our customers, or others. +

+ +

How We Protect Your Information

+

+ We take security seriously and use enterprise-grade protection: +

+ +

Authentication Security

+
    +
  • + Password protection: Passwords are hashed using bcrypt and checked against + known breach databases to prevent use of compromised passwords +
  • +
  • + Multi-factor authentication: Support for security keys (WebAuthn/passkeys) + and authenticator apps (TOTP) +
  • +
  • + Secure sessions: Sessions are encrypted and automatically expire after 24 + hours of inactivity +
  • +
+ +

Data Protection

+
    +
  • All data is transmitted over encrypted connections (HTTPS/TLS)
  • +
  • Sensitive data is encrypted at rest using XChaCha20-Poly1305
  • +
  • Session cookies are secure, encrypted, and scoped to our domain
  • +
  • Access to customer data is limited to team members who need it
  • +
+

+ No system is 100% secure, but we work hard to protect your information and will notify you + if there's ever a breach that affects your data. +

+ +

How Long We Keep Your Information

+

We keep your information for as long as:

+
    +
  • You have an active account with us
  • +
  • We need it to provide services to you
  • +
  • + We're required to keep it for legal or tax purposes (typically 7 years for financial + records) +
  • +
+

+ If you close your account, we'll delete or anonymize your personal information, except where + we need to keep it for legal compliance. +

+ +

Your Rights

+

You have the right to:

+
    +
  • + Access your data: See what information we have about you +
  • +
  • + Correct your data: Update inaccurate information +
  • +
  • + Delete your data: Request we remove your personal information +
  • +
  • + Export your data: Get a copy of your data in a portable format +
  • +
  • + Opt out of communications: Unsubscribe from marketing emails (you'll still + get essential service communications) +
  • +
+

+ To exercise any of these rights, contact us using the information below. +

+ +

Children's Privacy

+

+ Nexus is designed for businesses and is not intended for anyone under 18. We don't knowingly + collect personal information from minors. +

+ +

Changes to This Policy

+

+ We may update this Privacy Policy from time to time. If we make significant changes, we'll + let you know through Nexus or by email. The "Last updated" date at the top shows when this + policy was last revised. +

+ +

Contact Us

+

+ Questions about this Privacy Policy or how we handle your data? Reach out: +

+ +
+
+
diff --git a/src/routes/register/+page.svelte b/src/routes/register/+page.svelte new file mode 100644 index 0000000..2a76e90 --- /dev/null +++ b/src/routes/register/+page.svelte @@ -0,0 +1,33 @@ + + + + Sign Up - Nexus + + +
+
+
Redirecting to registration...
+
+
+
diff --git a/src/routes/services/+page.svelte b/src/routes/services/+page.svelte new file mode 100644 index 0000000..2b8a508 --- /dev/null +++ b/src/routes/services/+page.svelte @@ -0,0 +1,547 @@ + + + + Our Services - Nexus Cleaning Solutions + + + + +
+ + +
+

Our Services

+

+ From routine janitorial maintenance to specialized deep cleaning projects, Nexus + Cleaning Solutions provides comprehensive commercial cleaning services throughout Macomb + County and the greater Detroit area. +

+
+
+
+ + +
+ +
+
+
+ + + + Recurring Service +
+

Janitorial Service

+

+ Our core service offering. We work with you to develop a customized scope of work that + covers every area of your facility, with task frequencies tailored to your needs and + schedule. +

+
+
+ + + + Daily, weekly, and monthly task scheduling +
+
+ + + + Restroom cleaning and sanitation +
+
+ + + + Trash removal and recycling +
+
+ + + + Dusting, vacuuming, and mopping +
+
+ + + + Break room and common area maintenance +
+
+ + + + Window and glass cleaning (interior) +
+
+
+
+ Office cleaning service +
+
+
+
+ + +
+ +
+
+ Floor care and maintenance +
+
+
+ + + + Project-Based +
+

Floor Care

+

+ Keep your floors looking their best with professional floor care services. Whether you + need regular maintenance or a complete restoration, we have the equipment and expertise to + handle it. +

+
+
+ + + + Strip and wax (VCT, LVT, and other hard floors) +
+
+ + + + High-speed buffing and burnishing +
+
+ + + + Scrub and recoat maintenance +
+
+ + + + Carpet extraction and deep cleaning +
+
+ + + + Tile and grout cleaning +
+
+
+
+
+
+ + +
+ +
+
+
+ + + + Specialty Service +
+

Commercial Kitchen Cleaning

+

+ A thorough top-down cleaning service for commercial kitchens. We cover everything from + ceiling tiles to floors, giving your kitchen a deep clean that goes beyond daily + maintenance. +

+
+
+ + + + Ceiling tile cleaning and degreasing +
+
+ + + + Wall washing and degreasing +
+
+ + + + Equipment exterior cleaning +
+
+ + + + Conveyor pizza oven cleaning +
+
+ + + + Floor degreasing and deep cleaning +
+
+ +
+
+ Commercial kitchen cleaning +
+
+
+
+ + +
+ +
+

Specialty & One-Time Projects

+

+ Beyond our core services, we handle a variety of specialty cleaning projects. If you have a + unique cleaning need, let's talk. +

+
+ +
+
+

Impact Cleaning

+

+ Intensive deep cleaning to reset your facility. Perfect for spaces that need a fresh start + or have fallen behind on maintenance. +

+
+
+

Turnovers / Move-Out Cleaning

+

+ Deep cleaning for tenant transitions and property turnovers. We work with property + managers to get spaces move-in ready. +

+
+
+

Post-Construction Cleanup

+

+ Remove construction dust and debris to prepare your space for occupancy. +

+
+
+

Window Cleaning

+

+ Interior and exterior window cleaning for a spotless finish. +

+
+
+

Emergency Response

+

+ Rapid response cleaning for unexpected situations. +

+
+
+

Something Else?

+

+ Have a unique cleaning challenge? Give us a call. You'd be surprised what we've tackled. + We love a good project. +

+
+
+
+
+ + +
+ +
+

How It Works

+

+ Getting started with Nexus is simple. Here's what to expect. +

+
+ +
+
+
+ 1 +
+

Consultation

+

+ We visit your facility to understand your needs and assess the scope of work. +

+
+
+
+ 2 +
+

Custom Proposal

+

+ You receive a detailed proposal outlining services, frequency, and pricing. +

+
+
+
+ 3 +
+

Onboarding

+

+ We set up your service schedule and provide access to your customer portal. +

+
+
+
+ 4 +
+

Service Begins

+

+ Our team arrives on schedule to deliver consistent, quality cleaning. +

+
+
+
+
+ + +
+ +
+

Proudly Serving Southeast Michigan

+

+ Based in Macomb County, we provide commercial cleaning services to businesses throughout + Sterling Heights, Warren, Clinton Township, Shelby Township, and the surrounding + communities. We also serve Oakland County and the greater Metro Detroit area. +

+
+
+
+ + +
+ +
+

Let's discuss your cleaning needs

+

+ Every facility is different. Contact us for a free consultation and customized quote. +

+ + Request a Quote + +
+
+
diff --git a/src/routes/standard/+page.svelte b/src/routes/standard/+page.svelte new file mode 100644 index 0000000..75dd75e --- /dev/null +++ b/src/routes/standard/+page.svelte @@ -0,0 +1,438 @@ + + + + The Nexus Standard - Our Commitment to Excellence + + + + +
+ + +
+

+ The Nexus Standard +

+

+ Excellence isn't a buzzword. It's how we operate. The Nexus Standard is our promise that + every service, every visit, and every interaction meets the level of quality your business + deserves. +

+
+
+
+ + +
+ +
+
+

What It Means

+
+

+ When we say "Nexus Standard", we're talking about a measurable commitment, not just + good intentions. It means your facility gets the same thorough attention whether it's + our first visit or our hundredth. +

+

+ It means when something isn't right, we make it right. No excuses, no runaround. If + you're not satisfied with a service, we'll return to address it at no additional cost. +

+

+ This standard exists because we believe cleaning work deserves respect, and that starts with doing it well. When we hold ourselves to a higher bar, everyone + benefits: our clients get reliable service, and our team takes pride in work that + matters. +

+
+
+
+ Quality cleaning service +
+
+
+
+ + +
+ +
+

The Four Pillars

+

+ Every aspect of our service is built on these foundational commitments. +

+
+ +
+ +
+
+ + + +
+

Consistency

+

+ A clean building one week and a messy one the next isn't service. It's a gamble. We use + detailed checklists and documented scopes of work to ensure every task gets completed, + every time. +

+
    +
  • + + + + Documented task lists for every visit +
  • +
  • + + + + Same cleaning team when possible +
  • +
  • + + + + Routine quality inspections +
  • +
+
+ + +
+
+ + + +
+

Accountability

+

+ We own our work. When something falls short, we don't make excuses. We fix it. Our + satisfaction guarantee means you never pay for service that doesn't meet expectations. +

+
    +
  • + + + + Satisfaction guarantee on every service +
  • +
  • + + + + Direct line to management +
  • +
  • + + + + Rapid response to concerns +
  • +
+
+ + +
+
+ + + +
+

Communication

+

+ No surprises, no ghosting. We keep you informed about your service, respond promptly to + questions, and proactively let you know if anything changes. +

+
    +
  • + + + + Customer portal for service tracking +
  • +
  • + + + + Prompt responses to inquiries +
  • +
  • + + + + Proactive schedule updates +
  • +
+
+ + +
+
+ + + +
+

Professionalism

+

+ We represent your facility when we're in it. Our team arrives prepared, treats your space + with respect, and conducts themselves in a way that reflects well on both of us. +

+
    +
  • + + + + Background-checked team members +
  • +
  • + + + + Fully insured operations +
  • +
  • + + + + Respectful of your space and people +
  • +
+
+
+
+
+ + +
+ +
+
+ + + +
+

Our Guarantee

+

+ If you're not satisfied with any service we provide, let us know within 24 hours and we'll + return to make it right, at no additional charge. No fine print, no hoops to jump through. +

+

+ This isn't just a policy. It's a reflection of how we do business. We'd rather lose money on + a re-clean than lose your trust. +

+
+
+
+ + +
+ +
+

Experience the difference

+

+ Ready to see what the Nexus Standard looks like in your facility? Let's talk. +

+ + Request a Consultation + +
+
+
diff --git a/src/routes/team/+layout.server.ts b/src/routes/team/+layout.server.ts new file mode 100644 index 0000000..034adf9 --- /dev/null +++ b/src/routes/team/+layout.server.ts @@ -0,0 +1,57 @@ +import type { LayoutServerLoad } from './$types'; +import { redirect, error } from '@sveltejs/kit'; +import { TeamDashboardStore } from '$houdini'; +import { getCurrentMonth } from '$lib/utils/date'; + +export const load: LayoutServerLoad = async (event) => { + const { url, parent, locals, depends } = event; + + // Register dependency for invalidation + depends('app:dashboard'); + + const parentData = await parent(); + const me = parentData.user?.data?.me; + + // Not authenticated + if (!me) { + const returnTo = encodeURIComponent(url.pathname + url.search); + throw redirect(307, `/login?return_to=${returnTo}`); + } + + // Team routes require team profile (any role: admin, team_leader, team_member) + if (me.__typename !== 'TeamProfileType') { + throw error(403, 'This area is only accessible to team members'); + } + + // Get month from query params or use current month + const month = url.searchParams.get('month') || getCurrentMonth(); + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + // Single consolidated query for all team dashboard data + const dashboardResult = await new TeamDashboardStore().fetch({ + ...fetchParams, + variables: { teamProfileId: me.id, month } + }); + + const dashboard = dashboardResult.data?.teamDashboard; + + // Filter services and projects by status client-side + const services = dashboard?.services ?? []; + const projects = dashboard?.projects ?? []; + + return { + ...parentData, + services: { + scheduled: services.filter((s) => s.status === 'SCHEDULED'), + inProgress: services.filter((s) => s.status === 'IN_PROGRESS'), + completed: services.filter((s) => s.status === 'COMPLETED') + }, + projects: { + scheduled: projects.filter((p) => p.status === 'SCHEDULED'), + inProgress: projects.filter((p) => p.status === 'IN_PROGRESS'), + completed: projects.filter((p) => p.status === 'COMPLETED') + }, + reports: dashboard?.reports ?? [], + filters: { month } + }; +}; diff --git a/src/routes/team/+layout.svelte b/src/routes/team/+layout.svelte new file mode 100644 index 0000000..ae9c9d0 --- /dev/null +++ b/src/routes/team/+layout.svelte @@ -0,0 +1,7 @@ + + +{@render children()} diff --git a/src/routes/team/+page.svelte b/src/routes/team/+page.svelte new file mode 100644 index 0000000..7cac4da --- /dev/null +++ b/src/routes/team/+page.svelte @@ -0,0 +1,540 @@ + + + + Dashboard - Nexus + + +
+ + + + {#snippet subtitleSnippet({ toggleMenu })} + Here's your weekly snapshot. Use the or bottom nav to access your work. + {/snippet} + + + +
+ +
+
+
+ 1 +
+
+

Find Your Assigned Work

+

+ Browse your assigned services + and + projects. Use the month filter to find work scheduled for a specific time period. +

+
+
+
+ + +
+
+
+ 2 +
+
+

Log Your Completed Work

+

+ Open a service or project to start a new session. Record the tasks you've completed + and any notes about the work performed. +

+
+
+
+ + +
+
+
+ 3 +
+
+

Review Your Reports

+

+ At the end of each month, your completed work is summarized into a report. View your labor totals and payment breakdowns. +

+
+
+
+
+ + +
+ +
+
+
+

+ Services +

+ View all +
+
+ +
+ +
+

This Week

+ {#if thisWeekServices.length === 0} +

No services scheduled this week

+ {:else} + + {/if} +
+ + +
+

Recently Completed

+ {#if recentlyCompletedServices.length === 0} +

No completed services yet

+ {:else} + + {/if} +
+
+
+ + +
+
+
+

Projects

+ View all +
+
+ +
+ +
+

This Week

+ {#if thisWeekProjects.length === 0} +

No projects scheduled this week

+ {:else} + + {/if} +
+ + +
+

Recently Completed

+ {#if recentlyCompletedProjects.length === 0} +

No completed projects yet

+ {:else} + + {/if} +
+
+
+
+
+
diff --git a/src/routes/team/accounts/+page.svelte b/src/routes/team/accounts/+page.svelte new file mode 100644 index 0000000..bc690b5 --- /dev/null +++ b/src/routes/team/accounts/+page.svelte @@ -0,0 +1,82 @@ + + + + Accounts - Team - Nexus + + + diff --git a/src/routes/team/accounts/[account]/+page.server.ts b/src/routes/team/accounts/[account]/+page.server.ts new file mode 100644 index 0000000..020ae0f --- /dev/null +++ b/src/routes/team/accounts/[account]/+page.server.ts @@ -0,0 +1,18 @@ +import type { PageServerLoad } from './$types'; +import { GetAccountStore } from '$houdini'; + +export const load: PageServerLoad = async (event) => { + const { params, locals } = event; + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + // Fetch full account data with schedules, scopes, contacts + const accountResult = await new GetAccountStore().fetch({ + ...fetchParams, + variables: { id: params.account } + }); + + return { + accountId: params.account, + account: accountResult.data?.account ?? null + }; +}; diff --git a/src/routes/team/accounts/[account]/+page.svelte b/src/routes/team/accounts/[account]/+page.svelte new file mode 100644 index 0000000..3552a49 --- /dev/null +++ b/src/routes/team/accounts/[account]/+page.svelte @@ -0,0 +1,293 @@ + + + + {account?.name ?? 'Account'} - Team - Nexus + + +
+ + {#if !account} +
+ + + +

Account Not Found

+

+ The account you're looking for doesn't exist or you don't have access to it. +

+ Back to Accounts +
+ {:else} + + +
+ +
+
+

+ {#if account.addresses && account.addresses.length === 1} + {account.addresses[0].name || 'Primary Service Address'} + {:else} + Locations + {/if} +

+ + {#if account.addresses && account.addresses.length > 0} +
+ {#each account.addresses as location (location.id)} + {@const activeSchedule = getActiveSchedule(location.schedules)} + {@const activeScope = getActiveScope(location.scopes)} + {@const locationName = location.name || 'Primary Service Address'} +
+ + + + {locationName} + {#if location.isPrimary && account.addresses.length > 1} + Primary + {/if} + + + {location.streetAddress}, {location.city}, {location.state} + {location.zipCode} + + + + + + + + {#if location.notes} +

+ {location.notes} +

+ {/if} + + + {#if activeSchedule} +
+

Schedule

+
+
+
+ {activeSchedule.name || 'Schedule'} +
+ {#if activeSchedule.scheduleException} + {activeSchedule.scheduleException} + {/if} +
+
+ {#if activeSchedule.sundayService} + Sun + {/if} + {#if activeSchedule.mondayService} + Mon + {/if} + {#if activeSchedule.tuesdayService} + Tue + {/if} + {#if activeSchedule.wednesdayService} + Wed + {/if} + {#if activeSchedule.thursdayService} + Thu + {/if} + {#if activeSchedule.fridayService} + Fri + {/if} + {#if activeSchedule.saturdayService} + Sat + {/if} + {#if activeSchedule.weekendService} + Weekend + {/if} +
+
+
+ {:else if location.schedules.length > 0} +
+

Schedule

+

+ {location.schedules.length} schedule{location.schedules.length === 1 + ? '' + : 's'} (none active) +

+
+ {/if} + + + {#if activeScope} +
+ + Scope + + + + + +
+
+
{activeScope.name}
+ {#if activeScope.description} +

+ {activeScope.description} +

+ {/if} + + {#if activeScope.areas && activeScope.areas.length > 0} +
+ {#each activeScope.areas.toSorted((a, b) => a.order - b.order) as area (area.id)} +
+ + {area.name} + + + + + + {#if area.tasks && area.tasks.length > 0} +
+ {#each area.tasks.toSorted((a, b) => a.order - b.order) as task (task.id)} +
+

+ {task.checklistDescription || task.description} +

+ {#if task.frequency} +

+ Frequency: + {task.frequency.toLowerCase()} +

+ {/if} +
+ {/each} +
+ {/if} +
+ {/each} +
+ {/if} +
+
+
+ {:else if location.scopes.length > 0} +
+

Scope of Work

+

+ {location.scopes.length} scope{location.scopes.length === 1 ? '' : 's'} (none + active) +

+
+ {/if} +
+ {/each} +
+ {:else} +

No locations found.

+ {/if} +
+
+ + +
+
+

Contacts

+ + {#if account.contacts && account.contacts.length > 0} +
+ {#each account.contacts.filter((c) => c.isActive) as contact, i (contact.id)} +
c.isActive).length - 1 + ? 'list-divider' + : ''} + > +

+ {contact.fullName} + {#if contact.isPrimary} + Primary + {/if} +

+ {#if contact.email} + + {contact.email} + + {/if} + {#if contact.notes} +

{contact.notes}

+ {/if} +
+ {/each} +
+ {:else} +

No contacts found.

+ {/if} +
+
+
+ {/if} +
+
diff --git a/src/routes/team/profile/+page.svelte b/src/routes/team/profile/+page.svelte new file mode 100644 index 0000000..673e8b2 --- /dev/null +++ b/src/routes/team/profile/+page.svelte @@ -0,0 +1,184 @@ + + + + My Profile - Nexus + + +
+ + + +
+ +
+
+
+ + + +
+

Nexus Account

+

Your authentication and identity information

+
+
+
+ +
+ {#if kratosTraits} +
+
+
First Name
+
{kratosTraits.name?.first ?? 'Not set'}
+
+
+
Last Name
+
{kratosTraits.name?.last ?? 'Not set'}
+
+
+
Email
+
{kratosTraits.email ?? 'Not set'}
+
+
+
Phone
+
{kratosTraits.phone ?? 'Not set'}
+
+
+ {:else} +

Loading account information...

+ {/if} +
+ + +
+ + + {#if user} +
+
+
+ +
+ {user.fullName?.charAt(0).toUpperCase() ?? '?'} +
+
+
+

Profile Information

+ {#if userRole} + + {roleLabels[userRole] ?? userRole} + + {/if} +
+

Your team profile data

+
+
+
+ +
+
+
+
Full Name
+
{user.fullName ?? 'Not set'}
+
+
+
Email
+
{user.email ?? 'Not set'}
+
+
+
Phone
+
{user.phone ?? 'Not set'}
+
+ {#if userRole} +
+
Role
+
{roleLabels[userRole] ?? userRole}
+
+ {/if} +
+
+
+ {/if} +
+
+
diff --git a/src/routes/team/projects/+page.svelte b/src/routes/team/projects/+page.svelte new file mode 100644 index 0000000..a58de39 --- /dev/null +++ b/src/routes/team/projects/+page.svelte @@ -0,0 +1,191 @@ + + + + {#snippet emptyIcon()} + + + + {/snippet} + + {#snippet item(project: Project)} + {@const displayInfo = getProjectDisplayInfo(project)} + {@const teamCount = countNonAdminTeamMembers(project.teamMembers)} + +
+
+
+ +
+ + {formatDate(project.date)} + + + {project.status.replace('_', ' ')} + +
+ + {#if displayInfo.accountInfo} +

+ {displayInfo.accountInfo.accountName} +

+ {:else if displayInfo.customerInfo} +

+ {displayInfo.customerInfo.customerName} +

+ {:else} +

+ No account/customer linked +

+ {/if} + +

{project.name}

+ + {#if displayInfo.address} +

{displayInfo.address}

+ {/if} + +

+ {teamCount} team member{teamCount !== 1 ? 's' : ''} assigned +

+ + {#if project.notes} +

{project.notes}

+ {/if} +
+ + + +
+
+
+ {/snippet} +
diff --git a/src/routes/team/projects/[project]/+page.server.ts b/src/routes/team/projects/[project]/+page.server.ts new file mode 100644 index 0000000..b072ef0 --- /dev/null +++ b/src/routes/team/projects/[project]/+page.server.ts @@ -0,0 +1,99 @@ +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { + GetProjectStore, + ProjectSessionByProjectStore, + ProjectScopeStore, + ProjectSessionPhotosStore, + ProjectSessionVideosStore +} from '$houdini'; +import { fromGlobalId, toGlobalId } from '$lib/utils/relay'; + +export const load: PageServerLoad = async (event) => { + const { params, parent, locals } = event; + const data = await parent(); + const projectId = params.project; + + // Fetch params for Houdini stores + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + // Search through all status categories to find the project in parent data + const allProjects = [ + ...(data.projects.scheduled ?? []), + ...(data.projects.inProgress ?? []), + ...(data.projects.completed ?? []) + ]; + + const foundProject = allProjects.find((p) => p.id === projectId); + + // If not found in parent data, fetch directly + let project; + if (foundProject) { + project = foundProject; + } else { + const projectStore = new GetProjectStore(); + const projectData = await projectStore.fetch({ ...fetchParams, variables: { id: projectId } }); + project = projectData.data?.project; + + if (!project) { + throw error(404, 'Project not found'); + } + } + + // Get the project UUID from the global ID for querying + const projectUuid = fromGlobalId(projectId); + + // Fetch session data if project is in progress or completed + let session = null; + if (project.status === 'IN_PROGRESS' || project.status === 'COMPLETED') { + const sessionStore = new ProjectSessionByProjectStore(); + const sessionData = await sessionStore.fetch({ + ...fetchParams, + variables: { projectId: projectUuid } + }); + session = sessionData?.data?.projectSessions?.[0] ?? null; + + // Fetch photos and videos separately to handle missing file errors gracefully + if (session) { + const sessionUuid = fromGlobalId(session.id); + const [photosResult, videosResult] = await Promise.all([ + new ProjectSessionPhotosStore() + .fetch({ ...fetchParams, variables: { sessionId: sessionUuid } }) + .catch((err) => { + console.error('Failed to fetch session photos:', err); + return { data: { projectSessionImages: [] } }; + }), + new ProjectSessionVideosStore() + .fetch({ ...fetchParams, variables: { sessionId: sessionUuid } }) + .catch((err) => { + console.error('Failed to fetch session videos:', err); + return { data: { projectSessionVideos: [] } }; + }) + ]); + + // Merge photos and videos into session object + session = { + ...session, + photos: photosResult?.data?.projectSessionImages ?? [], + videos: videosResult?.data?.projectSessionVideos ?? [] + }; + } + } + + // Fetch scope data if project has a scopeId + // Note: project.scopeId is a UUID, but projectScope query expects a Relay Global ID + let scopeData = null; + if (project.scopeId) { + const scopeStore = new ProjectScopeStore(); + const scopeGlobalId = toGlobalId('ProjectScopeType', project.scopeId); + scopeData = await scopeStore.fetch({ ...fetchParams, variables: { id: scopeGlobalId } }); + } + + return { + project, + projectId, + projectUuid, + session, + scope: scopeData?.data?.projectScope ?? null + }; +}; diff --git a/src/routes/team/projects/[project]/+page.svelte b/src/routes/team/projects/[project]/+page.svelte new file mode 100644 index 0000000..5c36f4e --- /dev/null +++ b/src/routes/team/projects/[project]/+page.svelte @@ -0,0 +1,442 @@ + + + + {displayName} - Projects - Nexus + + +
+ + + + + + {#if project.status === 'IN_PROGRESS' && session} +
+ + selectedTaskIds.clear()} + onAddNote={handleAddNote} + onUpdateNote={handleUpdateNote} + onDeleteNote={handleDeleteNote} + onUploadPhoto={handleUploadPhoto} + onUploadVideo={handleUploadVideo} + onUpdatePhoto={handleUpdatePhoto} + onUpdateVideo={handleUpdateVideo} + onDeletePhoto={handleDeletePhoto} + onDeleteVideo={handleDeleteVideo} + /> +
+ {:else if project.status === 'COMPLETED' && session} +
+ + +
+ {:else if project.status === 'SCHEDULED'} + goto('/team/projects', { invalidateAll: true })} + /> + {/if} +
+
diff --git a/src/routes/team/reports/+page.svelte b/src/routes/team/reports/+page.svelte new file mode 100644 index 0000000..bded7b3 --- /dev/null +++ b/src/routes/team/reports/+page.svelte @@ -0,0 +1,137 @@ + + + + Reports - Team - Nexus + + +
+ + + + {#if sortedReports.length === 0} +
+ + + +

No Reports Yet

+

+ Your monthly labor reports will appear here once you have completed work logged in the + system. +

+
+ {:else} + + {/if} +
+
diff --git a/src/routes/team/reports/[report]/+page.server.ts b/src/routes/team/reports/[report]/+page.server.ts new file mode 100644 index 0000000..bd40b37 --- /dev/null +++ b/src/routes/team/reports/[report]/+page.server.ts @@ -0,0 +1,29 @@ +import { error } from '@sveltejs/kit'; +import { GetTeamReportStore } from '$houdini'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async (event) => { + const { params, locals, parent } = event; + const reportId = params.report; + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + // Get the lookups from parent layout data + const parentData = await parent(); + + // Fetch report details + const reportResult = await new GetTeamReportStore().fetch({ + ...fetchParams, + variables: { id: reportId } + }); + + const report = reportResult.data?.report; + if (!report) { + throw error(404, 'Report not found'); + } + + return { + report, + accountLookup: parentData.accountLookup, + customerLookup: parentData.customerLookup + }; +}; diff --git a/src/routes/team/reports/[report]/+page.svelte b/src/routes/team/reports/[report]/+page.svelte new file mode 100644 index 0000000..575b7de --- /dev/null +++ b/src/routes/team/reports/[report]/+page.svelte @@ -0,0 +1,322 @@ + + + + {reportTitle} Report - Nexus + + +
+ + + + +
+

Labor Summary

+ +
+ +
+
+
+ + + + +
+
+

Services

+

+ {report.services.length} service{report.services.length !== 1 ? 's' : ''} +

+
+
+

+ {formatCurrency(report.servicesLaborTotal)} +

+
+ + +
+
+
+ + + +
+
+

Projects

+

+ {report.projects.length} project{report.projects.length !== 1 ? 's' : ''} +

+
+
+

+ {formatCurrency(report.projectsLaborTotal)} +

+
+ + +
+
+
+ + + +
+
+

Total Labor

+

Combined earnings for the month

+
+
+

+ {formatCurrency(report.totalLaborValue)} +

+
+
+
+ + +
+

Services

+ + {#if report.services.length === 0} +

No services in this report.

+ {:else} +
+ {#each report.services as service (service.id)} + {@const labor = serviceLaborMap.get(service.id)} +
+
+
+

{getServiceDisplayName(service)}

+ {#if labor} +
+ Total: {formatCurrency(labor.totalLaborRate)} + Team: {labor.teamMemberCount} + + Share: {formatCurrency(labor.laborShare)} + +
+ {/if} +
+
+
+ {/each} +
+ {/if} +
+ + +
+

Projects

+ + {#if report.projects.length === 0} +

No projects in this report.

+ {:else} +
+ {#each report.projects as project (project.id)} + {@const labor = projectLaborMap.get(project.id)} +
+
+
+

{getProjectDisplayName(project)}

+ {#if labor} +
+ Total: {formatCurrency(labor.totalLaborAmount)} + Team: {labor.teamMemberCount} + + Share: {formatCurrency(labor.laborShare)} + +
+ {/if} +
+
+
+ {/each} +
+ {/if} +
+ + + {#if report.laborBreakdown && (report.laborBreakdown.services.length > 0 || report.laborBreakdown.projects.length > 0)} +
+

Labor Breakdown

+ + +
+
+
+ Services Total: + {formatCurrency(report.laborBreakdown.servicesTotal)} +
+
+ Projects Total: + {formatCurrency(report.laborBreakdown.projectsTotal)} +
+
+ Grand Total: + {formatCurrency(report.laborBreakdown.grandTotal)} +
+
+
+
+ {/if} + + + Back to Reports +
+
diff --git a/src/routes/team/services/+page.svelte b/src/routes/team/services/+page.svelte new file mode 100644 index 0000000..37bd18f --- /dev/null +++ b/src/routes/team/services/+page.svelte @@ -0,0 +1,167 @@ + + + + {#snippet emptyIcon()} + + + + + {/snippet} + + {#snippet item(service: Service)} + {@const accountInfo = getAccountInfo(service.accountAddressId)} + {@const address = accountInfo ? formatAddress(accountInfo) : null} + {@const teamCount = countNonAdminTeamMembers(service.teamMembers)} + +
+
+
+ +
+ + {formatDate(service.date)} + + + {service.status.replace('_', ' ')} + +
+ + {#if accountInfo} +

{accountInfo.accountName}

+ {:else} +

No account linked

+ {/if} + + {#if accountInfo} +

{accountInfo.addressName}

+ {/if} + + {#if address} +

{address}

+ {/if} + +

+ {teamCount} team member{teamCount !== 1 ? 's' : ''} assigned +

+ + {#if service.notes} +

{service.notes}

+ {/if} +
+ + + +
+
+
+ {/snippet} +
diff --git a/src/routes/team/services/[service]/+page.server.ts b/src/routes/team/services/[service]/+page.server.ts new file mode 100644 index 0000000..0201346 --- /dev/null +++ b/src/routes/team/services/[service]/+page.server.ts @@ -0,0 +1,99 @@ +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { + GetServiceStore, + ServiceSessionByServiceStore, + ScopeByAddressStore, + ServiceSessionPhotosStore, + ServiceSessionVideosStore +} from '$houdini'; +import { fromGlobalId } from '$lib/utils/relay'; + +export const load: PageServerLoad = async (event) => { + const { params, parent, locals } = event; + const data = await parent(); + const serviceId = params.service; + + // Fetch params for Houdini stores + const fetchParams = { event, metadata: { cookie: locals.cookie } }; + + // Search through all status categories to find the service in parent data + const allServices = [ + ...(data.services.scheduled ?? []), + ...(data.services.inProgress ?? []), + ...(data.services.completed ?? []) + ]; + + const foundService = allServices.find((s) => s.id === serviceId); + + // If not found in parent data, fetch directly + let service; + if (foundService) { + service = foundService; + } else { + const serviceStore = new GetServiceStore(); + const serviceData = await serviceStore.fetch({ ...fetchParams, variables: { id: serviceId } }); + service = serviceData.data?.service; + + if (!service) { + throw error(404, 'Service not found'); + } + } + + // Get the service UUID from the global ID for querying + const serviceUuid = fromGlobalId(serviceId); + + // Fetch session data if service is in progress or completed + let session = null; + if (service.status === 'IN_PROGRESS' || service.status === 'COMPLETED') { + const sessionStore = new ServiceSessionByServiceStore(); + const sessionData = await sessionStore.fetch({ + ...fetchParams, + variables: { serviceId: serviceUuid } + }); + session = sessionData?.data?.serviceSessions?.[0] ?? null; + + // Fetch photos and videos separately to handle missing file errors gracefully + if (session) { + const sessionUuid = fromGlobalId(session.id); + const [photosResult, videosResult] = await Promise.all([ + new ServiceSessionPhotosStore() + .fetch({ ...fetchParams, variables: { sessionId: sessionUuid } }) + .catch((err) => { + console.error('Failed to fetch session photos:', err); + return { data: { serviceSessionImages: [] } }; + }), + new ServiceSessionVideosStore() + .fetch({ ...fetchParams, variables: { sessionId: sessionUuid } }) + .catch((err) => { + console.error('Failed to fetch session videos:', err); + return { data: { serviceSessionVideos: [] } }; + }) + ]); + + // Merge photos and videos into session object + session = { + ...session, + photos: photosResult?.data?.serviceSessionImages ?? [], + videos: videosResult?.data?.serviceSessionVideos ?? [] + }; + } + } + + // Fetch scope data if service has an accountAddressId + let scopeData = null; + if (service.accountAddressId) { + const scopeStore = new ScopeByAddressStore(); + scopeData = await scopeStore.fetch({ + ...fetchParams, + variables: { accountAddressId: service.accountAddressId } + }); + } + + return { + service, + serviceUuid, + session, + scope: scopeData?.data?.scopes?.[0] ?? null + }; +}; diff --git a/src/routes/team/services/[service]/+page.svelte b/src/routes/team/services/[service]/+page.svelte new file mode 100644 index 0000000..38e4d80 --- /dev/null +++ b/src/routes/team/services/[service]/+page.svelte @@ -0,0 +1,419 @@ + + + + {accountInfo?.accountName ?? 'Service'} - Services - Nexus + + +
+ + + + + + {#if service.status === 'IN_PROGRESS' && session} +
+ + selectedTaskIds.clear()} + onAddNote={handleAddNote} + onUpdateNote={handleUpdateNote} + onDeleteNote={handleDeleteNote} + onUploadPhoto={handleUploadPhoto} + onUploadVideo={handleUploadVideo} + onUpdatePhoto={handleUpdatePhoto} + onUpdateVideo={handleUpdateVideo} + onDeletePhoto={handleDeletePhoto} + onDeleteVideo={handleDeleteVideo} + /> +
+ {:else if service.status === 'COMPLETED' && session} +
+ + +
+ {:else if service.status === 'SCHEDULED'} + goto('/team/services', { invalidateAll: true })} + /> + {/if} +
+
diff --git a/src/routes/terms/+page.svelte b/src/routes/terms/+page.svelte new file mode 100644 index 0000000..234dca8 --- /dev/null +++ b/src/routes/terms/+page.svelte @@ -0,0 +1,164 @@ + + + + Terms of Use - Nexus + + + + +
+ + +
+

Terms of Use

+

Last updated: December 2024

+
+
+
+ + +
+ +
+

Welcome

+

+ These Terms of Use govern your use of Nexus, the business management platform operated by + Nexus Cleaning Solutions. By using Nexus, you agree to these terms. If you don't agree, + please don't use the platform. +

+ +

Who We Are

+

+ Nexus Cleaning Solutions is a commercial cleaning company based in Macomb County, + Michigan. We provide janitorial services, floor care, and specialty cleaning for businesses + throughout Southeast Michigan. +

+ +

Using Nexus

+ +

Accounts

+

To access Nexus, you need an account. When you create one:

+
    +
  • Provide accurate information
  • +
  • Keep your login credentials secure
  • +
  • Let us know right away if you suspect unauthorized access
  • +
  • You're responsible for all activity under your account
  • +
+ +

What You Can Do

+
    +
  • View your service schedules and history
  • +
  • Access and download invoices
  • +
  • Communicate with our team
  • +
  • Manage your account information
  • +
  • Upload photos or documents related to your services
  • +
+ +

What You Shouldn't Do

+
    +
  • Share your account with others
  • +
  • Try to access accounts or data that isn't yours
  • +
  • Use the Service for anything illegal
  • +
  • Attempt to disrupt or interfere with the Service
  • +
  • Upload malicious files or content
  • +
+ +

Your Content

+

+ When you upload photos, documents, or other content to Nexus: +

+
    +
  • You keep ownership of your content
  • +
  • + You give us permission to use it as needed to provide our services (for example, sharing + photos with our team to document completed work) +
  • +
  • + Make sure you have the right to share anything you upload (don't upload content that + belongs to someone else without permission) +
  • +
+ +

Platform Ownership

+

+ The Nexus platform—including its design, software, code, and user interface—is owned by + Corellon Digital and licensed to Nexus Cleaning Solutions for business operations. + Nexus branding, service content, and customer data are owned by Nexus Cleaning + Solutions. You can use the platform to interact with our services, but you can't copy, + modify, or distribute any part of it without permission. +

+ +

Platform Availability

+

+ We do our best to keep Nexus running smoothly, but we can't promise it will be available + 100% of the time. Sometimes we need to perform maintenance or updates. We're not responsible + for any issues if the platform is temporarily unavailable. +

+ +

Cleaning Services

+

+ Nexus helps you manage your relationship with us, but the actual cleaning services are + governed by your service agreement with Nexus Cleaning Solutions. If there's ever a + conflict between these Terms and your service agreement, the service agreement wins. +

+ +

Limitation of Liability

+

+ Nexus is provided "as is." While we work hard to keep things running well, we can't + guarantee everything will be perfect. To the extent allowed by law, we're not liable for: +

+
    +
  • Lost data or content
  • +
  • Service interruptions
  • +
  • Indirect or consequential damages
  • +
  • Issues caused by third-party services we integrate with
  • +
+ +

Changes to These Terms

+

+ We may update these Terms from time to time. If we make significant changes, we'll let you + know through Nexus or by email. Continuing to use the platform after changes take effect + means you accept the updated Terms. +

+ +

Termination

+

+ You can stop using Nexus at any time. We can also suspend or terminate your access if you + violate these Terms or if we need to for legal or security reasons. If your account is + terminated, you may lose access to your data in Nexus. +

+ +

Governing Law

+

+ These Terms are governed by the laws of the State of Michigan. Any disputes will be resolved + in the courts of Macomb County, Michigan. +

+ +

Contact Us

+

Have questions about these Terms? Get in touch:

+ +
+
+
diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..b113b00 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,18 @@ +import adapter from '@sveltejs/adapter-node'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + kit: { + adapter: adapter(), + + alias: { + $houdini: '.houdini/' + } + } +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..887e784 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler", + "rootDirs": [".", "./.svelte-kit/types", "./.houdini/types"] + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..0c3061b --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,13 @@ +import houdini from 'houdini/vite'; +import devtoolsJson from 'vite-plugin-devtools-json'; +import tailwindcss from '@tailwindcss/vite'; +import mkcert from 'vite-plugin-mkcert'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + host: 'local.example.com' + }, + plugins: [houdini(), tailwindcss(), sveltekit(), devtoolsJson(), mkcert()] +});