diff --git a/AGENTS.md b/AGENTS.md index 6d9153d..b1b18c1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,3 +56,32 @@ Use consistent status/code mapping: - Request/correlation IDs appear in logs and async workflows. - No raw sensitive payload logging or raw upstream body throws. - New/changed webhook or LLM payloads are sanitized and documented. + +## Validation / Verification + +Before marking work complete, verify changes with the same checks used by CI. + +### Required CI-parity checks + +Run from repository root: + +1. `./orchestrator/node_modules/.bin/biome ci .` +2. `npm run check:types:shared` +3. `npm --workspace orchestrator run check:types` +4. `npm --workspace gradcracker-extractor run check:types` +5. `npm --workspace ukvisajobs-extractor run check:types` +6. `npm --workspace orchestrator run build:client` +7. `npm --workspace orchestrator run test:run` + +### Native module note (better-sqlite3) + +If tests fail with a Node ABI mismatch for `better-sqlite3`, rebuild it before running tests: + +- `npm --workspace orchestrator rebuild better-sqlite3` + +CI runs on Node 22. If local behavior differs, verify with Node 22 before concluding a change is valid. + +### Scope-specific checks + +- For focused changes, run targeted tests first (for touched files/modules), then still run the full CI-parity list above before finalizing. +- A change is considered valid only when all required checks pass without ignored failures. diff --git a/orchestrator/package.json b/orchestrator/package.json index 84ae945..a5acc0a 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -30,11 +30,12 @@ "@paralleldrive/cuid2": "^3.0.6", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-alert-dialog": "^1.1.15", - "@radix-ui/react-checkbox": "^1.3.2", + "@radix-ui/react-checkbox": "1.3.2", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-dropdown-menu": "2.1.15", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-radio-group": "^1.3.8", "@radix-ui/react-select": "^2.2.6", @@ -48,23 +49,24 @@ "canvas-confetti": "^1.9.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "cors": "^2.8.5", "dotenv": "^17.2.3", "drizzle-orm": "^0.38.2", + "express": "^4.18.2", "get-tsconfig": "^4.10.0", "jsdom": "^25.0.1", - "tsx": "^4.19.2", - "express": "^4.18.2", "lucide-react": "^0.561.0", "next-themes": "^0.4.6", "react-hook-form": "^7.71.1", "react-markdown": "^10.1.0", - "recharts": "^2.12.5", "react-transition-group": "^4.4.5", + "recharts": "^2.12.5", "remark-gfm": "^4.0.1", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", "tailwindcss-animate": "^1.0.7", + "tsx": "^4.19.2", "vaul": "^1.1.2", "zod": "^3.23.8" }, @@ -78,8 +80,8 @@ "@types/express": "^4.17.21", "@types/jsdom": "^27.0.0", "@types/node": "^22.10.1", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", + "@types/react": "18.3.12", + "@types/react-dom": "18.3.1", "@types/react-transition-group": "^4.4.12", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.22", @@ -96,11 +98,11 @@ "vitest": "^4.0.16" }, "optionalDependencies": { - "lightningcss-linux-x64-gnu": "^1.29.3", - "lightningcss-linux-arm64-gnu": "^1.29.3", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@rollup/rollup-linux-arm64-gnu": "^4.30.0", - "@rollup/rollup-linux-x64-gnu": "^4.30.0" + "@rollup/rollup-linux-x64-gnu": "^4.30.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "lightningcss-linux-arm64-gnu": "^1.29.3", + "lightningcss-linux-x64-gnu": "^1.29.3" } } diff --git a/orchestrator/src/client/pages/OrchestratorPage.test.tsx b/orchestrator/src/client/pages/OrchestratorPage.test.tsx index f79f6f3..f049ba1 100644 --- a/orchestrator/src/client/pages/OrchestratorPage.test.tsx +++ b/orchestrator/src/client/pages/OrchestratorPage.test.tsx @@ -22,6 +22,16 @@ vi.mock("../api", () => ({ })); let mockIsPipelineRunning = false; +let mockPipelineSources = ["linkedin"] as Array< + "gradcracker" | "indeed" | "linkedin" | "ukvisajobs" +>; +let mockAutomaticRunValues = { + topN: 12, + minSuitabilityScore: 55, + searchTerms: ["backend"], + runBudget: 150, + country: "united kingdom", +}; const jobFixture: Job = { id: "job-1", @@ -119,7 +129,7 @@ vi.mock("./orchestrator/useOrchestratorData", () => ({ vi.mock("./orchestrator/usePipelineSources", () => ({ usePipelineSources: () => ({ - pipelineSources: ["linkedin"], + pipelineSources: mockPipelineSources, setPipelineSources: vi.fn(), toggleSource: vi.fn(), }), @@ -300,19 +310,13 @@ vi.mock("./orchestrator/RunModeModal", () => ({ minSuitabilityScore: number; searchTerms: string[]; runBudget: number; + country: string; }) => Promise; }) => ( @@ -334,6 +338,14 @@ describe("OrchestratorPage", () => { beforeEach(() => { vi.clearAllMocks(); mockIsPipelineRunning = false; + mockPipelineSources = ["linkedin"]; + mockAutomaticRunValues = { + topN: 12, + minSuitabilityScore: 55, + searchTerms: ["backend"], + runBudget: 150, + country: "united kingdom", + }; }); it("syncs tab selection to the URL", () => { @@ -583,12 +595,49 @@ describe("OrchestratorPage", () => { jobspyResultsWanted: 150, gradcrackerMaxJobsPerTerm: 150, ukvisajobsMaxJobs: 150, + jobspyCountryIndeed: "united kingdom", + jobspyLocation: "United Kingdom", }); }); + expect(api.runPipeline).toHaveBeenCalledWith({ + topN: 12, + minSuitabilityScore: 55, + sources: ["linkedin"], + }); setIntervalSpy.mockRestore(); }); + it("blocks automatic run when no sources are compatible for selected country", async () => { + window.matchMedia = createMatchMedia( + true, + ) as unknown as typeof window.matchMedia; + mockPipelineSources = ["gradcracker", "ukvisajobs"]; + mockAutomaticRunValues = { + topN: 12, + minSuitabilityScore: 55, + searchTerms: ["backend"], + runBudget: 150, + country: "united states", + }; + + render( + + + } /> + } /> + + , + ); + + fireEvent.click(screen.getByTestId("run-automatic")); + + await waitFor(() => { + expect(api.updateSettings).not.toHaveBeenCalled(); + expect(api.runPipeline).not.toHaveBeenCalled(); + }); + }); + it("shows and hides bulk Recalculate match based on selected statuses", async () => { window.matchMedia = createMatchMedia( true, diff --git a/orchestrator/src/client/pages/OrchestratorPage.tsx b/orchestrator/src/client/pages/OrchestratorPage.tsx index 1e536c9..65deb13 100644 --- a/orchestrator/src/client/pages/OrchestratorPage.tsx +++ b/orchestrator/src/client/pages/OrchestratorPage.tsx @@ -3,6 +3,10 @@ */ import { useSettings } from "@client/hooks/useSettings"; +import { + formatCountryLabel, + getCompatibleSourcesForCountry, +} from "@shared/location-support.js"; import type { JobSource } from "@shared/types.js"; import type React from "react"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -121,7 +125,8 @@ export const OrchestratorPage: React.FC = () => { () => getEnabledSources(settings ?? null), [settings], ); - const { pipelineSources, toggleSource } = usePipelineSources(enabledSources); + const { pipelineSources, setPipelineSources, toggleSource } = + usePipelineSources(enabledSources); const activeJobs = useFilteredJobs( jobs, @@ -240,22 +245,35 @@ export const OrchestratorPage: React.FC = () => { const handleSaveAndRunAutomatic = useCallback( async (values: AutomaticRunValues) => { + const compatibleSources = getCompatibleSourcesForCountry( + pipelineSources, + values.country, + ); + if (compatibleSources.length === 0) { + toast.error( + "No compatible sources for the selected country. Choose another country or source.", + ); + return; + } + const limits = deriveExtractorLimits({ budget: values.runBudget, searchTerms: values.searchTerms, - sources: pipelineSources, + sources: compatibleSources, }); await api.updateSettings({ searchTerms: values.searchTerms, jobspyResultsWanted: limits.jobspyResultsWanted, gradcrackerMaxJobsPerTerm: limits.gradcrackerMaxJobsPerTerm, ukvisajobsMaxJobs: limits.ukvisajobsMaxJobs, + jobspyCountryIndeed: values.country, + jobspyLocation: formatCountryLabel(values.country), }); await refreshSettings(); await startPipelineRun({ topN: values.topN, minSuitabilityScore: values.minSuitabilityScore, - sources: pipelineSources, + sources: compatibleSources, }); setIsRunModeModalOpen(false); }, @@ -419,6 +437,7 @@ export const OrchestratorPage: React.FC = () => { enabledSources={enabledSources} pipelineSources={pipelineSources} onToggleSource={toggleSource} + onSetPipelineSources={setPipelineSources} isPipelineRunning={isPipelineRunning} onOpenChange={setIsRunModeModalOpen} onModeChange={setRunMode} diff --git a/orchestrator/src/client/pages/orchestrator/AutomaticRunTab.test.tsx b/orchestrator/src/client/pages/orchestrator/AutomaticRunTab.test.tsx new file mode 100644 index 0000000..8183fcc --- /dev/null +++ b/orchestrator/src/client/pages/orchestrator/AutomaticRunTab.test.tsx @@ -0,0 +1,99 @@ +import type { AppSettings } from "@shared/types"; +import { render, screen, waitFor } from "@testing-library/react"; +import type React from "react"; +import { describe, expect, it, vi } from "vitest"; +import { AutomaticRunTab } from "./AutomaticRunTab"; + +vi.mock("@/components/ui/tooltip", () => ({ + TooltipProvider: ({ children }: { children: React.ReactNode }) => ( + <>{children} + ), + Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}, + TooltipTrigger: ({ children }: { children: React.ReactNode }) => ( + <>{children} + ), + TooltipContent: ({ children }: { children: React.ReactNode }) => ( + <>{children} + ), +})); + +describe("AutomaticRunTab", () => { + it("loads persisted country from settings", () => { + render( + , + ); + + expect( + screen.getByRole("combobox", { name: "United States" }), + ).toBeInTheDocument(); + }); + + it("disables and prunes UK-only sources for non-UK country", async () => { + const onSetPipelineSources = vi.fn(); + + render( + , + ); + + await waitFor(() => { + expect(onSetPipelineSources).toHaveBeenCalledWith(["linkedin"]); + }); + + expect(screen.getByRole("button", { name: "Gradcracker" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "UK Visa Jobs" })).toBeDisabled(); + }); + + it("shows disabled source guidance copy for UK-only source", () => { + render( + , + ); + + expect( + screen.getByTitle( + "Gradcracker is available only when country is United Kingdom.", + ), + ).toBeInTheDocument(); + }); +}); diff --git a/orchestrator/src/client/pages/orchestrator/AutomaticRunTab.tsx b/orchestrator/src/client/pages/orchestrator/AutomaticRunTab.tsx index 4428c20..c4db0c3 100644 --- a/orchestrator/src/client/pages/orchestrator/AutomaticRunTab.tsx +++ b/orchestrator/src/client/pages/orchestrator/AutomaticRunTab.tsx @@ -1,5 +1,13 @@ +import * as PopoverPrimitive from "@radix-ui/react-popover"; +import { + formatCountryLabel, + getCompatibleSourcesForCountry, + isSourceAllowedForCountry, + normalizeCountryKey, + SUPPORTED_COUNTRY_KEYS, +} from "@shared/location-support.js"; import type { AppSettings, JobSource } from "@shared/types"; -import { Loader2, Sparkles, X } from "lucide-react"; +import { Check, ChevronsUpDown, Loader2, Sparkles, X } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { @@ -10,10 +18,25 @@ import { } from "@/components/ui/accordion"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { Popover, PopoverTrigger } from "@/components/ui/popover"; import { Separator } from "@/components/ui/separator"; -import { sourceLabel } from "@/lib/utils"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { cn, sourceLabel } from "@/lib/utils"; import { AUTOMATIC_PRESETS, type AutomaticPresetId, @@ -30,6 +53,7 @@ interface AutomaticRunTabProps { enabledSources: JobSource[]; pipelineSources: JobSource[]; onToggleSource: (source: JobSource, checked: boolean) => void; + onSetPipelineSources: (sources: JobSource[]) => void; isPipelineRunning: boolean; onSaveAndRun: (values: AutomaticRunValues) => Promise; } @@ -39,12 +63,14 @@ const DEFAULT_VALUES: AutomaticRunValues = { minSuitabilityScore: 50, searchTerms: ["web developer"], runBudget: 200, + country: "united kingdom", }; interface AutomaticRunFormValues { topN: string; minSuitabilityScore: string; runBudget: string; + country: string; searchTerms: string[]; searchTermDraft: string; } @@ -94,17 +120,20 @@ export const AutomaticRunTab: React.FC = ({ enabledSources, pipelineSources, onToggleSource, + onSetPipelineSources, isPipelineRunning, onSaveAndRun, }) => { const [isSaving, setIsSaving] = useState(false); const [advancedOpen, setAdvancedOpen] = useState(false); + const [countryMenuOpen, setCountryMenuOpen] = useState(false); const { watch, reset, setValue, getValues } = useForm( { defaultValues: { topN: String(DEFAULT_VALUES.topN), minSuitabilityScore: String(DEFAULT_VALUES.minSuitabilityScore), runBudget: String(DEFAULT_VALUES.runBudget), + country: DEFAULT_VALUES.country, searchTerms: DEFAULT_VALUES.searchTerms, searchTermDraft: "", }, @@ -114,6 +143,7 @@ export const AutomaticRunTab: React.FC = ({ const topNInput = watch("topN"); const minScoreInput = watch("minSuitabilityScore"); const runBudgetInput = watch("runBudget"); + const countryInput = watch("country"); const searchTerms = watch("searchTerms"); const searchTermDraft = watch("searchTermDraft"); @@ -129,14 +159,22 @@ export const AutomaticRunTab: React.FC = ({ settings?.gradcrackerMaxJobsPerTerm ?? settings?.ukvisajobsMaxJobs ?? DEFAULT_VALUES.runBudget; + const rememberedCountry = normalizeCountryKey( + settings?.jobspyCountryIndeed ?? + settings?.jobspyLocation ?? + DEFAULT_VALUES.country, + ); + reset({ topN: String(topN), minSuitabilityScore: String(minSuitabilityScore), runBudget: String(rememberedRunBudget), + country: rememberedCountry || DEFAULT_VALUES.country, searchTerms: settings?.searchTerms ?? DEFAULT_VALUES.searchTerms, searchTermDraft: "", }); setAdvancedOpen(false); + setCountryMenuOpen(false); }, [open, settings, reset]); const addSearchTerms = (input: string) => { @@ -151,6 +189,7 @@ export const AutomaticRunTab: React.FC = ({ }; const values = useMemo(() => { + const normalizedCountry = normalizeCountryKey(countryInput); return { topN: toNumber(topNInput, 1, 50, DEFAULT_VALUES.topN), minSuitabilityScore: toNumber( @@ -159,15 +198,54 @@ export const AutomaticRunTab: React.FC = ({ 100, DEFAULT_VALUES.minSuitabilityScore, ), - searchTerms, runBudget: toNumber(runBudgetInput, 1, 1000, DEFAULT_VALUES.runBudget), + country: normalizedCountry || DEFAULT_VALUES.country, + searchTerms, }; - }, [topNInput, minScoreInput, searchTerms, runBudgetInput]); + }, [topNInput, minScoreInput, runBudgetInput, countryInput, searchTerms]); + + const compatibleEnabledSources = useMemo( + () => + enabledSources.filter((source) => + isSourceAllowedForCountry(source, values.country), + ), + [enabledSources, values.country], + ); + + const compatiblePipelineSources = useMemo( + () => getCompatibleSourcesForCountry(pipelineSources, values.country), + [pipelineSources, values.country], + ); + + useEffect(() => { + const filtered = getCompatibleSourcesForCountry( + pipelineSources, + values.country, + ); + if (filtered.length === pipelineSources.length) return; + if (filtered.length > 0) { + onSetPipelineSources(filtered); + return; + } + if (compatibleEnabledSources.length > 0) { + onSetPipelineSources([compatibleEnabledSources[0]]); + } + }, [ + compatibleEnabledSources, + onSetPipelineSources, + pipelineSources, + values.country, + ]); const estimate = useMemo( - () => calculateAutomaticEstimate({ values, sources: pipelineSources }), - [values, pipelineSources], + () => + calculateAutomaticEstimate({ + values, + sources: compatiblePipelineSources, + }), + [values, compatiblePipelineSources], ); + const activePreset = useMemo( () => getPresetSelection(values), [values], @@ -176,7 +254,7 @@ export const AutomaticRunTab: React.FC = ({ const runDisabled = isPipelineRunning || isSaving || - pipelineSources.length === 0 || + compatiblePipelineSources.length === 0 || values.searchTerms.length === 0; const applyPreset = (presetId: AutomaticPresetId) => { @@ -201,6 +279,8 @@ export const AutomaticRunTab: React.FC = ({ } }; + const countryOptions = SUPPORTED_COUNTRY_KEYS; + return (
@@ -242,6 +322,73 @@ export const AutomaticRunTab: React.FC = ({
+ +
+ + + + + + + + + event.stopPropagation()} + > + No matching countries. + + {countryOptions.map((country) => { + const selected = values.country === country; + const label = formatCountryLabel(country); + return ( + { + setValue("country", country, { + shouldDirty: true, + }); + setCountryMenuOpen(false); + }} + > + {label} + + + ); + })} + + + + + +
= ({ - Sources ({pipelineSources.length}/{enabledSources.length}) + Sources ({compatiblePipelineSources.length}/ + {compatibleEnabledSources.length}) - {enabledSources.map((source) => ( - + ); + + if (allowed) { + return button; } - onClick={() => - onToggleSource(source, !pipelineSources.includes(source)) - } - > - {sourceLabel[source]} - - ))} + + return ( + + + + {button} + + + {disabledReason} + + ); + })} + diff --git a/orchestrator/src/client/pages/orchestrator/RunModeModal.test.tsx b/orchestrator/src/client/pages/orchestrator/RunModeModal.test.tsx index bcd2eec..b65a532 100644 --- a/orchestrator/src/client/pages/orchestrator/RunModeModal.test.tsx +++ b/orchestrator/src/client/pages/orchestrator/RunModeModal.test.tsx @@ -22,6 +22,7 @@ describe("RunModeModal", () => { enabledSources={["linkedin"]} pipelineSources={["linkedin"]} onToggleSource={vi.fn()} + onSetPipelineSources={vi.fn()} isPipelineRunning={false} onOpenChange={vi.fn()} onModeChange={vi.fn()} diff --git a/orchestrator/src/client/pages/orchestrator/RunModeModal.tsx b/orchestrator/src/client/pages/orchestrator/RunModeModal.tsx index 7f3a55b..4364f27 100644 --- a/orchestrator/src/client/pages/orchestrator/RunModeModal.tsx +++ b/orchestrator/src/client/pages/orchestrator/RunModeModal.tsx @@ -21,6 +21,7 @@ interface RunModeModalProps { enabledSources: JobSource[]; pipelineSources: JobSource[]; onToggleSource: (source: JobSource, checked: boolean) => void; + onSetPipelineSources: (sources: JobSource[]) => void; isPipelineRunning: boolean; onOpenChange: (open: boolean) => void; onModeChange: (mode: RunMode) => void; @@ -35,6 +36,7 @@ export const RunModeModal: React.FC = ({ enabledSources, pipelineSources, onToggleSource, + onSetPipelineSources, isPipelineRunning, onOpenChange, onModeChange, @@ -73,6 +75,7 @@ export const RunModeModal: React.FC = ({ enabledSources={enabledSources} pipelineSources={pipelineSources} onToggleSource={onToggleSource} + onSetPipelineSources={onSetPipelineSources} isPipelineRunning={isPipelineRunning} onSaveAndRun={onSaveAndRunAutomatic} /> diff --git a/orchestrator/src/client/pages/orchestrator/automatic-run.test.ts b/orchestrator/src/client/pages/orchestrator/automatic-run.test.ts index 82ec130..351ba28 100644 --- a/orchestrator/src/client/pages/orchestrator/automatic-run.test.ts +++ b/orchestrator/src/client/pages/orchestrator/automatic-run.test.ts @@ -26,6 +26,7 @@ describe("automatic-run utilities", () => { minSuitabilityScore: 50, searchTerms: ["backend", "platform"], runBudget: 100, + country: "united kingdom", }, sources: ["indeed", "linkedin", "gradcracker", "ukvisajobs"], }); @@ -57,6 +58,7 @@ describe("automatic-run utilities", () => { minSuitabilityScore: 50, searchTerms: [], runBudget: 750, + country: "united kingdom", }, sources: ["indeed", "linkedin", "gradcracker", "ukvisajobs"], }); diff --git a/orchestrator/src/client/pages/orchestrator/automatic-run.ts b/orchestrator/src/client/pages/orchestrator/automatic-run.ts index 567b295..f6c1326 100644 --- a/orchestrator/src/client/pages/orchestrator/automatic-run.ts +++ b/orchestrator/src/client/pages/orchestrator/automatic-run.ts @@ -7,6 +7,7 @@ export interface AutomaticRunValues { minSuitabilityScore: number; searchTerms: string[]; runBudget: number; + country: string; } export interface AutomaticPresetValues { diff --git a/orchestrator/src/client/pages/settings/components/JobspySection.tsx b/orchestrator/src/client/pages/settings/components/JobspySection.tsx index fff52ac..58f88aa 100644 --- a/orchestrator/src/client/pages/settings/components/JobspySection.tsx +++ b/orchestrator/src/client/pages/settings/components/JobspySection.tsx @@ -1,5 +1,10 @@ import { SettingsInput } from "@client/pages/settings/components/SettingsInput"; import type { JobspyValues } from "@client/pages/settings/types"; +import { + formatCountryLabel, + normalizeCountryKey, + SUPPORTED_COUNTRY_KEYS, +} from "@shared/location-support.js"; import type { UpdateSettingsInput } from "@shared/settings-schema.js"; import type React from "react"; import { Controller, useFormContext } from "react-hook-form"; @@ -24,119 +29,6 @@ type JobspySectionProps = { isSaving: boolean; }; -const JOBSPY_INDEED_COUNTRIES = [ - "argentina", - "australia", - "austria", - "bahrain", - "bangladesh", - "belgium", - "bulgaria", - "brazil", - "canada", - "chile", - "china", - "colombia", - "costa rica", - "croatia", - "cyprus", - "czech republic", - "czechia", - "denmark", - "ecuador", - "egypt", - "estonia", - "finland", - "france", - "germany", - "greece", - "hong kong", - "hungary", - "india", - "indonesia", - "ireland", - "israel", - "italy", - "japan", - "kuwait", - "latvia", - "lithuania", - "luxembourg", - "malaysia", - "malta", - "mexico", - "morocco", - "netherlands", - "new zealand", - "nigeria", - "norway", - "oman", - "pakistan", - "panama", - "peru", - "philippines", - "poland", - "portugal", - "qatar", - "romania", - "saudi arabia", - "singapore", - "slovakia", - "slovenia", - "south africa", - "south korea", - "spain", - "sweden", - "switzerland", - "taiwan", - "thailand", - "türkiye", - "turkey", - "ukraine", - "united arab emirates", - "uk", - "united kingdom", - "usa", - "us", - "united states", - "uruguay", - "venezuela", - "vietnam", - "usa/ca", - "worldwide", -]; - -const COUNTRY_ALIASES: Record = { - uk: "united kingdom", - us: "united states", - usa: "united states", - türkiye: "turkey", - "czech republic": "czechia", -}; - -const COUNTRY_LABELS: Record = { - "united kingdom": "United Kingdom", - "united states": "United States", - "usa/ca": "USA/CA", - turkey: "Turkey", - czechia: "Czechia", -}; - -const normalizeCountryValue = (value: string) => - COUNTRY_ALIASES[value] ?? value; - -const formatCountryLabel = (value: string) => - COUNTRY_LABELS[value] || value.replace(/\b\w/g, (char) => char.toUpperCase()); - -const JOBSPY_INDEED_COUNTRY_OPTIONS = Array.from( - new Map( - JOBSPY_INDEED_COUNTRIES.map((country) => { - const normalized = normalizeCountryValue(country); - return [normalized, normalized]; - }), - ).values(), -); - export const JobspySection: React.FC = ({ values, isLoading, @@ -332,8 +224,8 @@ export const JobspySection: React.FC = ({ countryIndeed.default ?? "" ).toLowerCase(); - const normalizedValue = normalizeCountryValue(currentValue); - const displayValue = JOBSPY_INDEED_COUNTRY_OPTIONS.includes( + const normalizedValue = normalizeCountryKey(currentValue); + const displayValue = SUPPORTED_COUNTRY_KEYS.includes( normalizedValue, ) ? normalizedValue @@ -365,7 +257,7 @@ export const JobspySection: React.FC = ({ {`Use default (${countryIndeed.default || "UK"})`} - {JOBSPY_INDEED_COUNTRY_OPTIONS.map((country) => ( + {SUPPORTED_COUNTRY_KEYS.map((country) => ( {formatCountryLabel(country)} diff --git a/orchestrator/src/components/ui/command.tsx b/orchestrator/src/components/ui/command.tsx new file mode 100644 index 0000000..5033694 --- /dev/null +++ b/orchestrator/src/components/ui/command.tsx @@ -0,0 +1,150 @@ +import type { DialogProps } from "@radix-ui/react-dialog"; +import { Command as CommandPrimitive } from "cmdk"; +import { Search } from "lucide-react"; +import * as React from "react"; +import { Dialog, DialogContent } from "@/components/ui/dialog"; +import { cn } from "@/lib/utils"; + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = CommandPrimitive.displayName; + +const CommandDialog = ({ children, ...props }: DialogProps) => { + return ( + + + + {children} + + + + ); +}; + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)); + +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)); + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +CommandShortcut.displayName = "CommandShortcut"; + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +}; diff --git a/orchestrator/src/components/ui/dialog.tsx b/orchestrator/src/components/ui/dialog.tsx new file mode 100644 index 0000000..f4777a8 --- /dev/null +++ b/orchestrator/src/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client"; + +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/orchestrator/src/components/ui/popover.tsx b/orchestrator/src/components/ui/popover.tsx new file mode 100644 index 0000000..644d397 --- /dev/null +++ b/orchestrator/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +import * as PopoverPrimitive from "@radix-ui/react-popover"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverAnchor = PopoverPrimitive.Anchor; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; diff --git a/orchestrator/src/server/pipeline/steps/discover-jobs.test.ts b/orchestrator/src/server/pipeline/steps/discover-jobs.test.ts index 53fac4b..20fbb82 100644 --- a/orchestrator/src/server/pipeline/steps/discover-jobs.test.ts +++ b/orchestrator/src/server/pipeline/steps/discover-jobs.test.ts @@ -225,6 +225,87 @@ describe("discoverJobsStep", () => { expect(progress.crawlingJobPagesProcessed).toBe(18); }); + it("skips UK-only sources for non-UK country and runs compatible sources", async () => { + const settingsRepo = await import("../../repositories/settings"); + const jobSpy = await import("../../services/jobspy"); + const crawler = await import("../../services/crawler"); + const ukVisa = await import("../../services/ukvisajobs"); + + vi.mocked(settingsRepo.getAllSettings).mockResolvedValue({ + searchTerms: JSON.stringify(["engineer"]), + jobspyCountryIndeed: "united states", + } as any); + + vi.mocked(jobSpy.runJobSpy).mockResolvedValue({ + success: true, + jobs: [ + { + source: "linkedin", + title: "Engineer", + employer: "ACME", + jobUrl: "https://example.com/job", + }, + ], + } as any); + + const result = await discoverJobsStep({ + mergedConfig: { + ...config, + sources: ["linkedin", "gradcracker", "ukvisajobs"], + }, + }); + + expect(result.discoveredJobs).toHaveLength(1); + expect(vi.mocked(jobSpy.runJobSpy)).toHaveBeenCalledTimes(1); + expect(vi.mocked(crawler.runCrawler)).not.toHaveBeenCalled(); + expect(vi.mocked(ukVisa.runUkVisaJobs)).not.toHaveBeenCalled(); + }); + + it("throws when all requested sources are incompatible for country", async () => { + const settingsRepo = await import("../../repositories/settings"); + + vi.mocked(settingsRepo.getAllSettings).mockResolvedValue({ + searchTerms: JSON.stringify(["engineer"]), + jobspyCountryIndeed: "united states", + } as any); + + await expect( + discoverJobsStep({ + mergedConfig: { + ...config, + sources: ["gradcracker", "ukvisajobs"], + }, + }), + ).rejects.toThrow( + "No compatible sources for selected country: United States", + ); + }); + + it("does not throw when no sources are requested", async () => { + const settingsRepo = await import("../../repositories/settings"); + const jobSpy = await import("../../services/jobspy"); + const crawler = await import("../../services/crawler"); + const ukVisa = await import("../../services/ukvisajobs"); + + vi.mocked(settingsRepo.getAllSettings).mockResolvedValue({ + searchTerms: JSON.stringify(["engineer"]), + jobspyCountryIndeed: "united states", + } as any); + + const result = await discoverJobsStep({ + mergedConfig: { + ...config, + sources: [], + }, + }); + + expect(result.discoveredJobs).toEqual([]); + expect(result.sourceErrors).toEqual([]); + expect(vi.mocked(jobSpy.runJobSpy)).not.toHaveBeenCalled(); + expect(vi.mocked(crawler.runCrawler)).not.toHaveBeenCalled(); + expect(vi.mocked(ukVisa.runUkVisaJobs)).not.toHaveBeenCalled(); + }); + it("tracks source completion counters across source transitions", async () => { const settingsRepo = await import("../../repositories/settings"); const jobSpy = await import("../../services/jobspy"); diff --git a/orchestrator/src/server/pipeline/steps/discover-jobs.ts b/orchestrator/src/server/pipeline/steps/discover-jobs.ts index 16fc315..f2f7183 100644 --- a/orchestrator/src/server/pipeline/steps/discover-jobs.ts +++ b/orchestrator/src/server/pipeline/steps/discover-jobs.ts @@ -1,4 +1,9 @@ import { logger } from "@infra/logger"; +import { + formatCountryLabel, + isSourceAllowedForCountry, + normalizeCountryKey, +} from "@shared/location-support.js"; import type { CreateJobInput, PipelineConfig } from "@shared/types"; import * as jobsRepo from "../../repositories/jobs"; import * as settingsRepo from "../../repositories/settings"; @@ -35,7 +40,33 @@ export async function discoverJobsStep(args: { .filter(Boolean); } - let jobSpySites = args.mergedConfig.sources.filter( + const selectedCountry = normalizeCountryKey( + settings.jobspyCountryIndeed ?? settings.jobspyLocation ?? "united kingdom", + ); + const compatibleSources = args.mergedConfig.sources.filter((source) => + isSourceAllowedForCountry(source, selectedCountry), + ); + const skippedSources = args.mergedConfig.sources.filter( + (source) => !compatibleSources.includes(source), + ); + + if (skippedSources.length > 0) { + logger.info("Skipping incompatible sources for selected country", { + step: "discover-jobs", + country: selectedCountry, + countryLabel: formatCountryLabel(selectedCountry), + requestedSources: args.mergedConfig.sources, + skippedSources, + }); + } + + if (args.mergedConfig.sources.length > 0 && compatibleSources.length === 0) { + throw new Error( + `No compatible sources for selected country: ${formatCountryLabel(selectedCountry)}`, + ); + } + + let jobSpySites = compatibleSources.filter( (source): source is "indeed" | "linkedin" => source === "indeed" || source === "linkedin", ); @@ -53,9 +84,8 @@ export async function discoverJobsStep(args: { } const shouldRunJobSpy = jobSpySites.length > 0; - const shouldRunGradcracker = - args.mergedConfig.sources.includes("gradcracker"); - const shouldRunUkVisaJobs = args.mergedConfig.sources.includes("ukvisajobs"); + const shouldRunGradcracker = compatibleSources.includes("gradcracker"); + const shouldRunUkVisaJobs = compatibleSources.includes("ukvisajobs"); const totalSources = Number(shouldRunJobSpy) + diff --git a/package-lock.json b/package-lock.json index 9d19e3d..a5ca06d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -706,6 +706,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -728,6 +729,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1148,6 +1150,44 @@ "node": ">=18" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", + "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.5" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@inquirer/external-editor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", @@ -1227,6 +1267,803 @@ "cuid2": "bin/cuid2.js" } }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.2.tgz", + "integrity": "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.15.tgz", + "integrity": "sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.15.tgz", + "integrity": "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", + "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", + "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", + "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, "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", @@ -1612,10 +2449,39 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz", "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/sax": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", @@ -1719,6 +2585,18 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1829,6 +2707,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2121,6 +3000,22 @@ "node": ">=0.8" } }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2244,6 +3139,12 @@ "node": ">=18" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, "node_modules/csv-stringify": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.6.0.tgz", @@ -2351,6 +3252,12 @@ "node": ">=8" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/devtools-protocol": { "version": "0.0.1577676", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1577676.tgz", @@ -2739,6 +3646,15 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-stream": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", @@ -3436,6 +4352,12 @@ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", "license": "MIT" }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, "node_modules/jsdom": { "version": "26.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", @@ -3838,6 +4760,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -4350,6 +5284,7 @@ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.1.tgz", "integrity": "sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright-core": "1.58.1" }, @@ -4368,6 +5303,7 @@ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.1.tgz", "integrity": "sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==", "license": "Apache-2.0", + "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -4495,6 +5431,102 @@ "rc": "cli.js" } }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -4671,6 +5703,15 @@ "node": ">=v12.22.7" } }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -5042,6 +6083,7 @@ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -5198,6 +6240,49 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5502,11 +6587,12 @@ "@paralleldrive/cuid2": "^3.0.6", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-alert-dialog": "^1.1.15", - "@radix-ui/react-checkbox": "^1.3.2", + "@radix-ui/react-checkbox": "1.3.2", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-dropdown-menu": "2.1.15", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-radio-group": "^1.3.8", "@radix-ui/react-select": "^2.2.6", @@ -5520,6 +6606,7 @@ "canvas-confetti": "^1.9.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "cors": "^2.8.5", "dotenv": "^17.2.3", "drizzle-orm": "^0.38.2", @@ -5550,8 +6637,8 @@ "@types/express": "^4.17.21", "@types/jsdom": "^27.0.0", "@types/node": "^22.10.1", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", + "@types/react": "18.3.12", + "@types/react-dom": "18.3.1", "@types/react-transition-group": "^4.4.12", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.22", @@ -5617,6 +6704,7 @@ "version": "7.28.5", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -6346,36 +7434,6 @@ "node": ">=12" } }, - "orchestrator/node_modules/@floating-ui/core": { - "version": "1.7.3", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.10" - } - }, - "orchestrator/node_modules/@floating-ui/dom": { - "version": "1.7.4", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" - } - }, - "orchestrator/node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.7.4" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "orchestrator/node_modules/@floating-ui/utils": { - "version": "0.2.10", - "license": "MIT" - }, "orchestrator/node_modules/@hookform/resolvers": { "version": "5.2.2", "license": "MIT", @@ -6430,10 +7488,6 @@ "version": "1.1.1", "license": "MIT" }, - "orchestrator/node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "license": "MIT" - }, "orchestrator/node_modules/@radix-ui/react-accordion": { "version": "1.2.12", "license": "MIT", @@ -6505,55 +7559,6 @@ } } }, - "orchestrator/node_modules/@radix-ui/react-arrow": { - "version": "1.1.7", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-checkbox": { - "version": "1.3.3", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "orchestrator/node_modules/@radix-ui/react-collapsible": { "version": "1.1.12", "license": "MIT", @@ -6582,239 +7587,6 @@ } } }, - "orchestrator/node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-dialog": { - "version": "1.1.15", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.11", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-escape-keydown": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.16", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.7", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "orchestrator/node_modules/@radix-ui/react-label": { "version": "2.1.8", "license": "MIT", @@ -6857,171 +7629,6 @@ } } }, - "orchestrator/node_modules/@radix-ui/react-menu": { - "version": "2.1.16", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-rect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-portal": { - "version": "1.1.9", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "orchestrator/node_modules/@radix-ui/react-progress": { "version": "1.1.8", "license": "MIT", @@ -7328,126 +7935,6 @@ } } }, - "orchestrator/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-use-previous": { - "version": "1.1.1", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-use-rect": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "orchestrator/node_modules/@radix-ui/react-visually-hidden": { "version": "1.2.3", "license": "MIT", @@ -7469,10 +7956,6 @@ } } }, - "orchestrator/node_modules/@radix-ui/rect": { - "version": "1.1.1", - "license": "MIT" - }, "orchestrator/node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "dev": true, @@ -7654,8 +8137,7 @@ "orchestrator/node_modules/@types/aria-query": { "version": "5.0.4", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "orchestrator/node_modules/@types/babel__core": { "version": "7.20.5", @@ -7698,6 +8180,7 @@ "version": "7.6.13", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -7876,10 +8359,6 @@ "undici-types": "~6.21.0" } }, - "orchestrator/node_modules/@types/prop-types": { - "version": "15.7.15", - "license": "MIT" - }, "orchestrator/node_modules/@types/qs": { "version": "6.14.0", "dev": true, @@ -7890,22 +8369,6 @@ "dev": true, "license": "MIT" }, - "orchestrator/node_modules/@types/react": { - "version": "18.3.27", - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "orchestrator/node_modules/@types/react-dom": { - "version": "18.3.7", - "devOptional": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^18.0.0" - } - }, "orchestrator/node_modules/@types/react-transition-group": { "version": "4.4.12", "dev": true, @@ -8076,16 +8539,6 @@ "node": ">= 0.6" } }, - "orchestrator/node_modules/aria-hidden": { - "version": "1.2.6", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "orchestrator/node_modules/aria-query": { "version": "5.3.0", "dev": true, @@ -8158,6 +8611,7 @@ "version": "11.10.0", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -8389,10 +8843,6 @@ "dev": true, "license": "MIT" }, - "orchestrator/node_modules/csstype": { - "version": "3.2.3", - "license": "MIT" - }, "orchestrator/node_modules/d3-array": { "version": "3.2.4", "license": "ISC", @@ -8536,10 +8986,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "orchestrator/node_modules/detect-node-es": { - "version": "1.1.0", - "license": "MIT" - }, "orchestrator/node_modules/devlop": { "version": "1.1.0", "license": "MIT", @@ -8554,8 +9000,7 @@ "orchestrator/node_modules/dom-accessibility-api": { "version": "0.5.16", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "orchestrator/node_modules/dom-helpers": { "version": "5.2.1", @@ -9469,13 +9914,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "orchestrator/node_modules/get-nonce": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "orchestrator/node_modules/get-proto": { "version": "1.0.1", "license": "MIT", @@ -9689,13 +10127,10 @@ "jiti": "lib/jiti-cli.mjs" } }, - "orchestrator/node_modules/js-tokens": { - "version": "4.0.0", - "license": "MIT" - }, "orchestrator/node_modules/jsdom": { "version": "25.0.1", "license": "MIT", + "peer": true, "dependencies": { "cssstyle": "^4.1.0", "data-urls": "^5.0.0", @@ -9815,16 +10250,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "orchestrator/node_modules/loose-envify": { - "version": "1.4.0", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "orchestrator/node_modules/lru-cache": { "version": "5.1.1", "dev": true, @@ -9844,7 +10269,6 @@ "version": "1.5.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -10756,6 +11180,7 @@ "orchestrator/node_modules/picomatch": { "version": "4.0.3", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10780,6 +11205,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -10798,7 +11224,6 @@ "version": "27.5.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -10812,7 +11237,6 @@ "version": "5.2.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -10885,30 +11309,10 @@ "node": ">= 0.8" } }, - "orchestrator/node_modules/react": { - "version": "18.3.1", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "orchestrator/node_modules/react-dom": { - "version": "18.3.1", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, "orchestrator/node_modules/react-hook-form": { "version": "7.71.1", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -10923,8 +11327,7 @@ "orchestrator/node_modules/react-is": { "version": "17.0.2", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "orchestrator/node_modules/react-markdown": { "version": "10.1.0", @@ -10959,49 +11362,6 @@ "node": ">=0.10.0" } }, - "orchestrator/node_modules/react-remove-scroll": { - "version": "2.7.2", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.7", - "react-style-singleton": "^2.2.3", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.3", - "use-sidecar": "^1.1.3" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/react-remove-scroll-bar": { - "version": "2.3.8", - "license": "MIT", - "dependencies": { - "react-style-singleton": "^2.2.2", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "orchestrator/node_modules/react-router": { "version": "7.10.1", "dev": true, @@ -11063,26 +11423,6 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "orchestrator/node_modules/react-style-singleton": { - "version": "2.2.3", - "license": "MIT", - "dependencies": { - "get-nonce": "^1.0.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "orchestrator/node_modules/react-transition-group": { "version": "4.4.5", "license": "BSD-3-Clause", @@ -11242,13 +11582,6 @@ "version": "0.7.1", "license": "MIT" }, - "orchestrator/node_modules/scheduler": { - "version": "0.23.2", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, "orchestrator/node_modules/semver": { "version": "6.3.1", "dev": true, @@ -11592,7 +11925,8 @@ }, "orchestrator/node_modules/tailwindcss": { "version": "4.1.18", - "license": "MIT" + "license": "MIT", + "peer": true }, "orchestrator/node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -11824,45 +12158,6 @@ "node": ">= 0.8" } }, - "orchestrator/node_modules/use-callback-ref": { - "version": "1.3.3", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "orchestrator/node_modules/use-sidecar": { - "version": "1.1.3", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "orchestrator/node_modules/utils-merge": { "version": "1.0.1", "license": "MIT", @@ -11935,6 +12230,7 @@ "orchestrator/node_modules/vite": { "version": "6.4.1", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/shared/src/index.ts b/shared/src/index.ts index a4d5a74..1a1ec42 100644 --- a/shared/src/index.ts +++ b/shared/src/index.ts @@ -1,2 +1,3 @@ +export * from "./location-support"; export * from "./types"; export * from "./utils/type-conversion"; diff --git a/shared/src/location-support.test.ts b/shared/src/location-support.test.ts new file mode 100644 index 0000000..1b1156a --- /dev/null +++ b/shared/src/location-support.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from "vitest"; +import { + formatCountryLabel, + getCompatibleSourcesForCountry, + isSourceAllowedForCountry, + isUkCountry, + normalizeCountryKey, + SUPPORTED_COUNTRY_KEYS, +} from "./location-support"; + +describe("location-support", () => { + it("normalizes country aliases", () => { + expect(normalizeCountryKey("UK")).toBe("united kingdom"); + expect(normalizeCountryKey("us")).toBe("united states"); + expect(normalizeCountryKey("usa")).toBe("united states"); + expect(normalizeCountryKey("czech republic")).toBe("czechia"); + }); + + it("formats country labels", () => { + expect(formatCountryLabel("united kingdom")).toBe("United Kingdom"); + expect(formatCountryLabel("usa/ca")).toBe("USA/CA"); + expect(formatCountryLabel("south korea")).toBe("South Korea"); + }); + + it("keeps supported country keys unique and canonical", () => { + expect(SUPPORTED_COUNTRY_KEYS).toContain("united kingdom"); + expect(SUPPORTED_COUNTRY_KEYS).toContain("united states"); + expect(SUPPORTED_COUNTRY_KEYS).toContain("worldwide"); + expect(SUPPORTED_COUNTRY_KEYS).not.toContain("uk"); + expect(SUPPORTED_COUNTRY_KEYS).not.toContain("us"); + }); + + it("treats only united kingdom as UK country", () => { + expect(isUkCountry("united kingdom")).toBe(true); + expect(isUkCountry("UK")).toBe(true); + expect(isUkCountry("worldwide")).toBe(false); + expect(isUkCountry("usa/ca")).toBe(false); + expect(isUkCountry("united states")).toBe(false); + }); + + it("applies source compatibility rules by country", () => { + expect(isSourceAllowedForCountry("gradcracker", "united kingdom")).toBe( + true, + ); + expect(isSourceAllowedForCountry("ukvisajobs", "uk")).toBe(true); + expect(isSourceAllowedForCountry("gradcracker", "united states")).toBe( + false, + ); + expect(isSourceAllowedForCountry("ukvisajobs", "worldwide")).toBe(false); + expect(isSourceAllowedForCountry("indeed", "united states")).toBe(true); + expect(isSourceAllowedForCountry("linkedin", "worldwide")).toBe(true); + }); + + it("filters incompatible sources while preserving compatible order", () => { + expect( + getCompatibleSourcesForCountry( + ["gradcracker", "indeed", "ukvisajobs", "linkedin"], + "united states", + ), + ).toEqual(["indeed", "linkedin"]); + }); +}); diff --git a/shared/src/location-support.ts b/shared/src/location-support.ts new file mode 100644 index 0000000..fd91baf --- /dev/null +++ b/shared/src/location-support.ts @@ -0,0 +1,141 @@ +import type { JobSource } from "./types"; + +const COUNTRY_ALIASES: Record = { + uk: "united kingdom", + us: "united states", + usa: "united states", + türkiye: "turkey", + "czech republic": "czechia", +}; + +const COUNTRY_LABELS: Record = { + "united kingdom": "United Kingdom", + "united states": "United States", + "usa/ca": "USA/CA", + turkey: "Turkey", + czechia: "Czechia", +}; + +// Keep this list aligned with the JobSpy supported country inputs. +export const SUPPORTED_COUNTRY_INPUTS = [ + "argentina", + "australia", + "austria", + "bahrain", + "bangladesh", + "belgium", + "bulgaria", + "brazil", + "canada", + "chile", + "china", + "colombia", + "costa rica", + "croatia", + "cyprus", + "czech republic", + "czechia", + "denmark", + "ecuador", + "egypt", + "estonia", + "finland", + "france", + "germany", + "greece", + "hong kong", + "hungary", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "kuwait", + "latvia", + "lithuania", + "luxembourg", + "malaysia", + "malta", + "mexico", + "morocco", + "netherlands", + "new zealand", + "nigeria", + "norway", + "oman", + "pakistan", + "panama", + "peru", + "philippines", + "poland", + "portugal", + "qatar", + "romania", + "saudi arabia", + "singapore", + "slovakia", + "slovenia", + "south africa", + "south korea", + "spain", + "sweden", + "switzerland", + "taiwan", + "thailand", + "türkiye", + "turkey", + "ukraine", + "united arab emirates", + "uk", + "united kingdom", + "usa", + "us", + "united states", + "uruguay", + "venezuela", + "vietnam", + "usa/ca", + "worldwide", +] as const; + +const UK_ONLY_SOURCES = new Set(["gradcracker", "ukvisajobs"]); + +export function normalizeCountryKey(value: string | null | undefined): string { + const normalized = value?.trim().toLowerCase() ?? ""; + return COUNTRY_ALIASES[normalized] ?? normalized; +} + +export function formatCountryLabel(value: string): string { + const normalized = normalizeCountryKey(value); + if (!normalized) return ""; + return ( + COUNTRY_LABELS[normalized] || + normalized.replace(/\b\w/g, (char) => char.toUpperCase()) + ); +} + +export const SUPPORTED_COUNTRY_KEYS = Array.from( + new Set( + SUPPORTED_COUNTRY_INPUTS.map((country) => normalizeCountryKey(country)), + ), +).filter(Boolean); + +export function isUkCountry(country: string | null | undefined): boolean { + return normalizeCountryKey(country) === "united kingdom"; +} + +export function isSourceAllowedForCountry( + source: JobSource, + country: string | null | undefined, +): boolean { + if (!UK_ONLY_SOURCES.has(source)) return true; + return isUkCountry(country); +} + +export function getCompatibleSourcesForCountry( + sources: JobSource[], + country: string | null | undefined, +): JobSource[] { + return sources.filter((source) => isSourceAllowedForCountry(source, country)); +}