diff --git a/.env.example b/.env.example index 9caadf92..19f38cd7 100644 --- a/.env.example +++ b/.env.example @@ -242,10 +242,25 @@ EMAIL_RESUME_ALLOWED_EXTENSIONS=pdf,doc,docx EMAIL_RESUME_MAX_FILE_SIZE_MB=10 EMAIL_REQUIRE_SENDER_AUTH_HEADERS=true -# Migadu mailbox automation (required for /create-mailbox command) -MIGADU_API_USER=your_migadu_api_user -MIGADU_API_KEY=your_migadu_api_key -MIGADU_MAILBOX_DOMAIN=508.dev +# Migadu mailbox automation settings are dashboard-managed in normal deployments. +# Set non-empty env values only when you intentionally want env to lock dashboard edits. +# MIGADU_API_USER=your_migadu_api_user +# MIGADU_API_KEY=your_migadu_api_key +# MIGADU_MAILBOX_DOMAIN=508.dev + +# Newsletter sync settings are dashboard-managed in normal deployments. +# Set non-empty env values only when you intentionally want env to lock dashboard edits. +# BREVO_API_KEY= +# BREVO_API_BASE_URL=https://api.brevo.com/v3 +# BREVO_API_TIMEOUT_SECONDS=20.0 +# BREVO_508_MEMBERS_NEWSLETTER_LIST_ID=4 +# BREVO_508_MEMBERS_NEWSLETTER_LIST_NAME="508 members" +# KEILA_API_KEY= +# KEILA_API_BASE_URL=https://app.keila.io +# KEILA_API_TIMEOUT_SECONDS=20.0 +# NEWSLETTER_SYNC_ENABLED=true +# NEWSLETTER_SYNC_INTERVAL_SECONDS=604800 +# NEWSLETTER_SYNC_EXCLUDED_MAILBOXES=system,service-account # EspoCRM (required for worker integration) ESPO_API_KEY=your_key_here diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md index 85d954a8..e9f03d3a 100644 --- a/ENVIRONMENT.md +++ b/ENVIRONMENT.md @@ -173,6 +173,20 @@ current precedence rules. - `Required for /create-mailbox and /create-user-accounts`: `MIGADU_API_USER`, `MIGADU_API_KEY` - `Optional`: `MIGADU_MAILBOX_DOMAIN` (default: `508.dev`) +- Newsletter sync settings are normally set from the admin dashboard configuration page. A non-empty env or `.env` value locks the matching dashboard field. +- `Optional for Brevo newsletter sync`: `BREVO_API_KEY` +- `Optional`: `BREVO_API_BASE_URL` (default: `https://api.brevo.com/v3`) +- `Optional`: `BREVO_API_TIMEOUT_SECONDS` (default: `20.0`) +- `Optional for Brevo newsletter sync`: `BREVO_508_MEMBERS_NEWSLETTER_LIST_ID` (explicit Brevo list ID override; use `4` for the 508 members list when setting it directly) +- `Optional`: `BREVO_508_MEMBERS_NEWSLETTER_LIST_NAME` (default: `508 members`; used to look up the list ID when the explicit ID is unset) +- `Optional for Keila contact sync`: `KEILA_API_KEY` +- `Optional`: `KEILA_API_BASE_URL` (default: `https://app.keila.io`) +- `Optional`: `KEILA_API_TIMEOUT_SECONDS` (default: `20.0`) +- `Optional`: `NEWSLETTER_SYNC_ENABLED` (default: `true`) +- `Optional`: `NEWSLETTER_SYNC_INTERVAL_SECONDS` (default: `604800`, one week) +- `Optional`: `NEWSLETTER_SYNC_EXCLUDED_MAILBOXES` (comma-separated mailbox local-parts or full addresses to skip during Migadu resync) +- Note: mailbox and backup email subscription to configured newsletter tools is best effort. Failures are reported as warnings and do not block mailbox or account creation. +- Note: the periodic sync uses Migadu mailboxes and password recovery emails as the source of truth for `@508.dev`. When CRM is configured, it only syncs mailboxes that match a CRM contact; it also skips configured excluded mailboxes and does not re-add provider-suppressed contacts. ## Authentik SSO Provisioning diff --git a/apps/admin_dashboard/src/main.tsx b/apps/admin_dashboard/src/main.tsx index 7059a010..560cd664 100644 --- a/apps/admin_dashboard/src/main.tsx +++ b/apps/admin_dashboard/src/main.tsx @@ -57,6 +57,7 @@ type View = | "gigs" | "projects" | "onboarding" + | "newsletter" | "jobs" | "agent" | "audit" @@ -116,6 +117,11 @@ const configurationGroups: ConfigurationGroupMetadata[] = [ description: "Editable onboarding integrations such as DocuSeal, Outline, and onboarding email SMTP.", }, + { + category: "Newsletter", + label: "Newsletter", + description: "Brevo, Keila, and recurring 508 members audience sync settings.", + }, { category: "AI", label: "AI Providers", @@ -422,6 +428,47 @@ type WikiMatchPreview = { }> } +type NewsletterSyncPreview = { + mailboxes_scanned?: number + contacts_considered?: number + providers?: Record< + string, + { + would_sync?: number + synced?: number + skipped?: number + failed?: number + } + > +} + +type NewsletterSuppression = { + email: string + source_provider: string + reason: string + active?: boolean + first_seen_at?: string + last_seen_at?: string + updated_at?: string + metadata?: Record +} + +type NewsletterStatus = { + scheduler_enabled?: boolean + interval_seconds?: number + active_suppression_count?: number + active_suppressed_email_count?: number + latest_job?: JobDetail | null +} + +type NewsletterProviderResult = { + synced?: number + would_sync?: number + skipped?: number + failed?: number + statuses?: Record +} + type AuditEvent = { id?: string occurred_at?: string @@ -490,6 +537,7 @@ const routes: Record = { gigs: "/dashboard/gigs", projects: "/dashboard/projects", onboarding: "/dashboard/onboarding", + newsletter: "/dashboard/newsletter", jobs: "/dashboard/jobs", agent: "/dashboard/agent", audit: "/dashboard/audit", @@ -501,6 +549,7 @@ const routePermissions: Record = { gigs: "gigs:read", projects: "projects:read", onboarding: "onboarding:read", + newsletter: "people:sync", jobs: "jobs:read", agent: "audit:read", audit: "audit:read", @@ -640,6 +689,52 @@ function messageFromUnknown(error: unknown, fallback: string) { return fallback } +function newsletterPreviewSummary(preview: NewsletterSyncPreview | undefined) { + if (!preview) return null + const providerSummaries = Object.entries(preview.providers || {}).map(([name, result]) => { + const wouldSync = result.would_sync ?? result.synced ?? 0 + return `${name}: ${wouldSync} would sync, ${result.skipped || 0} skipped, ${result.failed || 0} failed` + }) + const scanned = preview.mailboxes_scanned ?? 0 + const contacts = preview.contacts_considered ?? 0 + return [ + `${scanned} mailbox${scanned === 1 ? "" : "es"}`, + `${contacts} contact${contacts === 1 ? "" : "s"}`, + ...providerSummaries, + ].join("; ") +} + +function newsletterIntervalLabel(seconds?: number) { + const normalized = Number(seconds || 0) + if (!Number.isFinite(normalized) || normalized <= 0) return "Not configured" + if (normalized % 86400 === 0) { + const days = normalized / 86400 + return `${days} day${days === 1 ? "" : "s"}` + } + if (normalized % 3600 === 0) { + const hours = normalized / 3600 + return `${hours} hour${hours === 1 ? "" : "s"}` + } + if (normalized % 60 === 0) { + const minutes = normalized / 60 + return `${minutes} minute${minutes === 1 ? "" : "s"}` + } + return `${normalized} second${normalized === 1 ? "" : "s"}` +} + +function newsletterProviderResults(job?: JobDetail | null) { + const result = job?.result + if (!result || typeof result !== "object" || Array.isArray(result)) return [] + const providers = (result as { providers?: unknown }).providers + if (!providers || typeof providers !== "object" || Array.isArray(providers)) return [] + return Object.entries(providers) + .filter((entry): entry is [string, NewsletterProviderResult] => { + const [, value] = entry + return Boolean(value && typeof value === "object" && !Array.isArray(value)) + }) + .sort(([left], [right]) => left.localeCompare(right)) +} + function devErrorFromUnknown(error: unknown, fallback: string): DashboardDevError { const message = messageFromUnknown(error, fallback) const apiError = error instanceof ApiRequestError ? error : null @@ -737,7 +832,9 @@ async function requestJson(url: string, options: RequestInit = {}): Promise } -function sortValue(scope: View, item: Job | Person | Gig | Project | AuditEvent, key: string) { +type SortableItem = Job | Person | Gig | Project | AuditEvent | NewsletterSuppression + +function sortValue(scope: View, item: SortableItem, key: string) { if (scope === "gigs") { const gig = item as Gig if (key === "title") return gig.title || "" @@ -792,10 +889,18 @@ function sortValue(scope: View, item: Job | Person | Gig | Project | AuditEvent, if (key === "actor") return event.actor_display_name || event.actor_subject || event.actor_provider || "" } + if (scope === "newsletter") { + const record = item as NewsletterSuppression + if (key === "email") return record.email || "" + if (key === "source_provider") return record.source_provider || "" + if (key === "reason") return record.reason || "" + if (key === "first_seen_at") return record.first_seen_at || "" + if (key === "last_seen_at") return record.last_seen_at || record.updated_at || "" + } return (item as Record)[key] ?? "" } -function sortItems( +function sortItems( scope: View, items: T[], sort: { key: string; direction: SortDirection }, @@ -929,6 +1034,8 @@ function App() { const [notifications, setNotifications] = useState([]) const [notificationsOpen, setNotificationsOpen] = useState(false) const [people, setPeople] = useState([]) + const [newsletterSuppressions, setNewsletterSuppressions] = useState([]) + const [newsletterStatus, setNewsletterStatus] = useState(null) const [onboarding, setOnboarding] = useState([]) const [auditEvents, setAuditEvents] = useState([]) const [agentReport, setAgentReport] = useState(null) @@ -949,6 +1056,7 @@ function App() { onboarding: { key: "onboarding_state", direction: "asc" }, gigs: { key: "activity", direction: "desc" }, projects: { key: "display_name", direction: "asc" }, + newsletter: { key: "last_seen_at", direction: "desc" }, jobs: { key: "updated_at", direction: "desc" }, people: { key: "name", direction: "asc" }, agent: { key: "occurred_at", direction: "desc" }, @@ -971,6 +1079,7 @@ function App() { const [peopleFilters, setPeopleFilters] = useState({}) const [peopleFilterKind, setPeopleFilterKind] = useState("discord") const [peopleFilterValue, setPeopleFilterValue] = useState("linked") + const [newsletterProviderFilter, setNewsletterProviderFilter] = useState("") const [onboardingQuery, setOnboardingQuery] = useState("") const [onboardingState, setOnboardingState] = useState("") const [onboarderFilter, setOnboarderFilter] = useState("") @@ -1699,6 +1808,36 @@ function App() { } } + async function loadNewsletterStatus() { + setBusy("newsletterStatus", true) + try { + const payload = await requestJson("/dashboard/api/newsletter/status") + setNewsletterStatus(payload) + } catch (error) { + showError(error, "Unable to load newsletter sync status") + } finally { + setBusy("newsletterStatus", false) + } + } + + async function loadNewsletterSuppressions() { + setBusy("newsletterSuppressions", true) + try { + const payload = await requestJson<{ suppressions: NewsletterSuppression[] }>( + "/dashboard/api/newsletter/suppressions?limit=200", + ) + setNewsletterSuppressions(payload.suppressions || []) + } catch (error) { + showError(error, "Unable to load newsletter suppressions") + } finally { + setBusy("newsletterSuppressions", false) + } + } + + async function loadNewsletterDashboard() { + await Promise.all([loadNewsletterStatus(), loadNewsletterSuppressions()]) + } + function onboardingUrl() { const params = new URLSearchParams({ limit: "25" }) if (onboardingQuery.trim()) params.set("query", onboardingQuery.trim()) @@ -1939,6 +2078,32 @@ function App() { } } + async function syncNewsletters() { + setBusy("syncNewsletters", true) + showToast("Queueing newsletter sync") + try { + const payload = await requestJson<{ + job_id?: string + dry_run?: boolean + preview?: NewsletterSyncPreview + would_enqueue?: { job_type?: string } + }>("/dashboard/api/sync/newsletters", { + method: "POST", + }) + if (payload.dry_run) { + const summary = newsletterPreviewSummary(payload.preview) + showToast(summary ? `Dry run only: ${summary}` : "Dry run completed", "warning") + } else { + showToast(`Queued newsletter sync ${payload.job_id}`, "ok") + } + void loadNewsletterDashboard() + } catch (error) { + showError(error, "Unable to queue newsletter sync") + } finally { + setBusy("syncNewsletters", false) + } + } + async function assignOnboarder(contactId: string | undefined, onboarder: string) { const normalizedContactId = String(contactId || "").trim() const normalizedOnboarder = onboarder.trim() @@ -2167,6 +2332,7 @@ function App() { if (view === "gigs") void loadGigs() if (view === "projects") void loadProjects() if (view === "onboarding") void loadOnboarding() + if (view === "newsletter") void loadNewsletterDashboard() if (view === "jobs") void loadJobs() if (view === "agent") void loadAgentReport() if (view === "audit") void loadAuditEvents() @@ -2181,6 +2347,7 @@ function App() { if (view === "gigs") void loadGigs() if (view === "projects") void loadProjects() if (view === "onboarding") void loadOnboarding() + if (view === "newsletter") void loadNewsletterDashboard() if (view === "jobs") void loadJobs() if (view === "agent") void loadAgentReport() if (view === "audit") void loadAuditEvents() @@ -2243,6 +2410,29 @@ function App() { () => sortItems("projects", projects, sort.projects), [projects, sort.projects], ) + const newsletterProviderOptions = useMemo(() => { + const providerNames = new Set() + for (const record of newsletterSuppressions) { + if (record.source_provider) providerNames.add(record.source_provider) + } + for (const [providerName] of newsletterProviderResults(newsletterStatus?.latest_job)) { + providerNames.add(providerName) + } + return [...providerNames].sort((left, right) => left.localeCompare(right)) + }, [newsletterSuppressions, newsletterStatus]) + const sortedNewsletterSuppressions = useMemo( + () => + sortItems( + "newsletter", + newsletterProviderFilter + ? newsletterSuppressions.filter( + (record) => record.source_provider === newsletterProviderFilter, + ) + : newsletterSuppressions, + sort.newsletter, + ), + [newsletterProviderFilter, newsletterSuppressions, sort.newsletter], + ) const selectedGig = useMemo(() => { if (gigDetail?.id === selectedGigId) return gigDetail return sortedGigs.find((gig) => gig.id === selectedGigId) || null @@ -2416,6 +2606,7 @@ function App() { ["gigs", "Gigs", BriefcaseBusiness], ["projects", "Projects", FolderKanban], ["onboarding", "Onboarding", ClipboardList], + ["newsletter", "Newsletter", Mail], ["jobs", "Background tasks", Activity], ["agent", "Agent", ShieldCheck], ["audit", "Audit", FileClock], @@ -2486,6 +2677,22 @@ function App() { /> ) : null} + {view === "newsletter" ? ( + handleSort("newsletter", key)} + /> + ) : null} + {view === "gigs" ? ( + canSync: boolean + onRefresh: () => void + onSync: () => void + onProviderFilterChange: (value: string) => void + onSort: (key: string) => void +}) { + const latestJob = props.status?.latest_job || null + const uniqueSuppressed = props.status?.active_suppressed_email_count + const suppressionRows = props.status?.active_suppression_count + const providerResults = newsletterProviderResults(latestJob) + const providerOptions = props.providerFilter + ? [...new Set([...props.providerOptions, props.providerFilter])].sort((left, right) => + left.localeCompare(right), + ) + : props.providerOptions + const suppressionsByProvider = props.suppressions.reduce>( + (groups, record) => { + const provider = record.source_provider || "unknown" + groups[provider] = [...(groups[provider] || []), record] + return groups + }, + {}, + ) + const suppressionGroups = Object.entries(suppressionsByProvider).sort(([left], [right]) => + left.localeCompare(right), + ) + return ( + <> + + + Newsletter sync +
+ + {props.canSync ? ( + + ) : null} +
+
+ +
+
+ Scheduler + + {props.status?.scheduler_enabled ? "Enabled" : "Disabled"} + +
+
+ Interval + + {newsletterIntervalLabel(props.status?.interval_seconds)} + +
+
+ Suppressed emails + + {uniqueSuppressed ?? "Loading"} + +
+
+ Suppression rows + + {suppressionRows ?? "Loading"} + +
+
+
+
+ Latest sync + {latestJob ? ( + {latestJob.status} + ) : ( + No job found + )} +
+ {latestJob ? ( + <> +
+ {latestJob.type} | updated {formatDate(latestJob.updated_at)} | attempts{" "} + {latestJob.attempts}/{latestJob.max_attempts} +
+ {latestJob.last_error ? ( +
{latestJob.last_error}
+ ) : null} +
+ {providerResults.length ? ( + providerResults.map(([providerName, providerResult]) => { + const statuses = providerResult.statuses || {} + const statusEntries = Object.entries(statuses).sort(([left], [right]) => + left.localeCompare(right), + ) + const synced = providerResult.synced ?? providerResult.would_sync ?? 0 + return ( +
+
+ {providerName} +
+ {synced} synced + {providerResult.skipped || 0} skipped + {providerResult.failed ? ( + {providerResult.failed} failed + ) : null} +
+
+ {statusEntries.length ? ( +
+ {statusEntries.map(([status, count]) => ( + + {status.replaceAll("_", " ")}: {count} + + ))} +
+ ) : null} +
+ ) + }) + ) : ( +
+ No provider status details recorded for the latest sync. +
+ )} +
+ {latestJob.result ? ( +
+                    {jsonPreview(latestJob.result)}
+                  
+ ) : null} + + ) : ( +
+ No 508 members newsletter sync job was found in the last 90 days. +
+ )} +
+
+
+ + + + Newsletter suppressions +
+ + + {props.loading.newsletterSuppressions + ? "Loading" + : `${props.suppressions.length} shown`} + +
+
+ +
+ {suppressionGroups.map(([provider, records]) => ( +
+
+ {provider} + + {records.length} suppression{records.length === 1 ? "" : "s"} + +
+
+ + + + props.onSort(key)} + /> + props.onSort(key)} + /> + props.onSort(key)} + /> + props.onSort(key)} + /> + props.onSort(key)} + /> + + + + {records.map((record) => ( + + {record.email} + + {record.source_provider} + + {record.reason.replaceAll("_", " ")} + {formatDate(record.first_seen_at)} + + {formatDate(record.last_seen_at || record.updated_at)} + + + ))} + +
+
+
+ ))} +
+
+ + ) +} + function OnboardingView(props: { people: Person[] sort: { key: string; direction: SortDirection } diff --git a/apps/api/src/five08/backend/api.py b/apps/api/src/five08/backend/api.py index 2958350d..17ce59e6 100644 --- a/apps/api/src/five08/backend/api.py +++ b/apps/api/src/five08/backend/api.py @@ -142,6 +142,8 @@ send_onboarding_email_message, validate_plain_email, ) +from five08.newsletter_sync import NewsletterSyncProcessor +from five08.newsletter_suppressions import list_newsletter_suppressions from five08.projects import ( DEFAULT_WIKI_PROJECT_DOC_ID, PROJECT_ROSTER_KIND_HISTORICAL, @@ -166,6 +168,7 @@ runtime_config_definition_for_key, set_runtime_config_value, ) +from five08.redaction import redact_email_addresses from five08.worker.config import settings from five08.worker.db_migrations import run_job_migrations from five08.worker.dispatcher import build_queue_client @@ -453,7 +456,9 @@ def _get_agent_orchestrator() -> AgentOrchestrator: _AGENT_ORCHESTRATOR = AgentOrchestrator( registry=ToolRegistry( _AGENT_TASK_STORE, - runtime_config=ToolRuntimeConfig.from_settings(settings), + runtime_config_factory=lambda: ToolRuntimeConfig.from_settings( + settings + ), ), model_config=AgentModelConfig.from_settings(settings), intent_normalizer=OpenAICompatibleIntentNormalizer.from_settings( @@ -580,6 +585,12 @@ def _crm_sync_idempotency_key(*, now: datetime) -> str: return f"crm-sync:{bucket}" +def _newsletter_sync_idempotency_key(*, now: datetime) -> str: + interval_seconds = max(1, settings.newsletter_sync_interval_seconds) + bucket = int(now.timestamp()) // interval_seconds + return f"newsletter-sync:508-members:{bucket}" + + def _normalize_google_forms_input(value: str | None) -> str | None: if not isinstance(value, str): return None @@ -663,6 +674,37 @@ async def _enqueue_erpnext_project_sync_job( return job +async def _enqueue_newsletter_sync_job( + queue: QueueClient, + *, + reason: str, +) -> EnqueuedJob: + now = datetime.now(tz=timezone.utc) + idempotency_key = ( + _newsletter_sync_idempotency_key(now=now) + if reason == "scheduler" + else ( + f"newsletter-sync:508-members:{reason}:" + f"{now.strftime('%Y%m%d%H%M%S%f')}:{uuid4().hex}" + ) + ) + job: EnqueuedJob = await asyncio.to_thread( + enqueue_job, + queue=queue, + fn=JOB_FUNCTIONS["sync_508_members_newsletters_job"], + args=(), + settings=settings, + idempotency_key=idempotency_key, + ) + logger.info( + "Enqueued 508 members newsletter sync job id=%s created=%s reason=%s", + job.id, + job.created, + reason, + ) + return job + + async def _crm_sync_scheduler(app: FastAPI) -> None: queue = app.state.queue interval_seconds = max(1, settings.crm_sync_interval_seconds) @@ -674,6 +716,17 @@ async def _crm_sync_scheduler(app: FastAPI) -> None: await asyncio.sleep(interval_seconds) +async def _newsletter_sync_scheduler(app: FastAPI) -> None: + queue = app.state.queue + interval_seconds = max(1, settings.newsletter_sync_interval_seconds) + while True: + try: + await _enqueue_newsletter_sync_job(queue, reason="scheduler") + except Exception: + logger.exception("Failed scheduling 508 members newsletter sync job") + await asyncio.sleep(interval_seconds) + + async def _email_resume_scheduler() -> None: """Run periodic mailbox polling for resume ingestion.""" poller = ResumeMailboxProcessor(settings) @@ -6399,6 +6452,80 @@ async def dashboard_configuration_handler(request: Request) -> JSONResponse: return JSONResponse({"items": items}) +async def dashboard_newsletter_suppressions_handler( + request: Request, + limit: int = Query(default=200, ge=1, le=1000), +) -> JSONResponse: + """Return active newsletter suppressions for admin dashboard visibility.""" + _, error_response = await _dashboard_session_or_error( + request, + required_permission=DASHBOARD_PERMISSION_PEOPLE_SYNC, + ) + if error_response is not None: + return error_response + + records = await asyncio.to_thread( + list_newsletter_suppressions, + settings, + limit=limit, + active_only=True, + ) + return JSONResponse( + { + "suppressions": [ + { + "email": record.email, + "source_provider": record.source_provider, + "reason": record.reason, + "active": record.active, + "metadata": record.metadata, + "first_seen_at": record.first_seen_at.isoformat(), + "last_seen_at": record.last_seen_at.isoformat(), + "updated_at": record.updated_at.isoformat(), + } + for record in records + ] + } + ) + + +async def dashboard_newsletter_status_handler(request: Request) -> JSONResponse: + """Return current dashboard status for the 508 members newsletter sync.""" + _, error_response = await _dashboard_session_or_error( + request, + required_permission=DASHBOARD_PERMISSION_PEOPLE_SYNC, + ) + if error_response is not None: + return error_response + + recent_jobs = await asyncio.to_thread( + list_jobs, + settings, + created_after=datetime.now(tz=timezone.utc) - timedelta(days=90), + limit=1, + job_type=JOB_FUNCTIONS["sync_508_members_newsletters_job"].__name__, + ) + latest_job = recent_jobs[0] if recent_jobs else None + suppressions = await asyncio.to_thread( + list_newsletter_suppressions, + settings, + limit=1000, + active_only=True, + ) + suppressed_emails = {record.email for record in suppressions} + return JSONResponse( + { + "scheduler_enabled": settings.newsletter_sync_enabled, + "interval_seconds": settings.newsletter_sync_interval_seconds, + "active_suppression_count": len(suppressions), + "active_suppressed_email_count": len(suppressed_emails), + "latest_job": _dashboard_job_payload(latest_job) + if latest_job is not None + else None, + } + ) + + async def dashboard_update_configuration_handler( request: Request, key: str, @@ -6611,6 +6738,97 @@ async def dashboard_sync_people_handler(request: Request) -> JSONResponse: ) +def _redact_newsletter_sync_preview(value: object) -> object: + """Recursively redact emails from dashboard newsletter dry-run previews.""" + if isinstance(value, str): + return redact_email_addresses(value) + if isinstance(value, list): + return [_redact_newsletter_sync_preview(item) for item in value] + if isinstance(value, dict): + return { + key: _redact_newsletter_sync_preview(item) for key, item in value.items() + } + return value + + +async def dashboard_sync_newsletters_handler(request: Request) -> JSONResponse: + """Queue a 508 members newsletter sync from the authenticated dashboard.""" + session, error_response, dry_run = await _dashboard_write_session_or_dry_run( + request, + required_permission=DASHBOARD_PERMISSION_PEOPLE_SYNC, + dry_run_permission=DASHBOARD_PERMISSION_PEOPLE_SYNC_DRY_RUN, + ) + if error_response is not None: + return error_response + assert session is not None + + csrf_error = _dashboard_same_origin_post_or_error(request) + if csrf_error is not None: + return csrf_error + + if dry_run: + try: + preview = await asyncio.to_thread( + NewsletterSyncProcessor(settings).sync_508_members, + dry_run=True, + ) + except Exception as exc: + logger.warning( + "Newsletter sync dry run failed: %s", + type(exc).__name__, + ) + return JSONResponse( + { + "status": "dry_run_failed", + "dry_run": True, + "source": "dashboard", + "error": "newsletter_dry_run_failed", + }, + status_code=502, + ) + return JSONResponse( + { + "status": "dry_run", + "dry_run": True, + "source": "dashboard", + "preview": _redact_newsletter_sync_preview(preview), + "would_enqueue": { + "queue": settings.redis_queue_name, + "job_type": "sync_508_members_newsletters_job", + "reason": "dashboard", + "idempotency_key_pattern": "newsletter-sync:508-members:dashboard::", + }, + } + ) + + job = await _enqueue_newsletter_sync_job( + request.app.state.queue, reason="dashboard" + ) + actor_provider, actor_subject = _session_audit_actor(session) + await _write_auth_audit_event( + action="newsletter.508_members_sync", + result=AuditResult.SUCCESS, + actor_subject=actor_subject, + actor_display_name=session.display_name, + actor_provider=actor_provider, + resource_type="newsletter_sync", + resource_id=job.id, + metadata={ + "source": "dashboard", + "queue": settings.redis_queue_name, + }, + ) + return JSONResponse( + { + "status": "queued", + "source": "dashboard", + "job_id": job.id, + "created": job.created, + }, + status_code=202, + ) + + async def espocrm_people_sync_webhook_handler(request: Request) -> JSONResponse: """Queue per-contact people cache sync jobs from CRM webhook events.""" if not _is_webhook_authorized(request): @@ -8085,6 +8303,13 @@ async def _lifespan(app: FastAPI) -> Any: else: logger.info("CRM sync scheduler disabled by config") + if settings.newsletter_sync_enabled: + app.state.newsletter_sync_task = asyncio.create_task( + _newsletter_sync_scheduler(app) + ) + else: + logger.info("508 members newsletter sync scheduler disabled by config") + if settings.email_resume_intake_enabled: app.state.email_resume_task = asyncio.create_task(_email_resume_scheduler()) else: @@ -8105,6 +8330,12 @@ async def _lifespan(app: FastAPI) -> Any: with contextlib.suppress(asyncio.CancelledError): await task + if hasattr(app.state, "newsletter_sync_task"): + task = app.state.newsletter_sync_task + task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await task + if hasattr(app.state, "http_client"): await app.state.http_client.aclose() @@ -8325,11 +8556,26 @@ def create_app(*, run_lifespan: bool = True) -> FastAPI: dashboard_update_configuration_handler, methods=["PUT"], ) + app.add_api_route( + "/dashboard/api/newsletter/suppressions", + dashboard_newsletter_suppressions_handler, + methods=["GET"], + ) + app.add_api_route( + "/dashboard/api/newsletter/status", + dashboard_newsletter_status_handler, + methods=["GET"], + ) app.add_api_route( "/dashboard/api/sync/people", dashboard_sync_people_handler, methods=["POST"], ) + app.add_api_route( + "/dashboard/api/sync/newsletters", + dashboard_sync_newsletters_handler, + methods=["POST"], + ) app.add_api_route( "/dashboard/gigs/{item_id}", dashboard_handler, diff --git a/apps/api/src/five08/backend/static/dashboard/.vite/manifest.json b/apps/api/src/five08/backend/static/dashboard/.vite/manifest.json index e905a9b4..df9e40c8 100644 --- a/apps/api/src/five08/backend/static/dashboard/.vite/manifest.json +++ b/apps/api/src/five08/backend/static/dashboard/.vite/manifest.json @@ -1,11 +1,11 @@ { "index.html": { - "file": "assets/index-DUbmN0NW.js", + "file": "assets/index-BlyQwUQ4.js", "name": "index", "src": "index.html", "isEntry": true, "css": [ - "assets/index-BoK8s4aw.css" + "assets/index-Dtgvsgfe.css" ] } } \ No newline at end of file diff --git a/apps/api/src/five08/backend/static/dashboard/assets/index-BlyQwUQ4.js b/apps/api/src/five08/backend/static/dashboard/assets/index-BlyQwUQ4.js new file mode 100644 index 00000000..06bb5569 --- /dev/null +++ b/apps/api/src/five08/backend/static/dashboard/assets/index-BlyQwUQ4.js @@ -0,0 +1,9 @@ +var e=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var t=e((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var ee=Array.isArray;function te(){}var S={H:null,A:null,T:null,S:null},C=Object.prototype.hasOwnProperty;function ne(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function re(e,t){return ne(e.type,t,e.props)}function ie(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function ae(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var oe=/\/+/g;function se(e,t){return typeof e==`object`&&e&&e.key!=null?ae(``+e.key):t.toString(36)}function ce(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(te,te):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function le(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,le(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+se(e,0):a,ee(o)?(i=``,c!=null&&(i=c.replace(oe,`$&/`)+`/`),le(o,r,i,``,function(e){return e})):o!=null&&(ie(o)&&(o=re(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(oe,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(ee(e))for(var u=0;u{n.exports=t()})),r=(...e)=>e.filter((e,t,n)=>!!e&&e.trim()!==``&&n.indexOf(e)===t).join(` `).trim(),i=e=>e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).toLowerCase(),a=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,t,n)=>n?n.toUpperCase():t.toLowerCase()),o=e=>{let t=a(e);return t.charAt(0).toUpperCase()+t.slice(1)},s={xmlns:`http://www.w3.org/2000/svg`,width:24,height:24,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:2,strokeLinecap:`round`,strokeLinejoin:`round`},c=e=>{for(let t in e)if(t.startsWith(`aria-`)||t===`role`||t===`title`)return!0;return!1},l=n(),u=(0,l.createContext)({}),d=()=>(0,l.useContext)(u),f=(0,l.forwardRef)(({color:e,size:t,strokeWidth:n,absoluteStrokeWidth:i,className:a=``,children:o,iconNode:u,...f},p)=>{let{size:m=24,strokeWidth:h=2,absoluteStrokeWidth:g=!1,color:_=`currentColor`,className:v=``}=d()??{},y=i??g?Number(n??h)*24/Number(t??m):n??h;return(0,l.createElement)(`svg`,{ref:p,...s,width:t??m??s.width,height:t??m??s.height,stroke:e??_,strokeWidth:y,className:r(`lucide`,v,a),...!o&&!c(f)&&{"aria-hidden":`true`},...f},[...u.map(([e,t])=>(0,l.createElement)(e,t)),...Array.isArray(o)?o:[o]])}),p=(e,t)=>{let n=(0,l.forwardRef)(({className:n,...a},s)=>(0,l.createElement)(f,{ref:s,iconNode:t,className:r(`lucide-${i(o(e))}`,`lucide-${e}`,n),...a}));return n.displayName=o(e),n},m=p(`activity`,[[`path`,{d:`M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2`,key:`169zse`}]]),h=p(`arrow-left`,[[`path`,{d:`m12 19-7-7 7-7`,key:`1l729n`}],[`path`,{d:`M19 12H5`,key:`x3x0zl`}]]),g=p(`bell`,[[`path`,{d:`M10.268 21a2 2 0 0 0 3.464 0`,key:`vwvbt9`}],[`path`,{d:`M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326`,key:`11g9vi`}]]),_=p(`briefcase-business`,[[`path`,{d:`M12 12h.01`,key:`1mp3jc`}],[`path`,{d:`M16 6V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2`,key:`1ksdt3`}],[`path`,{d:`M22 13a18.15 18.15 0 0 1-20 0`,key:`12hx5q`}],[`rect`,{width:`20`,height:`14`,x:`2`,y:`6`,rx:`2`,key:`i6l2r4`}]]),v=p(`clipboard-list`,[[`rect`,{width:`8`,height:`4`,x:`8`,y:`2`,rx:`1`,ry:`1`,key:`tgr4d6`}],[`path`,{d:`M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2`,key:`116196`}],[`path`,{d:`M12 11h4`,key:`1jrz19`}],[`path`,{d:`M12 16h4`,key:`n85exb`}],[`path`,{d:`M8 11h.01`,key:`1dfujw`}],[`path`,{d:`M8 16h.01`,key:`18s6g9`}]]),y=p(`external-link`,[[`path`,{d:`M15 3h6v6`,key:`1q9fwt`}],[`path`,{d:`M10 14 21 3`,key:`gplh6r`}],[`path`,{d:`M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6`,key:`a6xqqp`}]]),b=p(`file-clock`,[[`path`,{d:`M16 22h2a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v2.85`,key:`ryk6xj`}],[`path`,{d:`M14 2v5a1 1 0 0 0 1 1h5`,key:`wfsgrz`}],[`path`,{d:`M8 14v2.2l1.6 1`,key:`6m4bie`}],[`circle`,{cx:`8`,cy:`16`,r:`6`,key:`10v15b`}]]),x=p(`folder-kanban`,[[`path`,{d:`M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z`,key:`1fr9dc`}],[`path`,{d:`M8 10v4`,key:`tgpxqk`}],[`path`,{d:`M12 10v2`,key:`hh53o1`}],[`path`,{d:`M16 10v6`,key:`1d6xys`}]]),ee=p(`log-out`,[[`path`,{d:`m16 17 5-5-5-5`,key:`1bji2h`}],[`path`,{d:`M21 12H9`,key:`dn1m92`}],[`path`,{d:`M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4`,key:`1uf3rs`}]]),te=p(`mail`,[[`path`,{d:`m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7`,key:`132q7q`}],[`rect`,{x:`2`,y:`4`,width:`20`,height:`16`,rx:`2`,key:`izxlao`}]]),S=p(`plus`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`M12 5v14`,key:`s699le`}]]),C=p(`refresh-cw`,[[`path`,{d:`M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8`,key:`v9h5vc`}],[`path`,{d:`M21 3v5h-5`,key:`1q7to0`}],[`path`,{d:`M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16`,key:`3uifl3`}],[`path`,{d:`M8 16H3v5`,key:`1cv678`}]]),ne=p(`search`,[[`path`,{d:`m21 21-4.34-4.34`,key:`14j7rj`}],[`circle`,{cx:`11`,cy:`11`,r:`8`,key:`4ej97u`}]]),re=p(`send`,[[`path`,{d:`M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z`,key:`1ffxy3`}],[`path`,{d:`m21.854 2.147-10.94 10.939`,key:`12cjpa`}]]),ie=p(`settings`,[[`path`,{d:`M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915`,key:`1i5ecw`}],[`circle`,{cx:`12`,cy:`12`,r:`3`,key:`1v7zrd`}]]),ae=p(`shield-check`,[[`path`,{d:`M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z`,key:`oel41y`}],[`path`,{d:`m9 12 2 2 4-4`,key:`dzmm74`}]]),oe=p(`user-minus`,[[`path`,{d:`M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2`,key:`1yyitq`}],[`circle`,{cx:`9`,cy:`7`,r:`4`,key:`nufk8`}],[`line`,{x1:`22`,x2:`16`,y1:`11`,y2:`11`,key:`1shjgl`}]]),se=p(`user-plus`,[[`path`,{d:`M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2`,key:`1yyitq`}],[`circle`,{cx:`9`,cy:`7`,r:`4`,key:`nufk8`}],[`line`,{x1:`19`,x2:`19`,y1:`8`,y2:`14`,key:`1bvyxn`}],[`line`,{x1:`22`,x2:`16`,y1:`11`,y2:`11`,key:`1shjgl`}]]),ce=p(`users`,[[`path`,{d:`M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2`,key:`1yyitq`}],[`path`,{d:`M16 3.128a4 4 0 0 1 0 7.744`,key:`16gr8j`}],[`path`,{d:`M22 21v-2a4 4 0 0 0-3-3.87`,key:`kshegd`}],[`circle`,{cx:`9`,cy:`7`,r:`4`,key:`nufk8`}]]),le=p(`x`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]),w=e((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,ee||(ee=!0,ie());else{var t=n(l);t!==null&&se(x,t.startTime-e)}}var ee=!1,te=-1,S=5,C=-1;function ne(){return g?!0:!(e.unstable_now()-Ct&&ne());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&se(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?ie():ee=!1}}}var ie;if(typeof y==`function`)ie=function(){y(re)};else if(typeof MessageChannel<`u`){var ae=new MessageChannel,oe=ae.port2;ae.port1.onmessage=re,ie=function(){oe.postMessage(null)}}else ie=function(){_(re,0)};function se(t,n){te=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(te),te=-1):h=!0,se(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,ee||(ee=!0,ie()))),r},e.unstable_shouldYield=ne,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),ue=e(((e,t)=>{t.exports=w()})),T=e((e=>{var t=n();function r(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=T()})),fe=e((e=>{var t=ue(),r=n(),i=de();function a(e){var t=`https://react.dev/errors/`+e;if(1me||(e.current=pe[me],pe[me]=null,me--)}function O(e,t){me++,pe[me]=e.current,e.current=t}var he=E(null),k=E(null),ge=E(null),_e=E(null);function ve(e,t){switch(O(ge,t),O(k,e),O(he,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}D(he),O(he,e)}function ye(){D(he),D(k),D(ge)}function be(e){e.memoizedState!==null&&O(_e,e);var t=he.current,n=Hd(t,e.type);t!==n&&(O(k,e),O(he,n))}function xe(e){k.current===e&&(D(he),D(k)),_e.current===e&&(D(_e),Qf._currentValue=fe)}var Se,Ce;function we(e){if(Se===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);Se=t&&t[1]||``,Ce=-1)`:-1i||c[r]!==l[i]){var u=` +`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{Te=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?we(n):``}function De(e,t){switch(e.tag){case 26:case 27:case 5:return we(e.type);case 16:return we(`Lazy`);case 13:return e.child!==t&&t!==null?we(`Suspense Fallback`):we(`Suspense`);case 19:return we(`SuspenseList`);case 0:case 15:return Ee(e.type,!1);case 11:return Ee(e.type.render,!1);case 1:return Ee(e.type,!0);case 31:return we(`Activity`);default:return``}}function Oe(e){try{var t=``,n=null;do t+=De(e,n),n=e,e=e.return;while(e);return t}catch(e){return` +Error generating stack: `+e.message+` +`+e.stack}}var ke=Object.prototype.hasOwnProperty,Ae=t.unstable_scheduleCallback,je=t.unstable_cancelCallback,Me=t.unstable_shouldYield,Ne=t.unstable_requestPaint,Pe=t.unstable_now,Fe=t.unstable_getCurrentPriorityLevel,Ie=t.unstable_ImmediatePriority,Le=t.unstable_UserBlockingPriority,Re=t.unstable_NormalPriority,ze=t.unstable_LowPriority,Be=t.unstable_IdlePriority,Ve=t.log,He=t.unstable_setDisableYieldValue,Ue=null,We=null;function Ge(e){if(typeof Ve==`function`&&He(e),We&&typeof We.setStrictMode==`function`)try{We.setStrictMode(Ue,e)}catch{}}var Ke=Math.clz32?Math.clz32:Ye,qe=Math.log,Je=Math.LN2;function Ye(e){return e>>>=0,e===0?32:31-(qe(e)/Je|0)|0}var Xe=256,Ze=262144,Qe=4194304;function $e(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function et(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=$e(n))):i=$e(o):i=$e(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=$e(n))):i=$e(o)):i=$e(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function tt(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function nt(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function rt(){var e=Qe;return Qe<<=1,!(Qe&62914560)&&(Qe=4194304),e}function A(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function it(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function at(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),nn=!1;if(tn)try{var rn={};Object.defineProperty(rn,`passive`,{get:function(){nn=!0}}),window.addEventListener(`test`,rn,rn),window.removeEventListener(`test`,rn,rn)}catch{nn=!1}var an=null,on=null,sn=null;function cn(){if(sn)return sn;var e,t=on,n=t.length,r,i=`value`in an?an.value:an.textContent,a=i.length;for(e=0;e=Rn),Vn=` `,Hn=!1;function Un(e,t){switch(e){case`keyup`:return In.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function Wn(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var Gn=!1;function Kn(e,t){switch(e){case`compositionend`:return Wn(t);case`keypress`:return t.which===32?(Hn=!0,Vn):null;case`textInput`:return e=t.data,e===Vn&&Hn?null:e;default:return null}}function qn(e,t){if(Gn)return e===`compositionend`||!Ln&&Un(e,t)?(e=cn(),sn=on=an=null,Gn=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=hr(n)}}function _r(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?_r(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function vr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=L(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=L(e.document)}return t}function yr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var br=tn&&`documentMode`in document&&11>=document.documentMode,xr=null,Sr=null,Cr=null,wr=!1;function Tr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;wr||xr==null||xr!==L(r)||(r=xr,`selectionStart`in r&&yr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Cr&&mr(Cr,r)||(Cr=r,r=Td(Sr,`onSelect`),0>=o,i-=o,_i=1<<32-Ke(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),J&&yi(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(i,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(i,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(i,h),J&&yi(i,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(i,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return J&&yi(i,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,i,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(i,e)}),J&&yi(i,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===_&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case h:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===_){if(r.tag===7){n(e,r.sibling),c=i(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===ne&&ya(l)===r.type){n(e,r.sibling),c=i(r,o.props),Ea(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===_?(c=ii(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=ri(o.type,o.key,o.props,null,e.mode,c),Ea(c,o),c.return=e,e=c)}return s(e);case g:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=i(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=si(o,e.mode,c),c.return=e,e=c}return s(e);case ne:return o=ya(o),b(e,r,o,c)}if(le(o))return v(e,r,o,c);if(oe(o)){if(l=oe(o),typeof l!=`function`)throw Error(a(150));return o=l.call(o),y(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Ta(o),c);if(o.$$typeof===x)return b(e,r,qi(e,o),c);Da(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=i(r,o),c.return=e,e=c):(n(e,r),c=ai(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{wa=0;var i=b(e,t,n,r);return Ca=null,i}catch(t){if(t===pa||t===ha)throw t;var a=$r(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var ka=Oa(!0),Aa=Oa(!1),ja=!1;function Ma(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Na(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Pa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Fa(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,X&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=Xr(e),Yr(e,null,n),t}return Kr(e,r,t,n),Xr(e)}function Ia(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,st(e,n)}}function La(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ra=!1;function za(){if(Ra){var e=ia;if(e!==null)throw e}}function Ba(e,t,n,r){Ra=!1;var i=e.updateQueue;ja=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,m=f!==s.lane;if(m?(Q&f)===f:(r&f)===f){f!==0&&f===ra&&(Ra=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=p({},d,f);break a;case 2:ja=!0}}f=s.callback,f!==null&&(e.flags|=64,m&&(e.flags|=8192),m=i.callbacks,m===null?i.callbacks=[f]:m.push(f))}else m={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=m,c=d):u=u.next=m,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;m=s,s=m.next,m.next=null,i.lastBaseUpdate=m,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Ul|=o,e.lanes=o,e.memoizedState=d}}function Va(e,t){if(typeof e!=`function`)throw Error(a(191,e));e.call(t)}function Ha(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=w.T,s={};w.T=s,Os(e,!1,t,n);try{var c=i(),l=w.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Ds(e,t,sa(c,r),du(e)):Ds(e,t,r,du(e))}catch(n){Ds(e,t,{then:function(){},status:`rejected`,reason:n},du())}finally{T.p=a,o!==null&&s.types!==null&&(o.types=s.types),w.T=o}}function _s(){}function vs(e,t,n,r){if(e.tag!==5)throw Error(a(476));var i=ys(e).queue;gs(e,i,t,fe,n===null?_s:function(){return bs(e),n(r)})}function ys(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:fe,baseState:fe,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ko,lastRenderedState:fe},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ko,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function bs(e){var t=ys(e);t.next===null&&(t=e.alternate.memoizedState),Ds(e,t.next.queue,{},du())}function xs(){return Ki(Qf)}function Ss(){return wo().memoizedState}function Cs(){return wo().memoizedState}function ws(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=du();e=Pa(n);var r=Fa(t,e,n);r!==null&&(pu(r,t,n),Ia(r,t,n)),t={cache:$i()},e.payload=t;return}t=t.return}}function Ts(e,t,n){var r=du();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},ks(e)?As(t,n):(n=qr(e,t,n,r),n!==null&&(pu(n,e,r),js(n,t,r)))}function Es(e,t,n){Ds(e,t,n,du())}function Ds(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(ks(e))As(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,pr(s,o))return Kr(e,t,i,0),Fl===null&&Gr(),!1}catch{}if(n=qr(e,t,i,r),n!==null)return pu(n,e,r),js(n,t,r),!0}return!1}function Os(e,t,n,r){if(r={lane:2,revertLane:ud(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},ks(e)){if(t)throw Error(a(479))}else t=qr(e,n,r,2),t!==null&&pu(t,e,2)}function ks(e){var t=e.alternate;return e===Y||t!==null&&t===Y}function As(e,t){so=oo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function js(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,st(e,n)}}var Ms={readContext:Ki,use:Do,useCallback:mo,useContext:mo,useEffect:mo,useImperativeHandle:mo,useLayoutEffect:mo,useInsertionEffect:mo,useMemo:mo,useReducer:mo,useRef:mo,useState:mo,useDebugValue:mo,useDeferredValue:mo,useTransition:mo,useSyncExternalStore:mo,useId:mo,useHostTransitionStatus:mo,useFormState:mo,useActionState:mo,useOptimistic:mo,useMemoCache:mo,useCacheRefresh:mo};Ms.useEffectEvent=mo;var Ns={readContext:Ki,use:Do,useCallback:function(e,t){return Co().memoizedState=[e,t===void 0?null:t],e},useContext:Ki,useEffect:rs,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),ts(4194308,4,ls.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ts(4194308,4,e,t)},useInsertionEffect:function(e,t){ts(4,2,e,t)},useMemo:function(e,t){var n=Co();t=t===void 0?null:t;var r=e();if(co){Ge(!0);try{e()}finally{Ge(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Co();if(n!==void 0){var i=n(t);if(co){Ge(!0);try{n(t)}finally{Ge(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Ts.bind(null,Y,e),[r.memoizedState,e]},useRef:function(e){var t=Co();return e={current:e},t.memoizedState=e},useState:function(e){e=zo(e);var t=e.queue,n=Es.bind(null,Y,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:ds,useDeferredValue:function(e,t){return ms(Co(),e,t)},useTransition:function(){var e=zo(!1);return e=gs.bind(null,Y,e.queue,!0,!1),Co().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=Y,i=Co();if(J){if(n===void 0)throw Error(a(407));n=n()}else{if(n=t(),Fl===null)throw Error(a(349));Q&127||Po(r,t,n)}i.memoizedState=n;var o={value:n,getSnapshot:t};return i.queue=o,rs(Io.bind(null,r,o,e),[e]),r.flags|=2048,$o(9,{destroy:void 0},Fo.bind(null,r,o,n,t),null),n},useId:function(){var e=Co(),t=Fl.identifierPrefix;if(J){var n=vi,r=_i;n=(r&~(1<<32-Ke(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=lo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(i,{is:r.is}):s.createElement(i)}}o[mt]=t,o[j]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Pd(o,i,r),i){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Dc(t)}}return Mc(t),Oc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Dc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(a(166));if(e=ge.current,Mi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,i=wi,i!==null)switch(i.tag){case 27:case 5:r=i.memoizedProps}e[mt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||jd(e.nodeValue,n)),e||ki(t,!0)}else e=Bd(e).createTextNode(r),e[mt]=t,t.stateNode=e}return Mc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Mi(t),n!==null){if(e===null){if(!r)throw Error(a(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(a(557));e[mt]=t}else Ni(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Mc(t),e=!1}else n=Pi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(eo(t),t):(eo(t),null);if(t.flags&128)throw Error(a(558))}return Mc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(i=Mi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!i)throw Error(a(318));if(i=t.memoizedState,i=i===null?null:i.dehydrated,!i)throw Error(a(317));i[mt]=t}else Ni(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Mc(t),i=!1}else i=Pi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=i),i=!0;if(!i)return t.flags&256?(eo(t),t):(eo(t),null)}return eo(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,i=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(i=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==i&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Ac(t,t.updateQueue),Mc(t),null);case 4:return ye(),e===null&&xd(t.stateNode.containerInfo),Mc(t),null;case 10:return Bi(t.type),Mc(t),null;case 19:if(D(to),r=t.memoizedState,r===null)return Mc(t),null;if(i=(t.flags&128)!=0,o=r.rendering,o===null)if(i)jc(r,!1);else{if(Hl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=no(e),o!==null){for(t.flags|=128,jc(r,!1),e=o.updateQueue,t.updateQueue=e,Ac(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)ni(n,e),n=n.sibling;return O(to,to.current&1|2),J&&yi(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Pe()>$l&&(t.flags|=128,i=!0,jc(r,!1),t.lanes=4194304)}else{if(!i)if(e=no(o),e!==null){if(t.flags|=128,i=!0,e=e.updateQueue,t.updateQueue=e,Ac(t,e),jc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!J)return Mc(t),null}else 2*Pe()-r.renderingStartTime>$l&&n!==536870912&&(t.flags|=128,i=!0,jc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(Mc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Pe(),e.sibling=null,n=to.current,O(to,i?n&1|2:n&1),J&&yi(t,r.treeForkCount),e);case 22:case 23:return eo(t),qa(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(Mc(t),t.subtreeFlags&6&&(t.flags|=8192)):Mc(t),n=t.updateQueue,n!==null&&Ac(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&D(la),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Bi(Qi),Mc(t),null;case 25:return null;case 30:return null}throw Error(a(156,t.tag))}function Pc(e,t){switch(Si(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Bi(Qi),ye(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return xe(t),null;case 31:if(t.memoizedState!==null){if(eo(t),t.alternate===null)throw Error(a(340));Ni()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(eo(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(a(340));Ni()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return D(to),null;case 4:return ye(),null;case 10:return Bi(t.type),null;case 22:case 23:return eo(t),qa(),e!==null&&D(la),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Bi(Qi),null;case 25:return null;default:return null}}function Fc(e,t){switch(Si(t),t.tag){case 3:Bi(Qi),ye();break;case 26:case 27:case 5:xe(t);break;case 4:ye();break;case 31:t.memoizedState!==null&&eo(t);break;case 13:eo(t);break;case 19:D(to);break;case 10:Bi(t.type);break;case 22:case 23:eo(t),qa(),e!==null&&D(la);break;case 24:Bi(Qi)}}function Ic(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Uu(t,t.return,e)}}function Lc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Uu(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Uu(t,t.return,e)}}function Rc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Ha(t,n)}catch(t){Uu(e,e.return,t)}}}function zc(e,t,n){n.props=Bs(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Uu(e,t,n)}}function Bc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Uu(e,t,n)}}function Vc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Uu(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Uu(e,t,n)}else n.current=null}function Hc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Uu(e,e.return,t)}}function Uc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[j]=t}catch(t){Uu(e,e.return,t)}}function Wc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function Gc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Wc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Kc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=G));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Kc(e,t,n),e=e.sibling;e!==null;)Kc(e,t,n),e=e.sibling}function qc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(qc(e,t,n),e=e.sibling;e!==null;)qc(e,t,n),e=e.sibling}function Jc(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[mt]=e,t[j]=n}catch(t){Uu(e,e.return,t)}}var Yc=!1,Xc=!1,Zc=!1,Qc=typeof WeakSet==`function`?WeakSet:Set,$c=null;function el(e,t){if(e=e.containerInfo,Rd=sp,e=vr(e),yr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var i=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||i!==0&&f.nodeType!==3||(c=s+i),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===i&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,$c=t;$c!==null;)if(t=$c,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,$c=e;else for(;$c!==null;){switch(t=$c,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(o,r,n),o[mt]=e,Tt(o),r=o;break a;case`link`:var s=Vf(`link`,`href`,i).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=gr(s,h),v=gr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,w.T=null,n=su,su=null;var o=ru,s=au;if(nu=0,iu=ru=null,au=0,X&6)throw Error(a(331));var c=X;if(X|=4,Al(o.current),Sl(o,o.current,s,n),X=c,rd(0,!1),We&&typeof We.onPostCommitFiberRoot==`function`)try{We.onPostCommitFiberRoot(Ue,o)}catch{}return!0}finally{T.p=i,w.T=r,zu(e,t)}}function Hu(e,t,n){t=li(n,t),t=Ks(e.stateNode,t,2),e=Fa(e,t,2),e!==null&&(it(e,2),nd(e))}function Uu(e,t,n){if(e.tag===3)Hu(e,e,n);else for(;t!==null;){if(t.tag===3){Hu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(tu===null||!tu.has(r))){e=li(n,e),n=qs(2),r=Fa(t,n,2),r!==null&&(Js(n,r,t,e),it(r,2),nd(r));break}}t=t.return}}function Wu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Pl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Bl=!0,i.add(n),e=Gu.bind(null,e,t,n),t.then(e,e))}function Gu(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,Fl===e&&(Q&n)===n&&(Hl===4||Hl===3&&(Q&62914560)===Q&&300>Pe()-Zl?!(X&2)&&bu(e,0):Gl|=n,ql===Q&&(ql=0)),nd(e)}function Ku(e,t){t===0&&(t=rt()),e=Jr(e,t),e!==null&&(it(e,t),nd(e))}function qu(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Ku(e,n)}function Ju(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,i=e.memoizedState;i!==null&&(n=i.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(a(314))}r!==null&&r.delete(t),Ku(e,n)}function Yu(e,t){return Ae(e,t)}var Xu=null,Zu=null,Qu=!1,$u=!1,ed=!1,td=0;function nd(e){e!==Zu&&e.next===null&&(Zu===null?Xu=Zu=e:Zu=Zu.next=e),$u=!0,Qu||(Qu=!0,ld())}function rd(e,t){if(!ed&&$u){ed=!0;do for(var n=!1,r=Xu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Ke(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,cd(r,a))}else a=Q,a=et(r,r===Fl?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||tt(r,a)||(n=!0,cd(r,a));r=r.next}while(n);ed=!1}}function id(){ad()}function ad(){$u=Qu=!1;var e=0;td!==0&&Gd()&&(e=td);for(var t=Pe(),n=null,r=Xu;r!==null;){var i=r.next,a=od(r,t);a===0?(r.next=null,n===null?Xu=i:n.next=i,i===null&&(Zu=n)):(n=r,(e!==0||a&3)&&($u=!0)),r=i}nu!==0&&nu!==5||rd(e,!1),td!==0&&(td=0)}function od(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=R(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),Tt(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+R(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+R(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+R(n.imageSizes)+`"]`)):i+=`[href="`+R(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=p({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),Tt(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+R(r)+`"][href="`+R(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=p({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),Tt(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=wt(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=p({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);Tt(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=wt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=p({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Tt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=wt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=p({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Tt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var i=(i=ge.current)?gf(i):null;if(!i)throw Error(a(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=wt(i).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var o=wt(i).hoistableStyles,s=o.get(e);if(s||(i=i.ownerDocument||i,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=i.querySelector(jf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),o||Nf(i,e,n,s.state))),t&&r===null)throw Error(a(528,``));return s}if(t&&r!==null)throw Error(a(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=wt(i).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(a(444,e))}}function Af(e){return`href="`+R(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return p({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),Tt(t),e.head.appendChild(t))}function Pf(e){return`[src="`+R(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+R(n.href)+`"]`);if(r)return t.instance=r,Tt(r),r;var i=p({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),Tt(r),Pd(r,`style`,i),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:i=Af(n.href);var o=e.querySelector(jf(i));if(o)return t.state.loading|=4,t.instance=o,Tt(o),o;r=Mf(n),(i=mf.get(i))&&Rf(r,i),o=(e.ownerDocument||e).createElement(`link`),Tt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Pd(o,`link`,r),t.state.loading|=4,Lf(o,n.precedence,e),t.instance=o;case`script`:return o=Pf(n.src),(i=e.querySelector(Ff(o)))?(t.instance=i,Tt(i),i):(r=n,(i=mf.get(o))&&(r=p({},n),zf(r,i)),e=e.ownerDocument||e,i=e.createElement(`script`),Tt(i),Pd(i,`link`,r),e.head.appendChild(i),t.instance=i);case`void`:return null;default:throw Error(a(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,Tt(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),Tt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=fe()}))();function me(e){var t,n,r=``;if(typeof e==`string`||typeof e==`number`)r+=e;else if(typeof e==`object`)if(Array.isArray(e)){var i=e.length;for(t=0;ttypeof e==`boolean`?`${e}`:e===0?`0`:e,O=E,he=(e,t)=>n=>{if(t?.variants==null)return O(e,n?.class,n?.className);let{variants:r,defaultVariants:i}=t,a=Object.keys(r).map(e=>{let t=n?.[e],a=i?.[e];if(t===null)return null;let o=D(t)||D(a);return r[e][o]}),o=n&&Object.entries(n).reduce((e,t)=>{let[n,r]=t;return r===void 0||(e[n]=r),e},{});return O(e,a,t?.compoundVariants?.reduce((e,t)=>{let{class:n,className:r,...a}=t;return Object.entries(a).every(e=>{let[t,n]=e;return Array.isArray(n)?n.includes({...i,...o}[t]):{...i,...o}[t]===n})?[...e,n,r]:e},[]),n?.class,n?.className)},k=(e,t)=>{let n=Array(e.length+t.length);for(let t=0;t({classGroupId:e,validator:t}),_e=(e=new Map,t=null,n)=>({nextPart:e,validators:t,classGroupId:n}),ve=`-`,ye=[],be=`arbitrary..`,xe=e=>{let t=we(e),{conflictingClassGroups:n,conflictingClassGroupModifiers:r}=e;return{getClassGroupId:e=>{if(e.startsWith(`[`)&&e.endsWith(`]`))return Ce(e);let n=e.split(ve);return Se(n,+(n[0]===``&&n.length>1),t)},getConflictingClassGroupIds:(e,t)=>{if(t){let t=r[e],i=n[e];return t?i?k(i,t):t:i||ye}return n[e]||ye}}},Se=(e,t,n)=>{if(e.length-t===0)return n.classGroupId;let r=e[t],i=n.nextPart.get(r);if(i){let n=Se(e,t+1,i);if(n)return n}let a=n.validators;if(a===null)return;let o=t===0?e.join(ve):e.slice(t).join(ve),s=a.length;for(let e=0;ee.slice(1,-1).indexOf(`:`)===-1?void 0:(()=>{let t=e.slice(1,-1),n=t.indexOf(`:`),r=t.slice(0,n);return r?be+r:void 0})(),we=e=>{let{theme:t,classGroups:n}=e;return Te(n,t)},Te=(e,t)=>{let n=_e();for(let r in e){let i=e[r];Ee(i,n,r,t)}return n},Ee=(e,t,n,r)=>{let i=e.length;for(let a=0;a{if(typeof e==`string`){Oe(e,t,n);return}if(typeof e==`function`){ke(e,t,n,r);return}Ae(e,t,n,r)},Oe=(e,t,n)=>{let r=e===``?t:je(t,e);r.classGroupId=n},ke=(e,t,n,r)=>{if(Me(e)){Ee(e(r),t,n,r);return}t.validators===null&&(t.validators=[]),t.validators.push(ge(n,e))},Ae=(e,t,n,r)=>{let i=Object.entries(e),a=i.length;for(let e=0;e{let n=e,r=t.split(ve),i=r.length;for(let e=0;e`isThemeGetter`in e&&e.isThemeGetter===!0,Ne=e=>{if(e<1)return{get:()=>void 0,set:()=>{}};let t=0,n=Object.create(null),r=Object.create(null),i=(i,a)=>{n[i]=a,t++,t>e&&(t=0,r=n,n=Object.create(null))};return{get(e){let t=n[e];if(t!==void 0)return t;if((t=r[e])!==void 0)return i(e,t),t},set(e,t){e in n?n[e]=t:i(e,t)}}},Pe=`!`,Fe=`:`,Ie=[],Le=(e,t,n,r,i)=>({modifiers:e,hasImportantModifier:t,baseClassName:n,maybePostfixModifierPosition:r,isExternal:i}),Re=e=>{let{prefix:t,experimentalParseClassName:n}=e,r=e=>{let t=[],n=0,r=0,i=0,a,o=e.length;for(let s=0;si?a-i:void 0;return Le(t,l,c,u)};if(t){let e=t+Fe,n=r;r=t=>t.startsWith(e)?n(t.slice(e.length)):Le(Ie,!1,t,void 0,!0)}if(n){let e=r;r=t=>n({className:t,parseClassName:e})}return r},ze=e=>{let t=new Map;return e.orderSensitiveModifiers.forEach((e,n)=>{t.set(e,1e6+n)}),e=>{let n=[],r=[];for(let i=0;i0&&(r.sort(),n.push(...r),r=[]),n.push(a)):r.push(a)}return r.length>0&&(r.sort(),n.push(...r)),n}},Be=e=>({cache:Ne(e.cacheSize),parseClassName:Re(e),sortModifiers:ze(e),postfixLookupClassGroupIds:Ve(e),...xe(e)}),Ve=e=>{let t=Object.create(null),n=e.postfixLookupClassGroups;if(n)for(let e=0;e{let{parseClassName:n,getClassGroupId:r,getConflictingClassGroupIds:i,sortModifiers:a,postfixLookupClassGroupIds:o}=t,s=[],c=e.trim().split(He),l=``;for(let e=c.length-1;e>=0;--e){let t=c[e],{isExternal:u,modifiers:d,hasImportantModifier:f,baseClassName:p,maybePostfixModifierPosition:m}=n(t);if(u){l=t+(l.length>0?` `+l:l);continue}let h=!!m,g;if(h){g=r(p.substring(0,m));let e=g&&o[g]?r(p):void 0;e&&e!==g&&(g=e,h=!1)}else g=r(p);if(!g){if(!h){l=t+(l.length>0?` `+l:l);continue}if(g=r(p),!g){l=t+(l.length>0?` `+l:l);continue}h=!1}let _=d.length===0?``:d.length===1?d[0]:a(d).join(`:`),v=f?_+Pe:_,y=v+g;if(s.indexOf(y)>-1)continue;s.push(y);let b=i(g,h);for(let e=0;e0?` `+l:l)}return l},We=(...e)=>{let t=0,n,r,i=``;for(;t{if(typeof e==`string`)return e;let t,n=``;for(let r=0;r{let n,r,i,a,o=o=>(n=Be(t.reduce((e,t)=>t(e),e())),r=n.cache.get,i=n.cache.set,a=s,s(o)),s=e=>{let t=r(e);if(t)return t;let a=Ue(e,n);return i(e,a),a};return a=o,(...e)=>a(We(...e))},qe=[],Je=e=>{let t=t=>t[e]||qe;return t.isThemeGetter=!0,t},Ye=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,Xe=/^\((?:(\w[\w-]*):)?(.+)\)$/i,Ze=/^\d+(?:\.\d+)?\/\d+(?:\.\d+)?$/,Qe=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,$e=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,et=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,tt=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,nt=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,rt=e=>Ze.test(e),A=e=>!!e&&!Number.isNaN(Number(e)),it=e=>!!e&&Number.isInteger(Number(e)),at=e=>e.endsWith(`%`)&&A(e.slice(0,-1)),ot=e=>Qe.test(e),st=()=>!0,ct=e=>$e.test(e)&&!et.test(e),lt=()=>!1,ut=e=>tt.test(e),dt=e=>nt.test(e),ft=e=>!j(e)&&!M(e),pt=e=>e.startsWith(`@container`)&&(e[10]===`/`&&e[11]!==void 0||e[11]===`s`&&e[16]!==void 0&&e.startsWith(`-size/`,10)||e[11]===`n`&&e[18]!==void 0&&e.startsWith(`-normal/`,10)),mt=e=>kt(e,P,lt),j=e=>Ye.test(e),ht=e=>kt(e,F,ct),gt=e=>kt(e,Mt,A),_t=e=>kt(e,Pt,st),vt=e=>kt(e,Nt,lt),yt=e=>kt(e,jt,lt),bt=e=>kt(e,N,dt),xt=e=>kt(e,Ft,ut),M=e=>Xe.test(e),St=e=>At(e,F),Ct=e=>At(e,Nt),wt=e=>At(e,jt),Tt=e=>At(e,P),Et=e=>At(e,N),Dt=e=>At(e,Ft,!0),Ot=e=>At(e,Pt,!0),kt=(e,t,n)=>{let r=Ye.exec(e);return r?r[1]?t(r[1]):n(r[2]):!1},At=(e,t,n=!1)=>{let r=Xe.exec(e);return r?r[1]?t(r[1]):n:!1},jt=e=>e===`position`||e===`percentage`,N=e=>e===`image`||e===`url`,P=e=>e===`length`||e===`size`||e===`bg-size`,F=e=>e===`length`,Mt=e=>e===`number`,Nt=e=>e===`family-name`,Pt=e=>e===`number`||e===`weight`,Ft=e=>e===`shadow`,It=Ke(()=>{let e=Je(`color`),t=Je(`font`),n=Je(`text`),r=Je(`font-weight`),i=Je(`tracking`),a=Je(`leading`),o=Je(`breakpoint`),s=Je(`container`),c=Je(`spacing`),l=Je(`radius`),u=Je(`shadow`),d=Je(`inset-shadow`),f=Je(`text-shadow`),p=Je(`drop-shadow`),m=Je(`blur`),h=Je(`perspective`),g=Je(`aspect`),_=Je(`ease`),v=Je(`animate`),y=()=>[`auto`,`avoid`,`all`,`avoid-page`,`page`,`left`,`right`,`column`],b=()=>[`center`,`top`,`bottom`,`left`,`right`,`top-left`,`left-top`,`top-right`,`right-top`,`bottom-right`,`right-bottom`,`bottom-left`,`left-bottom`],x=()=>[...b(),M,j],ee=()=>[`auto`,`hidden`,`clip`,`visible`,`scroll`],te=()=>[`auto`,`contain`,`none`],S=()=>[M,j,c],C=()=>[rt,`full`,`auto`,...S()],ne=()=>[it,`none`,`subgrid`,M,j],re=()=>[`auto`,{span:[`full`,it,M,j]},it,M,j],ie=()=>[it,`auto`,M,j],ae=()=>[`auto`,`min`,`max`,`fr`,M,j],oe=()=>[`start`,`end`,`center`,`between`,`around`,`evenly`,`stretch`,`baseline`,`center-safe`,`end-safe`],se=()=>[`start`,`end`,`center`,`stretch`,`center-safe`,`end-safe`],ce=()=>[`auto`,...S()],le=()=>[rt,`auto`,`full`,`dvw`,`dvh`,`lvw`,`lvh`,`svw`,`svh`,`min`,`max`,`fit`,...S()],w=()=>[rt,`screen`,`full`,`dvw`,`lvw`,`svw`,`min`,`max`,`fit`,...S()],ue=()=>[rt,`screen`,`full`,`lh`,`dvh`,`lvh`,`svh`,`min`,`max`,`fit`,...S()],T=()=>[e,M,j],de=()=>[...b(),wt,yt,{position:[M,j]}],fe=()=>[`no-repeat`,{repeat:[``,`x`,`y`,`space`,`round`]}],pe=()=>[`auto`,`cover`,`contain`,Tt,mt,{size:[M,j]}],me=()=>[at,St,ht],E=()=>[``,`none`,`full`,l,M,j],D=()=>[``,A,St,ht],O=()=>[`solid`,`dashed`,`dotted`,`double`],he=()=>[`normal`,`multiply`,`screen`,`overlay`,`darken`,`lighten`,`color-dodge`,`color-burn`,`hard-light`,`soft-light`,`difference`,`exclusion`,`hue`,`saturation`,`color`,`luminosity`],k=()=>[A,at,wt,yt],ge=()=>[``,`none`,m,M,j],_e=()=>[`none`,A,M,j],ve=()=>[`none`,A,M,j],ye=()=>[A,M,j],be=()=>[rt,`full`,...S()];return{cacheSize:500,theme:{animate:[`spin`,`ping`,`pulse`,`bounce`],aspect:[`video`],blur:[ot],breakpoint:[ot],color:[st],container:[ot],"drop-shadow":[ot],ease:[`in`,`out`,`in-out`],font:[ft],"font-weight":[`thin`,`extralight`,`light`,`normal`,`medium`,`semibold`,`bold`,`extrabold`,`black`],"inset-shadow":[ot],leading:[`none`,`tight`,`snug`,`normal`,`relaxed`,`loose`],perspective:[`dramatic`,`near`,`normal`,`midrange`,`distant`,`none`],radius:[ot],shadow:[ot],spacing:[`px`,A],text:[ot],"text-shadow":[ot],tracking:[`tighter`,`tight`,`normal`,`wide`,`wider`,`widest`]},classGroups:{aspect:[{aspect:[`auto`,`square`,rt,j,M,g]}],container:[`container`],"container-type":[{"@container":[``,`normal`,`size`,M,j]}],"container-named":[pt],columns:[{columns:[A,j,M,s]}],"break-after":[{"break-after":y()}],"break-before":[{"break-before":y()}],"break-inside":[{"break-inside":[`auto`,`avoid`,`avoid-page`,`avoid-column`]}],"box-decoration":[{"box-decoration":[`slice`,`clone`]}],box:[{box:[`border`,`content`]}],display:[`block`,`inline-block`,`inline`,`flex`,`inline-flex`,`table`,`inline-table`,`table-caption`,`table-cell`,`table-column`,`table-column-group`,`table-footer-group`,`table-header-group`,`table-row-group`,`table-row`,`flow-root`,`grid`,`inline-grid`,`contents`,`list-item`,`hidden`],sr:[`sr-only`,`not-sr-only`],float:[{float:[`right`,`left`,`none`,`start`,`end`]}],clear:[{clear:[`left`,`right`,`both`,`none`,`start`,`end`]}],isolation:[`isolate`,`isolation-auto`],"object-fit":[{object:[`contain`,`cover`,`fill`,`none`,`scale-down`]}],"object-position":[{object:x()}],overflow:[{overflow:ee()}],"overflow-x":[{"overflow-x":ee()}],"overflow-y":[{"overflow-y":ee()}],overscroll:[{overscroll:te()}],"overscroll-x":[{"overscroll-x":te()}],"overscroll-y":[{"overscroll-y":te()}],position:[`static`,`fixed`,`absolute`,`relative`,`sticky`],inset:[{inset:C()}],"inset-x":[{"inset-x":C()}],"inset-y":[{"inset-y":C()}],start:[{"inset-s":C(),start:C()}],end:[{"inset-e":C(),end:C()}],"inset-bs":[{"inset-bs":C()}],"inset-be":[{"inset-be":C()}],top:[{top:C()}],right:[{right:C()}],bottom:[{bottom:C()}],left:[{left:C()}],visibility:[`visible`,`invisible`,`collapse`],z:[{z:[it,`auto`,M,j]}],basis:[{basis:[rt,`full`,`auto`,s,...S()]}],"flex-direction":[{flex:[`row`,`row-reverse`,`col`,`col-reverse`]}],"flex-wrap":[{flex:[`nowrap`,`wrap`,`wrap-reverse`]}],flex:[{flex:[A,rt,`auto`,`initial`,`none`,j]}],grow:[{grow:[``,A,M,j]}],shrink:[{shrink:[``,A,M,j]}],order:[{order:[it,`first`,`last`,`none`,M,j]}],"grid-cols":[{"grid-cols":ne()}],"col-start-end":[{col:re()}],"col-start":[{"col-start":ie()}],"col-end":[{"col-end":ie()}],"grid-rows":[{"grid-rows":ne()}],"row-start-end":[{row:re()}],"row-start":[{"row-start":ie()}],"row-end":[{"row-end":ie()}],"grid-flow":[{"grid-flow":[`row`,`col`,`dense`,`row-dense`,`col-dense`]}],"auto-cols":[{"auto-cols":ae()}],"auto-rows":[{"auto-rows":ae()}],gap:[{gap:S()}],"gap-x":[{"gap-x":S()}],"gap-y":[{"gap-y":S()}],"justify-content":[{justify:[...oe(),`normal`]}],"justify-items":[{"justify-items":[...se(),`normal`]}],"justify-self":[{"justify-self":[`auto`,...se()]}],"align-content":[{content:[`normal`,...oe()]}],"align-items":[{items:[...se(),{baseline:[``,`last`]}]}],"align-self":[{self:[`auto`,...se(),{baseline:[``,`last`]}]}],"place-content":[{"place-content":oe()}],"place-items":[{"place-items":[...se(),`baseline`]}],"place-self":[{"place-self":[`auto`,...se()]}],p:[{p:S()}],px:[{px:S()}],py:[{py:S()}],ps:[{ps:S()}],pe:[{pe:S()}],pbs:[{pbs:S()}],pbe:[{pbe:S()}],pt:[{pt:S()}],pr:[{pr:S()}],pb:[{pb:S()}],pl:[{pl:S()}],m:[{m:ce()}],mx:[{mx:ce()}],my:[{my:ce()}],ms:[{ms:ce()}],me:[{me:ce()}],mbs:[{mbs:ce()}],mbe:[{mbe:ce()}],mt:[{mt:ce()}],mr:[{mr:ce()}],mb:[{mb:ce()}],ml:[{ml:ce()}],"space-x":[{"space-x":S()}],"space-x-reverse":[`space-x-reverse`],"space-y":[{"space-y":S()}],"space-y-reverse":[`space-y-reverse`],size:[{size:le()}],"inline-size":[{inline:[`auto`,...w()]}],"min-inline-size":[{"min-inline":[`auto`,...w()]}],"max-inline-size":[{"max-inline":[`none`,...w()]}],"block-size":[{block:[`auto`,...ue()]}],"min-block-size":[{"min-block":[`auto`,...ue()]}],"max-block-size":[{"max-block":[`none`,...ue()]}],w:[{w:[s,`screen`,...le()]}],"min-w":[{"min-w":[s,`screen`,`none`,...le()]}],"max-w":[{"max-w":[s,`screen`,`none`,`prose`,{screen:[o]},...le()]}],h:[{h:[`screen`,`lh`,...le()]}],"min-h":[{"min-h":[`screen`,`lh`,`none`,...le()]}],"max-h":[{"max-h":[`screen`,`lh`,...le()]}],"font-size":[{text:[`base`,n,St,ht]}],"font-smoothing":[`antialiased`,`subpixel-antialiased`],"font-style":[`italic`,`not-italic`],"font-weight":[{font:[r,Ot,_t]}],"font-stretch":[{"font-stretch":[`ultra-condensed`,`extra-condensed`,`condensed`,`semi-condensed`,`normal`,`semi-expanded`,`expanded`,`extra-expanded`,`ultra-expanded`,at,j]}],"font-family":[{font:[Ct,vt,t]}],"font-features":[{"font-features":[j]}],"fvn-normal":[`normal-nums`],"fvn-ordinal":[`ordinal`],"fvn-slashed-zero":[`slashed-zero`],"fvn-figure":[`lining-nums`,`oldstyle-nums`],"fvn-spacing":[`proportional-nums`,`tabular-nums`],"fvn-fraction":[`diagonal-fractions`,`stacked-fractions`],tracking:[{tracking:[i,M,j]}],"line-clamp":[{"line-clamp":[A,`none`,M,gt]}],leading:[{leading:[a,...S()]}],"list-image":[{"list-image":[`none`,M,j]}],"list-style-position":[{list:[`inside`,`outside`]}],"list-style-type":[{list:[`disc`,`decimal`,`none`,M,j]}],"text-alignment":[{text:[`left`,`center`,`right`,`justify`,`start`,`end`]}],"placeholder-color":[{placeholder:T()}],"text-color":[{text:T()}],"text-decoration":[`underline`,`overline`,`line-through`,`no-underline`],"text-decoration-style":[{decoration:[...O(),`wavy`]}],"text-decoration-thickness":[{decoration:[A,`from-font`,`auto`,M,ht]}],"text-decoration-color":[{decoration:T()}],"underline-offset":[{"underline-offset":[A,`auto`,M,j]}],"text-transform":[`uppercase`,`lowercase`,`capitalize`,`normal-case`],"text-overflow":[`truncate`,`text-ellipsis`,`text-clip`],"text-wrap":[{text:[`wrap`,`nowrap`,`balance`,`pretty`]}],indent:[{indent:S()}],"tab-size":[{tab:[it,M,j]}],"vertical-align":[{align:[`baseline`,`top`,`middle`,`bottom`,`text-top`,`text-bottom`,`sub`,`super`,M,j]}],whitespace:[{whitespace:[`normal`,`nowrap`,`pre`,`pre-line`,`pre-wrap`,`break-spaces`]}],break:[{break:[`normal`,`words`,`all`,`keep`]}],wrap:[{wrap:[`break-word`,`anywhere`,`normal`]}],hyphens:[{hyphens:[`none`,`manual`,`auto`]}],content:[{content:[`none`,M,j]}],"bg-attachment":[{bg:[`fixed`,`local`,`scroll`]}],"bg-clip":[{"bg-clip":[`border`,`padding`,`content`,`text`]}],"bg-origin":[{"bg-origin":[`border`,`padding`,`content`]}],"bg-position":[{bg:de()}],"bg-repeat":[{bg:fe()}],"bg-size":[{bg:pe()}],"bg-image":[{bg:[`none`,{linear:[{to:[`t`,`tr`,`r`,`br`,`b`,`bl`,`l`,`tl`]},it,M,j],radial:[``,M,j],conic:[it,M,j]},Et,bt]}],"bg-color":[{bg:T()}],"gradient-from-pos":[{from:me()}],"gradient-via-pos":[{via:me()}],"gradient-to-pos":[{to:me()}],"gradient-from":[{from:T()}],"gradient-via":[{via:T()}],"gradient-to":[{to:T()}],rounded:[{rounded:E()}],"rounded-s":[{"rounded-s":E()}],"rounded-e":[{"rounded-e":E()}],"rounded-t":[{"rounded-t":E()}],"rounded-r":[{"rounded-r":E()}],"rounded-b":[{"rounded-b":E()}],"rounded-l":[{"rounded-l":E()}],"rounded-ss":[{"rounded-ss":E()}],"rounded-se":[{"rounded-se":E()}],"rounded-ee":[{"rounded-ee":E()}],"rounded-es":[{"rounded-es":E()}],"rounded-tl":[{"rounded-tl":E()}],"rounded-tr":[{"rounded-tr":E()}],"rounded-br":[{"rounded-br":E()}],"rounded-bl":[{"rounded-bl":E()}],"border-w":[{border:D()}],"border-w-x":[{"border-x":D()}],"border-w-y":[{"border-y":D()}],"border-w-s":[{"border-s":D()}],"border-w-e":[{"border-e":D()}],"border-w-bs":[{"border-bs":D()}],"border-w-be":[{"border-be":D()}],"border-w-t":[{"border-t":D()}],"border-w-r":[{"border-r":D()}],"border-w-b":[{"border-b":D()}],"border-w-l":[{"border-l":D()}],"divide-x":[{"divide-x":D()}],"divide-x-reverse":[`divide-x-reverse`],"divide-y":[{"divide-y":D()}],"divide-y-reverse":[`divide-y-reverse`],"border-style":[{border:[...O(),`hidden`,`none`]}],"divide-style":[{divide:[...O(),`hidden`,`none`]}],"border-color":[{border:T()}],"border-color-x":[{"border-x":T()}],"border-color-y":[{"border-y":T()}],"border-color-s":[{"border-s":T()}],"border-color-e":[{"border-e":T()}],"border-color-bs":[{"border-bs":T()}],"border-color-be":[{"border-be":T()}],"border-color-t":[{"border-t":T()}],"border-color-r":[{"border-r":T()}],"border-color-b":[{"border-b":T()}],"border-color-l":[{"border-l":T()}],"divide-color":[{divide:T()}],"outline-style":[{outline:[...O(),`none`,`hidden`]}],"outline-offset":[{"outline-offset":[A,M,j]}],"outline-w":[{outline:[``,A,St,ht]}],"outline-color":[{outline:T()}],shadow:[{shadow:[``,`none`,u,Dt,xt]}],"shadow-color":[{shadow:T()}],"inset-shadow":[{"inset-shadow":[`none`,d,Dt,xt]}],"inset-shadow-color":[{"inset-shadow":T()}],"ring-w":[{ring:D()}],"ring-w-inset":[`ring-inset`],"ring-color":[{ring:T()}],"ring-offset-w":[{"ring-offset":[A,ht]}],"ring-offset-color":[{"ring-offset":T()}],"inset-ring-w":[{"inset-ring":D()}],"inset-ring-color":[{"inset-ring":T()}],"text-shadow":[{"text-shadow":[`none`,f,Dt,xt]}],"text-shadow-color":[{"text-shadow":T()}],opacity:[{opacity:[A,M,j]}],"mix-blend":[{"mix-blend":[...he(),`plus-darker`,`plus-lighter`]}],"bg-blend":[{"bg-blend":he()}],"mask-clip":[{"mask-clip":[`border`,`padding`,`content`,`fill`,`stroke`,`view`]},`mask-no-clip`],"mask-composite":[{mask:[`add`,`subtract`,`intersect`,`exclude`]}],"mask-image-linear-pos":[{"mask-linear":[A]}],"mask-image-linear-from-pos":[{"mask-linear-from":k()}],"mask-image-linear-to-pos":[{"mask-linear-to":k()}],"mask-image-linear-from-color":[{"mask-linear-from":T()}],"mask-image-linear-to-color":[{"mask-linear-to":T()}],"mask-image-t-from-pos":[{"mask-t-from":k()}],"mask-image-t-to-pos":[{"mask-t-to":k()}],"mask-image-t-from-color":[{"mask-t-from":T()}],"mask-image-t-to-color":[{"mask-t-to":T()}],"mask-image-r-from-pos":[{"mask-r-from":k()}],"mask-image-r-to-pos":[{"mask-r-to":k()}],"mask-image-r-from-color":[{"mask-r-from":T()}],"mask-image-r-to-color":[{"mask-r-to":T()}],"mask-image-b-from-pos":[{"mask-b-from":k()}],"mask-image-b-to-pos":[{"mask-b-to":k()}],"mask-image-b-from-color":[{"mask-b-from":T()}],"mask-image-b-to-color":[{"mask-b-to":T()}],"mask-image-l-from-pos":[{"mask-l-from":k()}],"mask-image-l-to-pos":[{"mask-l-to":k()}],"mask-image-l-from-color":[{"mask-l-from":T()}],"mask-image-l-to-color":[{"mask-l-to":T()}],"mask-image-x-from-pos":[{"mask-x-from":k()}],"mask-image-x-to-pos":[{"mask-x-to":k()}],"mask-image-x-from-color":[{"mask-x-from":T()}],"mask-image-x-to-color":[{"mask-x-to":T()}],"mask-image-y-from-pos":[{"mask-y-from":k()}],"mask-image-y-to-pos":[{"mask-y-to":k()}],"mask-image-y-from-color":[{"mask-y-from":T()}],"mask-image-y-to-color":[{"mask-y-to":T()}],"mask-image-radial":[{"mask-radial":[M,j]}],"mask-image-radial-from-pos":[{"mask-radial-from":k()}],"mask-image-radial-to-pos":[{"mask-radial-to":k()}],"mask-image-radial-from-color":[{"mask-radial-from":T()}],"mask-image-radial-to-color":[{"mask-radial-to":T()}],"mask-image-radial-shape":[{"mask-radial":[`circle`,`ellipse`]}],"mask-image-radial-size":[{"mask-radial":[{closest:[`side`,`corner`],farthest:[`side`,`corner`]}]}],"mask-image-radial-pos":[{"mask-radial-at":b()}],"mask-image-conic-pos":[{"mask-conic":[A]}],"mask-image-conic-from-pos":[{"mask-conic-from":k()}],"mask-image-conic-to-pos":[{"mask-conic-to":k()}],"mask-image-conic-from-color":[{"mask-conic-from":T()}],"mask-image-conic-to-color":[{"mask-conic-to":T()}],"mask-mode":[{mask:[`alpha`,`luminance`,`match`]}],"mask-origin":[{"mask-origin":[`border`,`padding`,`content`,`fill`,`stroke`,`view`]}],"mask-position":[{mask:de()}],"mask-repeat":[{mask:fe()}],"mask-size":[{mask:pe()}],"mask-type":[{"mask-type":[`alpha`,`luminance`]}],"mask-image":[{mask:[`none`,M,j]}],filter:[{filter:[``,`none`,M,j]}],blur:[{blur:ge()}],brightness:[{brightness:[A,M,j]}],contrast:[{contrast:[A,M,j]}],"drop-shadow":[{"drop-shadow":[``,`none`,p,Dt,xt]}],"drop-shadow-color":[{"drop-shadow":T()}],grayscale:[{grayscale:[``,A,M,j]}],"hue-rotate":[{"hue-rotate":[A,M,j]}],invert:[{invert:[``,A,M,j]}],saturate:[{saturate:[A,M,j]}],sepia:[{sepia:[``,A,M,j]}],"backdrop-filter":[{"backdrop-filter":[``,`none`,M,j]}],"backdrop-blur":[{"backdrop-blur":ge()}],"backdrop-brightness":[{"backdrop-brightness":[A,M,j]}],"backdrop-contrast":[{"backdrop-contrast":[A,M,j]}],"backdrop-grayscale":[{"backdrop-grayscale":[``,A,M,j]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[A,M,j]}],"backdrop-invert":[{"backdrop-invert":[``,A,M,j]}],"backdrop-opacity":[{"backdrop-opacity":[A,M,j]}],"backdrop-saturate":[{"backdrop-saturate":[A,M,j]}],"backdrop-sepia":[{"backdrop-sepia":[``,A,M,j]}],"border-collapse":[{border:[`collapse`,`separate`]}],"border-spacing":[{"border-spacing":S()}],"border-spacing-x":[{"border-spacing-x":S()}],"border-spacing-y":[{"border-spacing-y":S()}],"table-layout":[{table:[`auto`,`fixed`]}],caption:[{caption:[`top`,`bottom`]}],transition:[{transition:[``,`all`,`colors`,`opacity`,`shadow`,`transform`,`none`,M,j]}],"transition-behavior":[{transition:[`normal`,`discrete`]}],duration:[{duration:[A,`initial`,M,j]}],ease:[{ease:[`linear`,`initial`,_,M,j]}],delay:[{delay:[A,M,j]}],animate:[{animate:[`none`,v,M,j]}],backface:[{backface:[`hidden`,`visible`]}],perspective:[{perspective:[h,M,j]}],"perspective-origin":[{"perspective-origin":x()}],rotate:[{rotate:_e()}],"rotate-x":[{"rotate-x":_e()}],"rotate-y":[{"rotate-y":_e()}],"rotate-z":[{"rotate-z":_e()}],scale:[{scale:ve()}],"scale-x":[{"scale-x":ve()}],"scale-y":[{"scale-y":ve()}],"scale-z":[{"scale-z":ve()}],"scale-3d":[`scale-3d`],skew:[{skew:ye()}],"skew-x":[{"skew-x":ye()}],"skew-y":[{"skew-y":ye()}],transform:[{transform:[M,j,``,`none`,`gpu`,`cpu`]}],"transform-origin":[{origin:x()}],"transform-style":[{transform:[`3d`,`flat`]}],translate:[{translate:be()}],"translate-x":[{"translate-x":be()}],"translate-y":[{"translate-y":be()}],"translate-z":[{"translate-z":be()}],"translate-none":[`translate-none`],zoom:[{zoom:[it,M,j]}],accent:[{accent:T()}],appearance:[{appearance:[`none`,`auto`]}],"caret-color":[{caret:T()}],"color-scheme":[{scheme:[`normal`,`dark`,`light`,`light-dark`,`only-dark`,`only-light`]}],cursor:[{cursor:[`auto`,`default`,`pointer`,`wait`,`text`,`move`,`help`,`not-allowed`,`none`,`context-menu`,`progress`,`cell`,`crosshair`,`vertical-text`,`alias`,`copy`,`no-drop`,`grab`,`grabbing`,`all-scroll`,`col-resize`,`row-resize`,`n-resize`,`e-resize`,`s-resize`,`w-resize`,`ne-resize`,`nw-resize`,`se-resize`,`sw-resize`,`ew-resize`,`ns-resize`,`nesw-resize`,`nwse-resize`,`zoom-in`,`zoom-out`,M,j]}],"field-sizing":[{"field-sizing":[`fixed`,`content`]}],"pointer-events":[{"pointer-events":[`auto`,`none`]}],resize:[{resize:[`none`,``,`y`,`x`]}],"scroll-behavior":[{scroll:[`auto`,`smooth`]}],"scrollbar-thumb-color":[{"scrollbar-thumb":T()}],"scrollbar-track-color":[{"scrollbar-track":T()}],"scrollbar-gutter":[{"scrollbar-gutter":[`auto`,`stable`,`both`]}],"scrollbar-w":[{scrollbar:[`auto`,`thin`,`none`]}],"scroll-m":[{"scroll-m":S()}],"scroll-mx":[{"scroll-mx":S()}],"scroll-my":[{"scroll-my":S()}],"scroll-ms":[{"scroll-ms":S()}],"scroll-me":[{"scroll-me":S()}],"scroll-mbs":[{"scroll-mbs":S()}],"scroll-mbe":[{"scroll-mbe":S()}],"scroll-mt":[{"scroll-mt":S()}],"scroll-mr":[{"scroll-mr":S()}],"scroll-mb":[{"scroll-mb":S()}],"scroll-ml":[{"scroll-ml":S()}],"scroll-p":[{"scroll-p":S()}],"scroll-px":[{"scroll-px":S()}],"scroll-py":[{"scroll-py":S()}],"scroll-ps":[{"scroll-ps":S()}],"scroll-pe":[{"scroll-pe":S()}],"scroll-pbs":[{"scroll-pbs":S()}],"scroll-pbe":[{"scroll-pbe":S()}],"scroll-pt":[{"scroll-pt":S()}],"scroll-pr":[{"scroll-pr":S()}],"scroll-pb":[{"scroll-pb":S()}],"scroll-pl":[{"scroll-pl":S()}],"snap-align":[{snap:[`start`,`end`,`center`,`align-none`]}],"snap-stop":[{snap:[`normal`,`always`]}],"snap-type":[{snap:[`none`,`x`,`y`,`both`]}],"snap-strictness":[{snap:[`mandatory`,`proximity`]}],touch:[{touch:[`auto`,`none`,`manipulation`]}],"touch-x":[{"touch-pan":[`x`,`left`,`right`]}],"touch-y":[{"touch-pan":[`y`,`up`,`down`]}],"touch-pz":[`touch-pinch-zoom`],select:[{select:[`none`,`text`,`all`,`auto`]}],"will-change":[{"will-change":[`auto`,`scroll`,`contents`,`transform`,M,j]}],fill:[{fill:[`none`,...T()]}],"stroke-w":[{stroke:[A,St,ht,gt]}],stroke:[{stroke:[`none`,...T()]}],"forced-color-adjust":[{"forced-color-adjust":[`auto`,`none`]}]},conflictingClassGroups:{"container-named":[`container-type`],overflow:[`overflow-x`,`overflow-y`],overscroll:[`overscroll-x`,`overscroll-y`],inset:[`inset-x`,`inset-y`,`inset-bs`,`inset-be`,`start`,`end`,`top`,`right`,`bottom`,`left`],"inset-x":[`right`,`left`],"inset-y":[`top`,`bottom`],flex:[`basis`,`grow`,`shrink`],gap:[`gap-x`,`gap-y`],p:[`px`,`py`,`ps`,`pe`,`pbs`,`pbe`,`pt`,`pr`,`pb`,`pl`],px:[`pr`,`pl`],py:[`pt`,`pb`],m:[`mx`,`my`,`ms`,`me`,`mbs`,`mbe`,`mt`,`mr`,`mb`,`ml`],mx:[`mr`,`ml`],my:[`mt`,`mb`],size:[`w`,`h`],"font-size":[`leading`],"fvn-normal":[`fvn-ordinal`,`fvn-slashed-zero`,`fvn-figure`,`fvn-spacing`,`fvn-fraction`],"fvn-ordinal":[`fvn-normal`],"fvn-slashed-zero":[`fvn-normal`],"fvn-figure":[`fvn-normal`],"fvn-spacing":[`fvn-normal`],"fvn-fraction":[`fvn-normal`],"line-clamp":[`display`,`overflow`],rounded:[`rounded-s`,`rounded-e`,`rounded-t`,`rounded-r`,`rounded-b`,`rounded-l`,`rounded-ss`,`rounded-se`,`rounded-ee`,`rounded-es`,`rounded-tl`,`rounded-tr`,`rounded-br`,`rounded-bl`],"rounded-s":[`rounded-ss`,`rounded-es`],"rounded-e":[`rounded-se`,`rounded-ee`],"rounded-t":[`rounded-tl`,`rounded-tr`],"rounded-r":[`rounded-tr`,`rounded-br`],"rounded-b":[`rounded-br`,`rounded-bl`],"rounded-l":[`rounded-tl`,`rounded-bl`],"border-spacing":[`border-spacing-x`,`border-spacing-y`],"border-w":[`border-w-x`,`border-w-y`,`border-w-s`,`border-w-e`,`border-w-bs`,`border-w-be`,`border-w-t`,`border-w-r`,`border-w-b`,`border-w-l`],"border-w-x":[`border-w-r`,`border-w-l`],"border-w-y":[`border-w-t`,`border-w-b`],"border-color":[`border-color-x`,`border-color-y`,`border-color-s`,`border-color-e`,`border-color-bs`,`border-color-be`,`border-color-t`,`border-color-r`,`border-color-b`,`border-color-l`],"border-color-x":[`border-color-r`,`border-color-l`],"border-color-y":[`border-color-t`,`border-color-b`],translate:[`translate-x`,`translate-y`,`translate-none`],"translate-none":[`translate`,`translate-x`,`translate-y`,`translate-z`],"scroll-m":[`scroll-mx`,`scroll-my`,`scroll-ms`,`scroll-me`,`scroll-mbs`,`scroll-mbe`,`scroll-mt`,`scroll-mr`,`scroll-mb`,`scroll-ml`],"scroll-mx":[`scroll-mr`,`scroll-ml`],"scroll-my":[`scroll-mt`,`scroll-mb`],"scroll-p":[`scroll-px`,`scroll-py`,`scroll-ps`,`scroll-pe`,`scroll-pbs`,`scroll-pbe`,`scroll-pt`,`scroll-pr`,`scroll-pb`,`scroll-pl`],"scroll-px":[`scroll-pr`,`scroll-pl`],"scroll-py":[`scroll-pt`,`scroll-pb`],touch:[`touch-x`,`touch-y`,`touch-pz`],"touch-x":[`touch`],"touch-y":[`touch`],"touch-pz":[`touch`]},conflictingClassGroupModifiers:{"font-size":[`leading`]},postfixLookupClassGroups:[`container-type`],orderSensitiveModifiers:[`*`,`**`,`after`,`backdrop`,`before`,`details-content`,`file`,`first-letter`,`first-line`,`marker`,`placeholder`,`selection`]}});function I(...e){return It(E(e))}var Lt=e((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),L=e(((e,t)=>{t.exports=Lt()}))(),Rt=he(`inline-flex min-h-[22px] items-center rounded-full border px-2 py-0.5 text-[11px] font-extrabold uppercase leading-tight`,{variants:{variant:{neutral:`border-border bg-secondary text-muted-foreground`,succeeded:`border-emerald-400/35 bg-emerald-500/15 text-emerald-300`,failed:`border-red-400/40 bg-red-500/15 text-red-300`,dead:`border-red-400/40 bg-red-500/15 text-red-300`,missing:`border-red-400/40 bg-red-500/15 text-red-300`,running:`border-amber-400/40 bg-amber-500/15 text-amber-300`,queued:`border-teal-400/40 bg-teal-500/15 text-teal-200`,canceled:`border-border bg-secondary text-muted-foreground`}},defaultVariants:{variant:`neutral`}});function R({className:e,variant:t,...n}){return(0,L.jsx)(`span`,{"data-slot":`badge`,className:I(Rt({variant:t,className:e})),...n})}var zt=he(`inline-flex min-h-9 shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md border text-sm font-semibold transition-colors focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0`,{variants:{variant:{default:`border-primary bg-primary text-primary-foreground hover:bg-primary/90`,secondary:`border-border bg-secondary text-secondary-foreground hover:bg-secondary/80`,outline:`border-border bg-background hover:bg-accent hover:text-accent-foreground`,ghost:`border-transparent hover:bg-accent hover:text-accent-foreground`,destructive:`border-destructive bg-destructive text-white hover:bg-destructive/90`},size:{default:`h-9 px-4 py-2`,sm:`h-8 rounded-md px-3 text-xs`,icon:`size-9`}},defaultVariants:{variant:`secondary`,size:`default`}});function z({className:e,variant:t,size:n,type:r=`button`,...i}){return(0,L.jsx)(`button`,{"data-slot":`button`,type:r,className:I(zt({variant:t,size:n,className:e})),...i})}function B({className:e,...t}){return(0,L.jsx)(`div`,{"data-slot":`card`,className:I(`rounded-lg border bg-card text-card-foreground shadow-[0_18px_44px_rgb(0_0_0/0.22)]`,e),...t})}function V({className:e,...t}){return(0,L.jsx)(`div`,{"data-slot":`card-header`,className:I(`flex items-center justify-between gap-3 border-b px-4 py-3`,e),...t})}function Bt({className:e,...t}){return(0,L.jsx)(`h2`,{"data-slot":`card-title`,className:I(`text-[15px] font-bold`,e),...t})}function Vt({className:e,...t}){return(0,L.jsx)(`div`,{"data-slot":`card-content`,className:I(`p-4`,e),...t})}function H({className:e,type:t,...n}){return(0,L.jsx)(`input`,{"data-slot":`input`,type:t,className:I(`flex min-h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground shadow-xs transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50`,e),...n})}function U({className:e,...t}){return(0,L.jsx)(`label`,{"data-slot":`label`,className:I(`grid gap-1.5 text-xs font-bold text-muted-foreground`,e),...t})}function Ht({className:e,...t}){return(0,L.jsx)(`select`,{"data-slot":`select`,className:I(`flex min-h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground shadow-xs transition-colors focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50`,e),...t})}function Ut({className:e,...t}){return(0,L.jsx)(`table`,{"data-slot":`table`,className:I(`w-full border-collapse text-sm`,e),...t})}function Wt({className:e,...t}){return(0,L.jsx)(`thead`,{"data-slot":`table-header`,className:e,...t})}function Gt({className:e,...t}){return(0,L.jsx)(`tbody`,{"data-slot":`table-body`,className:e,...t})}function Kt({className:e,...t}){return(0,L.jsx)(`tr`,{"data-slot":`table-row`,className:I(`border-b transition-colors hover:bg-muted/45`,e),...t})}function W({className:e,...t}){return(0,L.jsx)(`th`,{"data-slot":`table-head`,className:I(`bg-secondary px-3 py-3 text-left align-middle text-xs font-extrabold text-muted-foreground`,e),...t})}function G({className:e,...t}){return(0,L.jsx)(`td`,{"data-slot":`table-cell`,className:I(`px-3 py-3 align-middle text-sm`,e),...t})}var qt={pending:`Needs review`,selected:`Assigned to onboarder`,reachingout:`Reaching out`,awaitingcontribution:`Awaiting contribution`,onboarded:`Onboarded`,waitlist:`Waitlist`,rejected:`Rejected`};function Jt(e){if(!e)return``;let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString(void 0,{year:`numeric`,month:`short`,day:`numeric`,hour:`2-digit`,minute:`2-digit`})}function Yt(e,t=new Date){if(!e)return null;let n=new Date(e);if(Number.isNaN(n.getTime()))return null;let r=t.getTime()-n.getTime();return r<0?0:Math.floor(r/864e5)}function Xt(e){return e==null?``:JSON.stringify(e,null,2)}function Zt(e){return e.onboarding_state||e.onboardingState||e.cOnboardingState||``}function Qt(e){let t=String(e||``).trim();if(!t)return`No status`;let n=t.toLowerCase();return qt[n]?qt[n]:t.replace(/[-_]+/g,` `).replace(/\s+/g,` `).trim().replace(/\b\w/g,e=>e.toUpperCase())}function $t(e){let t=String(e||``).trim().toLowerCase();return!t||t===`pending`?`neutral`:t===`selected`?`queued`:t===`rejected`?`failed`:t===`onboarded`?`succeeded`:t===`waitlist`?`running`:`queued`}function en(e){let t=String(e||``).trim();return!t||t.toLowerCase()===`none`?``:t}function tn(e){let t=String(e||``).trim();return t?/^https?:\/\//i.test(t)?t:`https://${t.replace(/^\/+/,``)}`:``}function nn(e){try{return new URL(tn(e))}catch{return null}}function rn(e,t){let n=e.toLowerCase();return n===t||n.endsWith(`.${t}`)}function an(e){return e.split(`/`).filter(Boolean).map(e=>encodeURIComponent(e)).join(`/`)}function on(e){let t=String(e||``).trim();if(!t)return``;let n=nn(t);if(n&&rn(n.hostname,`linkedin.com`))return n.href;if(/^https?:\/\//i.test(t))return``;let r=t.replace(/^@/,``).replace(/^\/+|\/+$/g,``).replace(/^in\//i,``);return r?`https://www.linkedin.com/in/${an(r)}`:``}function sn(e){let t=String(e||``).trim().replace(/^@/,``);if(!t)return``;let n=nn(t);if(n&&rn(n.hostname,`github.com`))return n.href;if(/^https?:\/\//i.test(t))return``;let r=t.replace(/^\/+|\/+$/g,``);return r?`https://github.com/${an(r)}`:``}var cn=[{category:`CRM`,label:`CRM`,description:`EspoCRM connection settings used by the API, worker, and Discord bot.`},{category:`Projects`,label:`Projects`,description:`ERPNext credentials and project workflow settings.`},{category:`Onboarding`,label:`Onboarding`,description:`Editable onboarding integrations such as DocuSeal, Outline, and onboarding email SMTP.`},{category:`Newsletter`,label:`Newsletter`,description:`Brevo, Keila, and recurring 508 members audience sync settings.`},{category:`AI`,label:`AI Providers`,description:`Provider credentials, base URLs, and model defaults.`},{category:`Agent`,label:`Agent Runtime`,description:`Planner, fallback, and tiered model routing for agent workflows.`},{category:`Observability`,label:`Observability`,description:`Telemetry and request tracing integrations.`},{category:`Intake`,label:`Intake`,description:`Resume and mailbox intake limits and parser defaults.`},{category:`Operations`,label:`Operations`,description:`Queue, sync, GitHub, and notification behavior.`},{category:`Legacy`,label:`Legacy`,description:`Older integrations retained for compatibility.`}],ln=new Map(cn.map((e,t)=>[e.category,{...e,index:t}]));function un(e){return`configurationGroup-${e.replace(/[^a-zA-Z0-9_-]+/g,`-`)}`}function dn(e){return e.key.startsWith(`ONBOARDING_EMAIL_`)||e.is_secret||e.value_type===`url`||e.key.endsWith(`_MODEL`)||e.key.endsWith(`_API_USER`)||e.key.endsWith(`_BASE_URL`)}var fn={people:`/dashboard/people`,gigs:`/dashboard/gigs`,projects:`/dashboard/projects`,onboarding:`/dashboard/onboarding`,newsletter:`/dashboard/newsletter`,jobs:`/dashboard/jobs`,agent:`/dashboard/agent`,audit:`/dashboard/audit`,configuration:`/dashboard/configuration`},pn={people:`people:read`,gigs:`gigs:read`,projects:`projects:read`,onboarding:`onboarding:read`,newsletter:`people:sync`,jobs:`jobs:read`,agent:`audit:read`,audit:`audit:read`,configuration:`configuration:read`},mn={discord:{label:`Discord`,options:[[`linked`,`Linked`],[`missing`,`Missing`]]},email_508:{label:`508 email`,options:[[`present`,`Present`],[`missing`,`Missing`]]},resume:{label:`Resume`,options:[[`present`,`Present`],[`missing`,`Missing`]]},skills:{label:`Skills`,options:[[`present`,`Parsed`],[`missing`,`Not parsed`]]},sync_status:{label:`Sync status`,options:[[`active`,`Active`],[`conflict`,`Conflict`],[`missing_in_crm`,`Missing in CRM`]]}},hn=[[`pending`,`Needs review`],[`selected`,`Assigned to onboarder`],[`reachingout`,`Reaching out`],[`awaitingcontribution`,`Awaiting contribution`],[`onboarded`,`Onboarded`],[`waitlist`,`Waitlist`],[`rejected`,`Rejected`]],gn=hn.slice(0,4),_n=new Set([`onboarded`,`waitlist`,`rejected`]);function vn(e){return String(e||``).trim().toLowerCase().replace(/[-_\s]+/g,``)}var yn=class extends Error{status;statusText;payload;url;method;constructor(e,t,n,r,i,a){super(e),this.name=`ApiRequestError`,this.status=t,this.statusText=n,this.payload=r,this.url=i,this.method=a}};function bn(e,t){let n=e.detail;if(typeof n==`string`&&n.trim())return n;let r=e.error;return typeof r==`string`?r===`person_not_found`?`No CRM person, ERPNext user, or ERPNext supplier matched "${typeof e.person==`string`&&e.person.trim()?e.person:`that person`}". Try an email address or an exact name from CRM/ERPNext.`:r===`candidate_not_found`?`The selected person record is no longer available. Search again and choose one of the current matches.`:r===`invalid_crm_profile`?`Paste a valid CRM Contact profile URL or Contact id.`:r===`crm_profile_not_found`?`That CRM Contact profile was not found.`:r===`crm_profile_mismatch`?`CRM returned a different Contact than the profile requested. Check the profile URL and try again.`:r===`crm_profile_lookup_failed`?`CRM profile lookup failed. Try again after CRM is reachable.`:r===`ambiguous_person`?`Multiple people matched. Choose the matching person record.`:r||t:t}function xn(e,t){return typeof e==`string`&&e.trim()?e:e instanceof Error&&e.message.trim()?e.message:t}function Sn(e){if(!e)return null;let t=Object.entries(e.providers||{}).map(([e,t])=>`${e}: ${t.would_sync??t.synced??0} would sync, ${t.skipped||0} skipped, ${t.failed||0} failed`),n=e.mailboxes_scanned??0,r=e.contacts_considered??0;return[`${n} mailbox${n===1?``:`es`}`,`${r} contact${r===1?``:`s`}`,...t].join(`; `)}function Cn(e){let t=Number(e||0);if(!Number.isFinite(t)||t<=0)return`Not configured`;if(t%86400==0){let e=t/86400;return`${e} day${e===1?``:`s`}`}if(t%3600==0){let e=t/3600;return`${e} hour${e===1?``:`s`}`}if(t%60==0){let e=t/60;return`${e} minute${e===1?``:`s`}`}return`${t} second${t===1?``:`s`}`}function wn(e){let t=e?.result;if(!t||typeof t!=`object`||Array.isArray(t))return[];let n=t.providers;return!n||typeof n!=`object`||Array.isArray(n)?[]:Object.entries(n).filter(e=>{let[,t]=e;return!!(t&&typeof t==`object`&&!Array.isArray(t))}).sort(([e],[t])=>e.localeCompare(t))}function Tn(){return window.location.pathname.split(`/`).filter(Boolean)[1]||``}function En(){let e=Tn();return Object.hasOwn(fn,e)?e:`people`}function Dn(e=`gigs`){let[,t,n]=window.location.pathname.split(`/`).filter(Boolean);if(t!==e||!n)return``;try{return decodeURIComponent(n)}catch{return``}}async function K(e,t={}){let n=String(t.method||`GET`).toUpperCase(),r=new Headers(t.headers);r.set(`Accept`,`application/json`);let i;try{i=await fetch(e,{credentials:`same-origin`,...t,headers:r})}catch(t){throw new yn(xn(t,`Network request failed`),0,`Network request failed`,null,e,n)}if(i.status===401){let t=`${window.location.pathname}${window.location.search}`||`/dashboard`;throw window.location.assign(`/auth/login?next=${encodeURIComponent(t)}`),new yn(`Session expired`,i.status,i.statusText,null,e,n)}if(!i.ok){let t=i.statusText,r=null;try{r=await i.json(),r&&typeof r==`object`&&(t=bn(r,String(t||`Request failed`)))}catch{t=i.statusText}throw new yn(typeof t==`string`?t:JSON.stringify(t),i.status,i.statusText,r,e,n)}return i.json()}function On(e,t,n){if(e===`gigs`){let e=t;if(n===`title`)return e.title||``;if(n===`status`)return e.status||``;if(n===`applications`)return Number(e.application_count||0);if(n===`activity`)return Hn(e)}if(e===`projects`){let e=t;if(n===`display_name`)return e.display_name||``;if(n===`customer`)return e.customer||``;if(n===`status`)return e.source_status||``;if(n===`roster_count`)return Number(e.roster_count||0);if(n===`modified`)return e.source_modified_at||e.last_synced_at||``}if(e===`onboarding`){let e=t,r=e.profile_status||{};if(n===`name`)return e.name||e.email_508||e.email||``;if(n===`onboarding_state`){let t=Zt(e);return t.toLowerCase()===`pending`?`zzz-${t}`:t}if(n===`onboarder`)return e.onboarder||``;if(n===`updated`)return e.onboarding_updated_at||``;if(n===`profile_gaps`)return[!r.discord_linked,!r.latest_resume,Number(r.skills_count||0)<=0].filter(Boolean).length}if(e===`people`){let e=t,r=e.profile_status||{};if(n===`name`)return e.name||e.email_508||e.email||``;if(n===`status`)return[r.crm_active,r.is_member,r.discord_linked,r.email_508,r.latest_resume].filter(Boolean).length;if(n===`discord`)return e.discord_username||e.discord_user_id||``;if(n===`resume`)return e.latest_resume_name||e.latest_resume_id||``}if(e===`audit`){let e=t;if(n===`actor`)return e.actor_display_name||e.actor_subject||e.actor_provider||``}if(e===`newsletter`){let e=t;if(n===`email`)return e.email||``;if(n===`source_provider`)return e.source_provider||``;if(n===`reason`)return e.reason||``;if(n===`first_seen_at`)return e.first_seen_at||``;if(n===`last_seen_at`)return e.last_seen_at||e.updated_at||``}return t[n]??``}function kn(e,t,n){let r=n.direction===`asc`?1:-1;return[...t].sort((t,i)=>{let a=On(e,t,n.key),o=On(e,i,n.key);return typeof a==`number`&&typeof o==`number`?(a-o)*r:String(a).localeCompare(String(o),void 0,{numeric:!0})*r})}function An({label:e,scope:t,sort:n,sortKey:r,onSort:i}){let a=n.key===r,o=n.direction===`asc`?`↑`:`↓`;return(0,L.jsx)(`button`,{type:`button`,"data-sort-scope":t,"data-sort-key":r,className:`text-left font-[inherit] text-inherit hover:text-foreground`,onClick:()=>i(t,r),children:a?`${e} ${o}`:e})}function q({className:e,label:t,scope:n,sort:r,sortKey:i,onSort:a}){return(0,L.jsx)(W,{className:e,"aria-sort":r.key===i?r.direction===`asc`?`ascending`:`descending`:`none`,children:(0,L.jsx)(An,{label:t,scope:n,sort:r,sortKey:i,onSort:a})})}function jn({label:e,value:t,id:n}){return(0,L.jsxs)(B,{className:`p-4`,children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:e}),(0,L.jsx)(`strong`,{id:n,className:`block text-2xl`,children:t})]})}function Mn({children:e,hidden:t}){return t?null:(0,L.jsx)(`div`,{className:`px-4 py-7 text-center text-sm text-muted-foreground`,children:e})}function Nn({value:e,query:t}){let n=t.trim().toLowerCase();if(!n)return(0,L.jsx)(L.Fragment,{children:e});let r=e.toLowerCase(),i=[],a=0,o=r.indexOf(n);for(;o>=0;){o>a&&i.push(e.slice(a,o));let t=o+n.length;i.push((0,L.jsx)(`mark`,{className:`rounded-sm bg-amber-200 px-0.5 text-inherit dark:bg-amber-500/35`,children:e.slice(o,t)},`${o}-${t}`)),a=t,o=r.indexOf(n,a)}return avoid 0);function Dt(e){return s.includes(e)}function Ot(e){return s.includes(`${e}:dry_run`)}function kt(e){return Dt(e)||Ot(e)}function At(e){return Dt(pn[e])}function jt(){return Object.keys(fn).find(e=>At(e))||`people`}function N(e,t){o({message:e,tone:t})}function P(e,t){N(xn(e,t),`error`)}function F(e,t){Me(n=>({...n,[e]:t}))}function Mt(e,t=!1){let n=e;At(n)||(N(`${n[0].toUpperCase()}${n.slice(1)} requires SSO validation`,`error`),n=jt()),n!==`gigs`&&T(``),n!==`projects`&&fe(``),n===`gigs`&&t&&T(``),n===`projects`&&t&&fe(``),i(n),t?window.history.pushState({view:n},``,fn[n]):(!Object.hasOwn(fn,Tn())||n!==e)&&window.history.replaceState({view:n},``,fn[n])}Et.current=Mt;function Nt(e){return!u||!e?``:`${u}/#Contact/view/${encodeURIComponent(e)}`}function Pt(e){return!u||!e?``:`${u}/api/v1/Attachment/file/${encodeURIComponent(e)}`}function Ft(e,t){Re(n=>{let r=n[e];return{...n,[e]:{key:t,direction:r.key===t&&r.direction===`asc`?`desc`:`asc`}}})}function It(e){T(e),w(h.find(t=>t.id===e)||null),i(`gigs`),window.history.pushState({view:`gigs`,gigId:e},``,`/dashboard/gigs/${encodeURIComponent(e)}`)}function Lt(){T(``),w(null),window.history.replaceState({view:`gigs`},``,fn.gigs)}function Rt(e){fe(e),i(`projects`),window.history.pushState({view:`projects`,projectId:e},``,`/dashboard/projects/${encodeURIComponent(e)}`)}function R(){fe(``),window.history.replaceState({view:`projects`},``,fn.projects)}async function zt(){let e=await K(`/dashboard/api/me`);n(e);let t=Array.isArray(e.permissions)?e.permissions:[];return c(t),d((e.crm_base_url||``).replace(/\/+$/,``)),t}function B(){let e=new URLSearchParams({minutes:ze,limit:`100`});return Ve&&e.set(`status`,Ve),Ue.trim()&&e.set(`type`,Ue.trim()),`/dashboard/api/jobs?${e.toString()}`}function V(){let e=new URLSearchParams({limit:String(Ze)});return Ge&&e.set(`status`,Ge),qe.trim()&&e.set(`query`,qe.trim()),Ye&&e.set(`include_historical`,`true`),`/dashboard/api/gigs?${e.toString()}`}function Bt(){let e=new URLSearchParams({limit:`100`,status:tt});return $e.trim()&&e.set(`query`,$e.trim()),`/dashboard/api/projects?${e.toString()}`}async function Vt(){F(`jobs`,!0),N(`Loading background tasks`);try{let e=await K(B());p(e),N(`Loaded ${e.length} background task${e.length===1?``:`s`}`,`ok`)}catch(e){P(e,`Unable to load background tasks`)}finally{F(`jobs`,!1)}}async function H(){F(`gigs`,!0);try{let e=await K(V());y(e),N(`Loaded ${e.length} gig${e.length===1?``:`s`}`,`ok`),rn()}catch(e){P(e,`Unable to load gigs`)}finally{F(`gigs`,!1)}}async function U(){F(`projects`,!0);try{let e=await K(Bt());C(e.projects||[]),re(e.summary||{}),N(`Loaded ${(e.projects||[]).length} project${(e.projects||[]).length===1?``:`s`}`,`ok`)}catch(e){P(e,`Unable to load projects`)}finally{F(`projects`,!1)}}async function Ht(){F(`syncProjects`,!0),N(`Queueing project sync`);try{let e=await K(`/dashboard/api/sync/projects`,{method:`POST`});e.dry_run?N(`Dry run only: would queue ${e.would_enqueue?.job_type||`project sync`}`,`warning`):N(`Queued project sync ${e.job_id}`,`ok`)}catch(e){P(e,`Unable to queue project sync`)}finally{F(`syncProjects`,!1)}}async function Ut(e){let t=e.trim();if(t.length<2)return[];try{return(await K(`/dashboard/api/erpnext/customers?${new URLSearchParams({query:t}).toString()}`)).customers||[]}catch(e){return N(e instanceof Error?e.message:`Unable to search customers`,`error`),[]}}async function Wt(e){let t=e.trim();if(t.length<2)return[];try{return(await K(`/dashboard/api/erpnext/contacts?${new URLSearchParams({query:t}).toString()}`)).contacts||[]}catch(e){return N(e instanceof Error?e.message:`Unable to search contacts`,`error`),[]}}async function Gt(e){let t=e.trim();if(t.length<2)return[];try{return(await K(`/dashboard/api/erpnext/account-managers?${new URLSearchParams({query:t}).toString()}`)).users||[]}catch(e){return N(e instanceof Error?e.message:`Unable to search account managers`,`error`),[]}}async function Kt(){try{let e=(await K(`/dashboard/api/erpnext/cost-centers`)).cost_centers||[];return e.length?e:[{name:`Projects - 5`,cost_center_name:`Projects`}]}catch(e){return N(e instanceof Error?e.message:`Unable to load cost centers`,`error`),[{name:`Projects - 5`,cost_center_name:`Projects`}]}}async function W(e){F(`createProject`,!0);try{let t=await K(`/dashboard/api/projects/create`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)});return t.project.id?(C(e=>e.some(e=>e.id===t.project.id)?e.map(e=>e.id===t.project.id?t.project:e):[t.project,...e]),N(t.setup_warnings?.length?t.setup_warning_message||`Created ERP project setup; account manager setup needs follow-up`:`Created ERP project setup`,t.setup_warnings?.length?`warning`:`ok`),Rt(t.project.id)):(N([t.cache_refresh_message||`Created ERP project in ERPNext; local sync is pending`,t.setup_warnings?.length?t.setup_warning_message||`Account manager setup needs follow-up`:``].filter(Boolean).join(` `),t.setup_warnings?.length?`warning`:`ok`),U()),!0}catch(e){return N(e instanceof Error?e.message:`Unable to create project`,`error`),!1}finally{F(`createProject`,!1)}}async function G(e,t){F(`project:${e}:status`,!0);try{let n=await K(`/dashboard/api/projects/${encodeURIComponent(e)}/status`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({status:t})});C(t=>t.map(t=>t.id===e?n.project:t)),N(`Updated project status`,`ok`)}catch(e){P(e,`Unable to update project`)}finally{F(`project:${e}:status`,!1)}}async function qt(e,t){if(e.length===0)return!1;F(`projectsBulkUpdate`,!0);try{let n=await K(`/dashboard/api/projects/bulk`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({project_ids:e,...t})}),r=n.projects||[];C(e=>e.map(e=>r.find(t=>t.id===e.id)||e));let i=n.failures||[];return N(i.length?`Updated ${r.length}; ${i.length} failed`:`Updated ${r.length} project${r.length===1?``:`s`}`,i.length?`error`:`ok`),i.length===0}catch(e){return P(e,`Unable to bulk update projects`),!1}finally{F(`projectsBulkUpdate`,!1)}}async function Jt(e,t,n,r){let i=t.trim(),a=n.trim();if(!i||!a)return!1;F(`project:${e}:user`,!0);try{let t=await K(`/dashboard/api/projects/${encodeURIComponent(e)}/users`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({user:i,candidate_id:a,...r||{}})});return C(n=>n.map(n=>n.id===e?t.project:n)),N(t.activity_cost_error?`Added project user; rate failed`:t.activity_cost?`Added project user and rate`:`Added project user`,t.activity_cost_error?`error`:`ok`),!0}catch(e){return P(e,`Unable to add project user`),!1}finally{F(`project:${e}:user`,!1)}}async function Yt(e,t){let n=t.trim();if(!n)return!1;F(`project:${e}:user`,!0);try{let t=await K(`/dashboard/api/projects/${encodeURIComponent(e)}/users/remove`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({user:n})});return C(n=>n.map(n=>n.id===e?t.project:n)),N(`Removed project user`,`ok`),!0}catch(e){return N(e instanceof Error?e.message:`Unable to remove project user`,`error`),!1}finally{F(`project:${e}:user`,!1)}}async function Xt(e,t,n){let r=t.trim();if(!r)return!1;F(`project:${e}:historical`,!0);try{let t=await K(`/dashboard/api/projects/${encodeURIComponent(e)}/historical-members`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({person:r,candidate_id:n})});return C(n=>n.map(n=>n.id===e?t.project:n)),Ie(null),N(`Added historical project member`,`ok`),!0}catch(t){if(t instanceof yn&&t.status===409){let n=t.payload?.candidates||[];if(n.length>0)return Ie({projectId:e,person:r,candidates:n}),N(`Choose the matching person record`,`error`),!1}return P(t,`Unable to add historical member`),!1}finally{F(`project:${e}:historical`,!1)}}async function Zt(e,t){let n=t.trim();if(!n)return!1;F(`project:${e}:historical`,!0);try{let t=await K(`/dashboard/api/projects/${encodeURIComponent(e)}/historical-members/remove`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({source_user_id:n})});return C(n=>n.map(n=>n.id===e?t.project:n)),N(`Removed historical project member`,`ok`),!0}catch(e){return N(e instanceof Error?e.message:`Unable to remove historical member`,`error`),!1}finally{F(`project:${e}:historical`,!1)}}async function $t(e,t,n){F(`project:${e}:wiki`,!0);try{await K(`/dashboard/api/projects/${encodeURIComponent(e)}/wiki-match`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({status:t,row_key:n})}),N(t===`no_row`?`Marked as no wiki row`:`Confirmed wiki match`,`ok`),await en()}catch(e){P(e,`Unable to save wiki match`)}finally{F(`project:${e}:wiki`,!1)}}async function en(){F(`wikiMatches`,!0);try{se(await K(`/dashboard/api/projects/wiki-matches`)),N(`Loaded wiki match preview`,`ok`)}catch(e){P(e,`Unable to load wiki matches`)}finally{F(`wikiMatches`,!1)}}async function tn(e){F(`gig:${e}:detail`,!0);try{w(await K(`/dashboard/api/gigs/${encodeURIComponent(e)}`))}catch(e){w(null),P(e,`Unable to load gig`)}finally{F(`gig:${e}:detail`,!1)}}async function nn(){await H(),ue&&await tn(ue)}async function rn(){if(Dt(`gigs:read`)){F(`notifications`,!0);try{let e=await K(`/dashboard/api/notifications?limit=20`);A(e.stale_days||7),me(e.notifications||[])}catch(e){P(e,`Unable to load notifications`)}finally{F(`notifications`,!1)}}}async function an(e,t){F(`gig:${e}:status`,!0);try{let n=(await K(`/dashboard/api/gigs/${encodeURIComponent(e)}/status`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({status:t})})).discord_title_sync?.status;N(n===`error`?`Updated gig status; Discord title sync failed`:`Updated gig status`,n===`error`?`error`:`ok`),await H(),ue===e&&await tn(e)}catch(e){P(e,`Unable to update gig`)}finally{F(`gig:${e}:status`,!1)}}async function on(e,t,n){F(`application:${t}:status`,!0);try{await K(`/dashboard/api/gigs/${encodeURIComponent(e)}/applications/${encodeURIComponent(t)}/status`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({status:n})}),N(`Updated candidate status`,`ok`),await H(),ue===e&&await tn(e)}catch(e){P(e,`Unable to update candidate`)}finally{F(`application:${t}:status`,!1)}}async function sn(e,t){let n=t.trim();if(!n)return N(`Paste a CRM Contact profile first`,`warning`),!1;F(`gig:${e}:addCandidate`,!0);try{return await K(`/dashboard/api/gigs/${encodeURIComponent(e)}/applications`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({crm_profile:n})}),N(`Added candidate`,`ok`),await H(),ue===e&&await tn(e),!0}catch(e){return P(e,`Unable to add candidate`),!1}finally{F(`gig:${e}:addCandidate`,!1)}}function cn(){let e=new URLSearchParams({limit:`25`});it.trim()&&e.set(`query`,it.trim()),ot&&e.set(`is_member`,ot);for(let[t,n]of Object.entries(ct))n&&e.set(t,n);return`/dashboard/api/people?${e.toString()}`}async function ln(){F(`people`,!0);try{he(await K(cn()))}catch(e){P(e,`Unable to load people`)}finally{F(`people`,!1)}}async function un(){F(`newsletterStatus`,!0);try{ve(await K(`/dashboard/api/newsletter/status`))}catch(e){P(e,`Unable to load newsletter sync status`)}finally{F(`newsletterStatus`,!1)}}async function dn(){F(`newsletterSuppressions`,!0);try{ge((await K(`/dashboard/api/newsletter/suppressions?limit=200`)).suppressions||[])}catch(e){P(e,`Unable to load newsletter suppressions`)}finally{F(`newsletterSuppressions`,!1)}}async function hn(){await Promise.all([un(),dn()])}function gn(){let e=new URLSearchParams({limit:`25`});ht.trim()&&e.set(`query`,ht.trim()),_t&&e.set(`onboarding_state`,_t),yt.trim()&&e.set(`onboarder`,yt.trim());for(let[t,n]of Object.entries(xt))n&&e.set(t,n);return`/dashboard/api/onboarding?${e.toString()}`}async function bn(){F(`onboarding`,!0);try{be(await K(gn()))}catch(e){P(e,`Unable to load onboarding`)}finally{F(`onboarding`,!1)}}async function Cn(e,t){if(!e)return N(`Missing CRM contact`,`error`),null;let n=`onboarding-email-draft:${e}`;F(n,!0);try{let n=await K(`/dashboard/api/onboarding/${encodeURIComponent(e)}/email/draft`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)});return N(`Drafted onboarding email`,`ok`),n}catch(e){return P(e,`Unable to draft onboarding email`),null}finally{F(n,!1)}}async function On(e,t,n){if(!e)return N(`Missing CRM contact`,`error`),null;let r=`onboarding-email-send:${e}`;F(r,!0);try{let r=await K(`/dashboard/api/onboarding/${encodeURIComponent(e)}/email/send`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({...t,markdown_body:n})});return be(t=>t.map(t=>t.crm_contact_id===e?{...t,onboarding_email_sent_at:r.onboarding_email_sent_at||t.onboarding_email_sent_at,onboarding_email_sent_by:r.onboarding_email_sent_by||t.onboarding_email_sent_by,onboarding_email_recipient:r.onboarding_email_recipient||r.recipient_email||t.onboarding_email_recipient}:t)),N(`Sent onboarding email`,`ok`),r}catch(e){return P(e,`Unable to send onboarding email`),null}finally{F(r,!1)}}async function An(){F(`audit`,!0);try{Se(await K(`/dashboard/api/audit-events?limit=25`))}catch(e){P(e,`Unable to load audit events`)}finally{F(`audit`,!1)}}async function q(){F(`agent`,!0);try{we(await K(`/dashboard/api/agent?limit=100`))}catch(e){P(e,`Unable to load agent report`)}finally{F(`agent`,!1)}}async function jn(){F(`configuration`,!0);try{Ee((await K(`/dashboard/api/configuration`)).items)}catch(e){P(e,`Unable to load configuration`)}finally{F(`configuration`,!1)}}async function Mn(e,t){F(`configuration:${e}`,!0);try{Ee((await K(`/dashboard/api/configuration/${encodeURIComponent(e)}`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({value:t})})).items),N(`Saved ${e}`,`ok`)}catch(t){P(t,`Unable to save ${e}`)}finally{F(`configuration:${e}`,!1)}}async function Nn(e){F(`configuration:${e}`,!0);try{Ee((await K(`/dashboard/api/configuration/${encodeURIComponent(e)}`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({clear:!0})})).items),N(`Cleared ${e}`,`ok`)}catch(t){P(t,`Unable to clear ${e}`)}finally{F(`configuration:${e}`,!1)}}async function Pn(e){F(`detail:${e}`,!0),N(`Loading ${e}`);try{Ae(await K(`/dashboard/api/jobs/${encodeURIComponent(e)}`)),N(`Loaded ${e}`,`ok`)}catch(e){P(e,`Unable to load task detail`)}finally{F(`detail:${e}`,!1)}}async function Rn(e){F(`rerun:${e}`,!0),N(`Rerunning ${e}`);try{let t=await K(`/dashboard/api/jobs/${encodeURIComponent(e)}/rerun`,{method:`POST`});t.dry_run?N(`Dry run only: would rerun ${t.would_enqueue?.job_type||e}`,`warning`):(N(`Queued rerun ${t.job_id}`,`ok`),await Vt())}catch(e){P(e,`Unable to rerun task`)}finally{F(`rerun:${e}`,!1)}}async function zn(){F(`syncPeople`,!0),N(`Queueing people sync`);try{let e=await K(`/dashboard/api/sync/people`,{method:`POST`});e.dry_run?N(`Dry run only: would queue ${e.would_enqueue?.job_type||`people sync`}`,`warning`):N(`Queued people sync ${e.job_id}`,`ok`)}catch(e){P(e,`Unable to queue people sync`)}finally{F(`syncPeople`,!1)}}async function Bn(){F(`syncNewsletters`,!0),N(`Queueing newsletter sync`);try{let e=await K(`/dashboard/api/sync/newsletters`,{method:`POST`});if(e.dry_run){let t=Sn(e.preview);N(t?`Dry run only: ${t}`:`Dry run completed`,`warning`)}else N(`Queued newsletter sync ${e.job_id}`,`ok`);hn()}catch(e){P(e,`Unable to queue newsletter sync`)}finally{F(`syncNewsletters`,!1)}}async function Vn(e,t){let n=String(e||``).trim(),r=t.trim();if(!n){N(`Missing CRM contact id`,`error`);return}if(!r){N(`Enter a 508 username`,`error`);return}F(`onboarder:${n}`,!0),N(`Assigning ${r}`);try{let e=await K(`/dashboard/api/onboarding/${encodeURIComponent(n)}/onboarder`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({onboarder:r})});be(t=>t.map(t=>t.crm_contact_id===e.contact_id?{...t,onboarder:e.onboarder,onboarding_state:e.state_updated&&e.onboarding_state?e.onboarding_state:t.onboarding_state,onboarding_status_label:e.onboarding_status_label||(e.state_updated?void 0:t.onboarding_status_label)}:t)),N(`Assigned ${e.onboarder}`,`ok`)}catch(e){P(e,`Unable to assign onboarder`)}finally{F(`onboarder:${n}`,!1)}}async function Hn(e,t){let n=String(e||``).trim(),r=t.trim();if(!n){N(`Missing CRM contact id`,`error`);return}if(!r){N(`Choose an onboarding status`,`error`);return}F(`onboarding-status:${n}`,!0),N(`Updating onboarding status`);try{let e=await K(`/dashboard/api/onboarding/${encodeURIComponent(n)}/status`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({status:r})}),t=vn(e.onboarding_state),i=e.onboarding_status_label||Qt(t);be(n=>n.map(n=>n.crm_contact_id===e.contact_id?{...n,onboarding_state:t,onboarding_status_label:i}:n).filter(n=>n.crm_contact_id!==e.contact_id||!_n.has(t))),N(`Status set to ${i}`,`ok`)}catch(e){P(e,`Unable to update onboarding status`)}finally{F(`onboarding-status:${n}`,!1)}}async function Un(e){let t=e.email.trim().toLowerCase(),n=e.first_name.trim();if(!t?.endsWith(`@508.dev`))return N(`Enter the engineer's @508.dev email`,`error`),null;if(!n)return N(`Enter the engineer name`,`error`),null;F(`engineerSetup`,!0);try{let r=await K(`/dashboard/api/onboarding/engineers`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({...e,email:t,first_name:n})});return N(`Set up ${r.employee_name||r.user||t}`,`ok`),r}catch(e){if(e instanceof yn&&e.status===409){let t=e.payload&&typeof e.payload==`object`?e.payload:null,n=(Array.isArray(t?.matches)?t.matches:[]).map(e=>e?.label||e?.email).filter(Boolean).slice(0,2).join(`, `);N(n?`Similar account exists: ${n}`:`Similar account exists; confirm before creating`,`error`)}else N(e instanceof Error?e.message:`Unable to set up engineer`,`error`);return null}finally{F(`engineerSetup`,!1)}}async function Wn(){F(`logout`,!0);try{let e=await K(`/auth/logout`,{method:`POST`});window.location.assign(e.end_session_url||`/dashboard`)}catch(e){P(e,`Unable to log out`),F(`logout`,!1)}}(0,l.useEffect)(()=>{zt().then(e=>{let t=En(),n=e.includes(pn[t])?t:Object.keys(fn).find(t=>e.includes(pn[t]))||`people`;T(n===`gigs`?Dn():``),fe(n===`projects`?Dn(`projects`):``),i(n),(!Object.hasOwn(fn,Tn())||n!==t)&&window.history.replaceState({view:n},``,fn[n])}).catch(e=>{P(e,`Dashboard failed to load`)})},[]),(0,l.useEffect)(()=>{let e=()=>{T(Dn()),fe(Dn(`projects`)),Et.current(En(),!1)};return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)},[]),(0,l.useEffect)(()=>{if(!a.message)return;let e=window.setTimeout(()=>o({message:``}),4500);return()=>window.clearTimeout(e)},[a.message]),(0,l.useEffect)(()=>{},[]),(0,l.useEffect)(()=>{s.length!==0&&(Dt(`gigs:read`)&&rn(),r===`people`&&ln(),r===`gigs`&&H(),r===`projects`&&U(),r===`onboarding`&&bn(),r===`newsletter`&&hn(),r===`jobs`&&Vt(),r===`agent`&&q(),r===`audit`&&An(),r===`configuration`&&jn())},[r]),(0,l.useEffect)(()=>{s.length!==0&&(Dt(`gigs:read`)&&rn(),r===`people`&&ln(),r===`gigs`&&H(),r===`projects`&&U(),r===`onboarding`&&bn(),r===`newsletter`&&hn(),r===`jobs`&&Vt(),r===`agent`&&q(),r===`audit`&&An(),r===`configuration`&&jn())},[s]),(0,l.useEffect)(()=>{r===`jobs`&&s.length>0&&Vt()},[ze,Ve]),(0,l.useEffect)(()=>{r===`gigs`&&s.length>0&&H()},[Ge,Ye,Ze]),(0,l.useEffect)(()=>{r===`projects`&&s.length>0&&U()},[tt]),(0,l.useEffect)(()=>{r===`gigs`&&ue&&s.length>0&&tn(ue)},[r,ue,s]),(0,l.useEffect)(()=>{r===`people`&&s.length>0&&ln()},[ot]),(0,l.useEffect)(()=>{r===`people`&&s.length>0&&ln()},[ct]),(0,l.useEffect)(()=>{r===`onboarding`&&s.length>0&&bn()},[_t]),(0,l.useEffect)(()=>{r===`onboarding`&&s.length>0&&bn()},[xt]);let Gn=(0,l.useMemo)(()=>kn(`jobs`,f,Le.jobs),[f,Le.jobs]),Kn=(0,l.useMemo)(()=>kn(`people`,O,Le.people),[O,Le.people]),qn=(0,l.useMemo)(()=>kn(`onboarding`,ye,Le.onboarding),[ye,Le.onboarding]),Yn=(0,l.useMemo)(()=>kn(`gigs`,h,Le.gigs),[h,Le.gigs]),Xn=(0,l.useMemo)(()=>kn(`projects`,S,Le.projects),[S,Le.projects]),Qn=(0,l.useMemo)(()=>{let e=new Set;for(let t of k)t.source_provider&&e.add(t.source_provider);for(let[t]of wn(_e?.latest_job))e.add(t);return[...e].sort((e,t)=>e.localeCompare(t))},[k,_e]),$n=(0,l.useMemo)(()=>kn(`newsletter`,mt?k.filter(e=>e.source_provider===mt):k,Le.newsletter),[mt,k,Le.newsletter]),er=(0,l.useMemo)(()=>le?.id===ue?le:Yn.find(e=>e.id===ue)||null,[le,ue,Yn]),tr=(0,l.useMemo)(()=>Xn.find(e=>e.id===de)||null,[de,Xn]),ar=(0,l.useMemo)(()=>kn(`audit`,xe,Le.audit),[xe,Le.audit]),or=(0,l.useMemo)(()=>f.reduce((e,t)=>(e[t.status]=(e[t.status]||0)+1,e),{}),[f]),sr=Object.keys(mn).filter(e=>!ct[e]),cr=Object.keys(mn).filter(e=>e!==`sync_status`&&e!==`email_508`&&!xt[e]);function lr(e){if(e.type===`stale_recruiting_gig`){let t=e.engagement_id||(e.id.startsWith(`stale-recruiting:`)?e.id.slice(17):``);t?It(t):(Ke(`recruiting`),Mt(`gigs`,!0))}D(!1)}(0,l.useEffect)(()=>{!sr.includes(ut)&&sr[0]&&dt(sr[0])},[sr,ut]),(0,l.useEffect)(()=>{let e=mn[ut]?.options;e?.[0]&&!e.some(([e])=>e===ft)&&pt(e[0][0])},[ut,ft]),(0,l.useEffect)(()=>{!cr.includes(St)&&cr[0]&&Ct(cr[0])},[cr,St]),(0,l.useEffect)(()=>{let e=mn[St]?.options;e?.[0]&&!e.some(([e])=>e===wt)&&Tt(e[0][0])},[St,wt]);let ur=[t?.email,t?.crm_contact_id?`CRM ${t.crm_contact_id}`:``,t?.actor_provider].filter(Boolean).join(` | `);return(0,L.jsxs)(L.Fragment,{children:[(0,L.jsx)(`header`,{className:`sticky top-0 z-20 border-b bg-background/90 backdrop-blur`,children:(0,L.jsxs)(`div`,{className:`mx-auto flex max-w-7xl flex-col gap-4 px-5 py-4 md:flex-row md:items-center md:justify-between`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`h1`,{className:`text-xl font-bold`,children:`508 Operations Dashboard`}),(0,L.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Operations view for authenticated 508 operators.`})]}),(0,L.jsxs)(`div`,{className:`flex min-w-0 items-center gap-3`,children:[Dt(`gigs:read`)?(0,L.jsx)(`div`,{className:`relative`,children:(0,L.jsxs)(z,{id:`notifications`,type:`button`,variant:`outline`,size:`icon`,"aria-label":`Notifications`,"aria-expanded":E,onClick:()=>D(e=>!e),children:[(0,L.jsx)(g,{}),pe.length>0?(0,L.jsx)(`span`,{className:`absolute -right-1 -top-1 grid min-h-5 min-w-5 place-items-center rounded-full bg-red-500 px-1 text-[11px] font-bold text-white`,children:pe.length}):null]})}):null,(0,L.jsxs)(`div`,{className:`grid min-w-0 gap-0.5 text-right text-sm text-muted-foreground`,children:[(0,L.jsx)(`strong`,{id:`userName`,className:`truncate text-foreground`,children:t?.display_name||t?.email||t?.subject||`Loading user`}),(0,L.jsx)(`span`,{id:`userMeta`,className:`truncate`,children:ur||`Checking session`})]}),(0,L.jsxs)(z,{id:`logout`,type:`button`,variant:`outline`,onClick:Wn,disabled:je.logout,children:[(0,L.jsx)(ee,{}),`Log out`]})]})]})}),(0,L.jsx)(Fn,{open:E,notifications:pe,loading:je.notifications,onClose:()=>D(!1),onRefresh:rn,onOpenNotification:lr}),(0,L.jsx)(Ln,{toast:a}),null,(0,L.jsx)(In,{choice:Fe,loading:!!(Fe&&je[`project:${Fe.projectId}:historical`]),crmContactUrl:Nt,onClose:()=>Ie(null),onChoose:e=>{Fe&&Xt(Fe.projectId,Fe.person,e)}}),(0,L.jsxs)(`main`,{className:`mx-auto grid max-w-7xl grid-cols-1 gap-5 px-5 py-5 md:grid-cols-[190px_minmax(0,1fr)]`,children:[(0,L.jsx)(`nav`,{className:`grid content-start gap-1 md:sticky md:top-24`,"aria-label":`Dashboard sections`,children:[[`people`,`People`,ce],[`gigs`,`Gigs`,_],[`projects`,`Projects`,x],[`onboarding`,`Onboarding`,v],[`newsletter`,`Newsletter`,te],[`jobs`,`Background tasks`,m],[`agent`,`Agent`,ae],[`audit`,`Audit`,b],[`configuration`,`Configuration`,ie]].filter(([e])=>At(e)).map(([e,t,n])=>(0,L.jsxs)(`a`,{className:I(`flex min-h-10 items-center gap-2 rounded-md border border-transparent px-3 text-sm font-extrabold text-muted-foreground hover:border-border hover:bg-secondary hover:text-foreground`,r===e&&`border-primary bg-accent text-accent-foreground`),"data-view-link":e,"data-permission":pn[e],href:fn[e],"aria-current":r===e?`page`:void 0,onClick:t=>{t.preventDefault(),Mt(e,!0)},children:[(0,L.jsx)(n,{className:`size-4`}),t]},e))}),(0,L.jsxs)(`div`,{className:`grid min-w-0 gap-5`,children:[r===`people`?(0,L.jsx)(nr,{crmBaseUrl:u,people:Kn,sort:Le.people,canSync:kt(`people:sync`),loading:je,peopleQuery:it,peopleMember:ot,peopleFilters:ct,peopleFilterKind:ut,peopleFilterValue:ft,peopleFilterKeys:sr,onSearch:ln,onSync:zn,onSort:e=>Ft(`people`,e),setPeopleQuery:at,setPeopleMember:st,setPeopleFilterKind:dt,setPeopleFilterValue:pt,addFilter:()=>{lt(e=>({...e,[ut]:ft}))},removeFilter:e=>{lt(t=>{let n={...t};return delete n[e],n})},crmContactUrl:Nt,crmAttachmentUrl:Pt}):null,r===`newsletter`?(0,L.jsx)(rr,{status:_e,suppressions:$n,providerOptions:Qn,providerFilter:mt,sort:Le.newsletter,loading:je,canSync:kt(`people:sync`),onRefresh:hn,onSync:Bn,onProviderFilterChange:j,onSort:e=>Ft(`newsletter`,e)}):null,r===`gigs`?(0,L.jsx)(Zn,{gigs:Yn,selectedGig:er,selectedGigId:ue,sort:Le.gigs,loading:je,status:Ge,query:qe,includeHistorical:Ye,limit:Ze,staleDays:rt,canWrite:Dt(`gigs:write`),canIncludeHistorical:Dt(`people:read`),crmContactUrl:Nt,crmAttachmentUrl:Pt,setStatus:Ke,setQuery:Je,setIncludeHistorical:Xe,setLimit:Qe,onRefresh:nn,onSort:e=>Ft(`gigs`,e),onOpenGig:It,onCloseGig:Lt,onUpdateStatus:an,onAddApplication:sn,onUpdateApplicationStatus:on}):null,r===`projects`?(0,L.jsx)(Jn,{projects:Xn,selectedProject:tr,selectedProjectId:de,summary:ne,wikiMatches:oe,sort:Le.projects,loading:je,query:$e,status:tt,canSync:kt(`projects:sync`),canWrite:Dt(`projects:write`),crmContactUrl:Nt,setQuery:et,setStatus:nt,onSearch:U,onSync:Ht,onSearchCustomers:Ut,onSearchContacts:Wt,onSearchAccountManagers:Gt,onLoadCostCenters:Kt,onCreateProject:W,onUpdateStatus:G,onBulkUpdate:qt,onAddUser:Jt,onRemoveUser:Yt,onAddHistoricalMember:Xt,onRemoveHistoricalMember:Zt,onUpdateWikiMatch:$t,onWikiMatches:en,onOpenProject:Rt,onCloseProject:R,onSort:e=>Ft(`projects`,e)}):null,r===`onboarding`?(0,L.jsx)(ir,{people:qn,sort:Le.onboarding,loading:je,onboardingQuery:ht,onboardingState:_t,onboarderFilter:yt,onboardingFilters:xt,onboardingFilterKind:St,onboardingFilterValue:wt,onboardingFilterKeys:cr,onSearch:bn,onSort:e=>Ft(`onboarding`,e),onAssign:Vn,onStatusChange:Hn,onDraftEmail:Cn,onSendEmail:On,onSetupEngineer:Un,setOnboardingQuery:gt,setOnboardingState:vt,setOnboarderFilter:bt,setOnboardingFilterKind:Ct,setOnboardingFilterValue:Tt,addFilter:()=>{M(e=>({...e,[St]:wt}))},removeFilter:e=>{M(t=>{let n={...t};return delete n[e],n})},crmContactUrl:Nt,crmAttachmentUrl:Pt,canWrite:Dt(`onboarding:write`),canConfigure:Dt(`configuration:write`),onOpenConfiguration:()=>{Oe({category:`Onboarding`,nonce:Date.now()}),Mt(`configuration`,!0)}}):null,r===`jobs`?(0,L.jsx)(fr,{jobs:Gn,jobDetail:ke,sort:Le.jobs,loading:je,minutes:ze,status:Ve,jobType:Ue,jobCounts:or,canWrite:kt(`jobs:write`),setMinutes:Be,setStatus:He,setJobType:We,onSearch:Vt,onSort:e=>Ft(`jobs`,e),onDetail:Pn,onRerun:Rn}):null,r===`audit`?(0,L.jsx)(pr,{events:ar,sort:Le.audit,loading:je,onRefresh:An,onSort:e=>Ft(`audit`,e)}):null,r===`agent`?(0,L.jsx)(mr,{report:Ce,loading:je,onRefresh:q}):null,r===`configuration`?(0,L.jsx)(hr,{items:Te,loading:je,canWrite:Dt(`configuration:write`),focusCategory:De?.category,focusNonce:De?.nonce,onRefresh:jn,onSave:Mn,onClear:Nn}):null]})]})]})}function Fn({open:e,notifications:t,loading:n,onClose:r,onRefresh:i,onOpenNotification:a}){return e?(0,L.jsxs)(`div`,{className:`fixed inset-0 z-40`,"aria-labelledby":`notificationsTitle`,"aria-modal":`true`,role:`dialog`,children:[(0,L.jsx)(`button`,{type:`button`,className:`absolute inset-0 cursor-default bg-black/45`,"aria-label":`Close notifications`,onClick:r}),(0,L.jsxs)(`aside`,{className:`absolute right-0 top-0 grid h-full w-full max-w-md grid-rows-[auto_minmax(0,1fr)] border-l bg-background shadow-2xl`,children:[(0,L.jsxs)(`div`,{className:`flex items-center justify-between gap-3 border-b p-4`,children:[(0,L.jsxs)(`div`,{className:`grid gap-0.5`,children:[(0,L.jsx)(`strong`,{id:`notificationsTitle`,className:`text-base`,children:`Notifications`}),(0,L.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:t.length===0?`No active notifications`:`${t.length} active`})]}),(0,L.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,L.jsxs)(z,{type:`button`,variant:`outline`,size:`sm`,onClick:i,disabled:n,children:[(0,L.jsx)(C,{}),`Refresh`]}),(0,L.jsx)(z,{type:`button`,variant:`ghost`,size:`icon`,"aria-label":`Close`,onClick:r,children:(0,L.jsx)(le,{})})]})]}),(0,L.jsx)(`div`,{className:`min-h-0 overflow-auto p-4`,children:t.length===0?(0,L.jsx)(`div`,{className:`rounded-md border border-dashed p-6 text-sm text-muted-foreground`,children:`No active notifications.`}):(0,L.jsx)(`div`,{className:`grid gap-3`,children:t.map(e=>(0,L.jsxs)(`button`,{type:`button`,className:`grid gap-2 rounded-md border p-3 text-left hover:bg-secondary`,onClick:()=>a(e),children:[(0,L.jsx)(`span`,{className:`text-sm font-bold`,children:e.title}),(0,L.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:e.message})]},e.id))})})]})]}):null}function In({choice:e,loading:t,crmContactUrl:n,onClose:r,onChoose:i}){return e?(0,L.jsxs)(`div`,{className:`fixed inset-0 z-50 grid place-items-center p-4`,"aria-labelledby":`historicalPersonChoiceTitle`,"aria-modal":`true`,role:`dialog`,children:[(0,L.jsx)(`button`,{type:`button`,className:`absolute inset-0 cursor-default bg-black/45`,"aria-label":`Close person selection`,onClick:r}),(0,L.jsxs)(`div`,{className:`relative grid w-full max-w-2xl gap-4 rounded-md border bg-background p-5 shadow-2xl`,children:[(0,L.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`strong`,{id:`historicalPersonChoiceTitle`,className:`block text-base`,children:`Choose person record`}),(0,L.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:e.person})]}),(0,L.jsx)(z,{type:`button`,variant:`ghost`,size:`icon`,"aria-label":`Close person selection`,onClick:r,children:(0,L.jsx)(le,{})})]}),(0,L.jsx)(`div`,{className:`grid gap-2`,children:e.candidates.map(e=>(0,L.jsxs)(`div`,{className:`grid gap-3 rounded-md border p-3 md:grid-cols-[minmax(0,1fr)_auto] md:items-center`,children:[(0,L.jsxs)(`div`,{className:`min-w-0`,children:[(0,L.jsx)(`strong`,{className:`block truncate`,children:e.label||e.full_name||e.email||`Person`}),(0,L.jsxs)(`div`,{className:`flex flex-wrap gap-x-3 gap-y-1 text-sm text-muted-foreground`,children:[e.email?(0,L.jsx)(`span`,{children:e.email}):null,e.sources?.length?(0,L.jsx)(`span`,{children:e.sources.join(`, `)}):null,e.erpnext_user_id?(0,L.jsxs)(`span`,{children:[`ERP `,e.erpnext_user_id]}):null,e.supplier_erpnext_id?(0,L.jsxs)(`span`,{children:[`Supplier `,e.supplier_erpnext_id]}):null,e.crm_contact_id&&n(e.crm_contact_id)?(0,L.jsx)(`a`,{className:`font-semibold text-primary underline-offset-4 hover:underline`,href:n(e.crm_contact_id),target:`_blank`,rel:`noreferrer`,children:`CRM`}):null]})]}),(0,L.jsx)(z,{type:`button`,disabled:t,onClick:()=>i(e.candidate_id),children:`Select`})]},e.candidate_id))})]})]}):null}function Ln({toast:e}){return e.message?(0,L.jsx)(`div`,{id:`toast`,role:`status`,className:I(`fixed bottom-5 right-5 z-50 max-w-sm rounded-md border bg-background px-4 py-3 text-sm font-semibold shadow-lg`,e.tone===`ok`&&`border-emerald-500/40 text-emerald-300`,e.tone===`warning`&&`border-amber-500/40 text-amber-200`,e.tone===`error`&&`border-red-500/40 text-red-300`),children:e.message}):null}function Rn({filters:e,onRemove:t,suffix:n=`filter`}){return(0,L.jsx)(`fieldset`,{className:`m-0 flex min-h-7 flex-wrap gap-2 border-0 p-0`,"aria-label":`Active filters`,children:Object.entries(e).map(([e,r])=>{let i=mn[e],a=i.options.find(([e])=>e===r),o=`${i.label}: ${a?a[1]:r}`;return(0,L.jsxs)(z,{type:`button`,variant:`outline`,size:`sm`,className:`rounded-full`,"aria-label":`Remove ${o} ${n}`,onClick:()=>t(e),children:[o,` x`]},e)})})}var zn=[`recruiting`,`filled`,`unknown`,`lost`,`outdated`],Bn=[`suggested`,`interested`,`reviewing`,`contacted`,`accepted`,`unavailable`,`rejected`,`withdrawn`];function Vn(e){return String(e||``).replace(/[-_]+/g,` `).replace(/\s+/g,` `).trim().replace(/\b\w/g,e=>e.toUpperCase())}function Hn(e){let t=[e.last_activity_at,e.last_status_changed_at,e.posted_at,e.created_at].map(e=>e?new Date(e).getTime():NaN).filter(e=>!Number.isNaN(e));return t.length>0?new Date(Math.max(...t)).toISOString():``}function Un(e,t){if(e.status!==`recruiting`)return null;let n=Yt(Hn(e));return n===null||ne.projects.map(e=>e.id),[e.projects]),m=(0,l.useMemo)(()=>new Set(p),[p]),g=n.filter(e=>m.has(e)),_=e.projects.length>0&&g.length===e.projects.length;(0,l.useEffect)(()=>{r(e=>e.filter(e=>m.has(e)))},[m]);function v(e,t){r(n=>t?Array.from(new Set([...n,e])):n.filter(t=>t!==e))}async function b(){let t={};i&&(t.status=i),o&&(t.project_type=o),await e.onBulkUpdate(g,t)&&(r([]),a(``),s(``),u(!1))}let x=(0,L.jsxs)(B,{className:`grid gap-3 p-4 md:grid-cols-[minmax(0,1fr)_180px_auto_auto_auto] md:items-end`,children:[(0,L.jsxs)(U,{children:[`Search projects`,(0,L.jsx)(H,{id:`projectQuery`,value:e.query,autoComplete:`off`,placeholder:`Project, customer, ERP id`,onChange:t=>e.setQuery(t.target.value),onKeyDown:t=>t.key===`Enter`&&e.onSearch()})]}),(0,L.jsxs)(U,{children:[`Status`,(0,L.jsxs)(Ht,{id:`projectStatus`,value:e.status,onChange:t=>e.setStatus(t.target.value),children:[(0,L.jsx)(`option`,{value:`Open`,children:`Open`}),(0,L.jsx)(`option`,{value:``,children:`Any status`})]})]}),(0,L.jsxs)(z,{id:`refreshProjects`,type:`button`,onClick:e.onSearch,disabled:e.loading.projects,children:[(0,L.jsx)(C,{}),`Refresh`]}),e.canSync?(0,L.jsxs)(z,{id:`syncProjects`,type:`button`,variant:`outline`,onClick:e.onSync,disabled:e.loading.syncProjects,children:[(0,L.jsx)(C,{}),`Sync ERP`]}):null,(0,L.jsxs)(z,{id:`wikiProjectMatches`,type:`button`,variant:`outline`,onClick:e.onWikiMatches,disabled:e.loading.wikiMatches,children:[(0,L.jsx)(ne,{}),`Wiki match`]})]});return e.selectedProjectId&&!e.selectedProject&&e.loading.projects?(0,L.jsxs)(L.Fragment,{children:[x,(0,L.jsxs)(B,{children:[(0,L.jsx)(V,{children:(0,L.jsx)(Bt,{children:`Project detail`})}),(0,L.jsx)(Vt,{className:`text-sm text-muted-foreground`,children:`Loading project.`})]})]}):e.selectedProjectId&&!e.selectedProject?(0,L.jsxs)(L.Fragment,{children:[x,(0,L.jsxs)(B,{children:[(0,L.jsx)(V,{children:(0,L.jsx)(Bt,{children:`Project detail`})}),(0,L.jsxs)(Vt,{className:`grid gap-3`,children:[(0,L.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`This project is not in the current result set. Clear filters or refresh the project list.`}),(0,L.jsxs)(z,{type:`button`,variant:`outline`,onClick:e.onCloseProject,children:[(0,L.jsx)(h,{}),`Back to projects`]})]})]})]}):e.selectedProject?(0,L.jsxs)(L.Fragment,{children:[x,(0,L.jsx)(Xn,{project:e.selectedProject,loading:e.loading,canWrite:e.canWrite,crmContactUrl:e.crmContactUrl,onBack:e.onCloseProject,onUpdateStatus:e.onUpdateStatus,onAddUser:e.onAddUser,onRemoveUser:e.onRemoveUser,onAddHistoricalMember:e.onAddHistoricalMember,onRemoveHistoricalMember:e.onRemoveHistoricalMember})]}):(0,L.jsxs)(L.Fragment,{children:[x,(0,L.jsxs)(`section`,{className:`grid gap-3 md:grid-cols-2`,"aria-label":`Project summary`,children:[(0,L.jsx)(jn,{id:`projectMetricOpen`,label:`Open`,value:e.summary.open_project_count||0}),(0,L.jsx)(jn,{id:`projectMetricTotal`,label:`Projects`,value:e.summary.project_count||0})]}),e.canWrite?(0,L.jsxs)(B,{className:`flex flex-wrap items-center justify-between gap-3 p-4`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Selected`}),(0,L.jsxs)(`strong`,{className:`block`,children:[g.length,` project(s)`]})]}),(0,L.jsxs)(`div`,{className:`flex flex-wrap gap-2`,children:[(0,L.jsxs)(z,{type:`button`,onClick:()=>f(!0),children:[(0,L.jsx)(S,{}),`New project`]}),(0,L.jsx)(z,{type:`button`,variant:`outline`,disabled:g.length===0,onClick:()=>u(!0),children:`Bulk edit`})]})]}):null,d?(0,L.jsx)(Yn,{loading:e.loading.createProject,onClose:()=>f(!1),onSearchCustomers:e.onSearchCustomers,onSearchContacts:e.onSearchContacts,onSearchAccountManagers:e.onSearchAccountManagers,onLoadCostCenters:e.onLoadCostCenters,onCreateProject:e.onCreateProject}):null,c?(0,L.jsxs)(`div`,{className:`fixed inset-0 z-50 grid place-items-center p-4`,"aria-labelledby":`bulkProjectEditTitle`,"aria-modal":`true`,role:`dialog`,children:[(0,L.jsx)(`button`,{type:`button`,className:`absolute inset-0 cursor-default bg-black/45`,"aria-label":`Close bulk project edit`,onClick:()=>u(!1)}),(0,L.jsxs)(`div`,{className:`relative grid w-full max-w-lg gap-4 rounded-md border bg-background p-5 shadow-2xl`,children:[(0,L.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`strong`,{id:`bulkProjectEditTitle`,className:`block text-base`,children:`Bulk edit projects`}),(0,L.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[g.length,` selected`]})]}),(0,L.jsx)(z,{type:`button`,variant:`ghost`,size:`icon`,"aria-label":`Close bulk project edit`,onClick:()=>u(!1),children:(0,L.jsx)(le,{})})]}),(0,L.jsxs)(`div`,{className:`grid gap-3`,children:[(0,L.jsx)(`strong`,{className:`text-sm`,children:`Changes`}),(0,L.jsxs)(U,{children:[`Status`,(0,L.jsxs)(Ht,{value:i,onChange:e=>a(e.target.value),children:[(0,L.jsx)(`option`,{value:``,children:`No change`}),(0,L.jsx)(`option`,{value:`Open`,children:`Open`}),(0,L.jsx)(`option`,{value:`Completed`,children:`Completed`}),(0,L.jsx)(`option`,{value:`Cancelled`,children:`Cancelled`})]})]}),(0,L.jsxs)(U,{children:[`ERP Type`,(0,L.jsxs)(Ht,{value:o,onChange:e=>s(e.target.value),children:[(0,L.jsx)(`option`,{value:``,children:`No change`}),(0,L.jsx)(`option`,{value:`Internal`,children:`Internal`}),(0,L.jsx)(`option`,{value:`External`,children:`External`})]})]})]}),(0,L.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-2`,children:[(0,L.jsx)(z,{type:`button`,variant:`outline`,onClick:()=>u(!1),children:`Cancel`}),(0,L.jsx)(z,{type:`button`,disabled:e.loading.projectsBulkUpdate||g.length===0||!i&&!o,onClick:()=>void b(),children:`Apply changes`})]})]})]}):null,(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`ERP projects`}),(0,L.jsx)(`span`,{id:`projectsStatus`,className:`text-sm text-muted-foreground`,children:e.loading.projects?`Loading`:`${e.projects.length} shown | synced ${Jt(e.summary.last_synced_at)}`})]}),(0,L.jsx)(Mn,{hidden:e.projects.length!==0,children:`No projects match this view. Sync ERP projects if the cache is empty.`}),(0,L.jsx)(`div`,{className:`overflow-x-auto`,children:(0,L.jsxs)(Ut,{id:`projectsTable`,className:I(`min-w-[1100px]`,e.projects.length===0&&`hidden`),"aria-label":`ERP projects`,children:[(0,L.jsx)(Wt,{children:(0,L.jsxs)(Kt,{children:[e.canWrite?(0,L.jsx)(W,{className:`w-[48px]`,children:(0,L.jsx)(`input`,{type:`checkbox`,"aria-label":`Select all visible projects`,checked:_,onChange:e=>{r(e.target.checked?p:[])}})}):null,(0,L.jsx)(q,{className:`w-[24%]`,label:`Project`,scope:`projects`,sort:e.sort,sortKey:`display_name`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[16%]`,label:`Customer`,scope:`projects`,sort:e.sort,sortKey:`customer`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[10%]`,label:`Status`,scope:`projects`,sort:e.sort,sortKey:`status`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(W,{className:`w-[16%]`,children:`Timeline`}),(0,L.jsx)(q,{className:`w-[10%]`,label:`Roster`,scope:`projects`,sort:e.sort,sortKey:`roster_count`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[14%]`,label:`Modified`,scope:`projects`,sort:e.sort,sortKey:`modified`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(W,{children:`ERP`})]})}),(0,L.jsx)(Gt,{id:`projectsBody`,children:e.projects.map(t=>{let n=t.roster_members||[];return(0,L.jsxs)(Kt,{children:[e.canWrite?(0,L.jsx)(G,{children:(0,L.jsx)(`input`,{type:`checkbox`,"aria-label":`Select ${t.display_name}`,checked:g.includes(t.id),onChange:e=>v(t.id,e.target.checked)})}):null,(0,L.jsxs)(G,{children:[(0,L.jsx)(`button`,{type:`button`,className:`text-left font-bold text-primary underline-offset-4 hover:underline`,onClick:()=>e.onOpenProject(t.id),children:t.display_name}),(0,L.jsxs)(`div`,{className:`mt-1 flex flex-wrap items-center gap-1.5`,children:[t.project_type?(0,L.jsx)(R,{variant:`neutral`,children:t.project_type}):null,t.linked_engagement_count?(0,L.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[t.linked_engagement_count,` linked gig`]}):null]})]}),(0,L.jsx)(G,{children:t.customer_erpnext_url?(0,L.jsxs)(`a`,{className:`inline-flex items-center gap-1 font-semibold text-primary underline-offset-4 hover:underline`,href:t.customer_erpnext_url,target:`_blank`,rel:`noreferrer`,children:[t.customer,(0,L.jsx)(y,{className:`size-3.5`})]}):t.customer||`None`}),(0,L.jsx)(G,{children:(0,L.jsx)(R,{variant:Wn(t.source_status),children:t.source_status||`Unknown`})}),(0,L.jsx)(G,{children:[t.actual_start_date,t.actual_end_date].filter(Boolean).map(e=>Gn(e)).join(` to `)||`Not set`}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`grid gap-1`,children:[(0,L.jsx)(`strong`,{children:n.length}),(0,L.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[n.map(Kn).slice(0,4).join(`, `)||`No ERP roster`,n.length>4?` +${n.length-4}`:``]})]})}),(0,L.jsx)(G,{children:Jt(t.source_modified_at)}),(0,L.jsx)(G,{className:`text-xs`,children:t.erpnext_project_url?(0,L.jsxs)(`a`,{className:`inline-flex items-center gap-1 font-mono font-semibold text-primary underline-offset-4 hover:underline`,href:t.erpnext_project_url,target:`_blank`,rel:`noreferrer`,children:[t.erpnext_project_id,(0,L.jsx)(y,{className:`size-3.5`})]}):(0,L.jsx)(`span`,{className:`font-mono`,children:`Unlinked`})})]},t.id)})})]})})]}),e.wikiMatches?(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`Wiki match preview`}),(0,L.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[e.wikiMatches.document?.title||`Client & Project Info`,` |`,` `,Jt(e.wikiMatches.document?.updatedAt)]})]}),(0,L.jsx)(`div`,{className:`overflow-x-auto`,children:(0,L.jsxs)(Ut,{id:`wikiMatchesTable`,className:`min-w-[920px]`,"aria-label":`Wiki matches`,children:[(0,L.jsx)(Wt,{children:(0,L.jsxs)(Kt,{children:[(0,L.jsx)(W,{children:`ERP project`}),(0,L.jsx)(W,{children:`Best wiki row`}),(0,L.jsx)(W,{children:`Confidence`}),(0,L.jsx)(W,{children:`Section`}),(0,L.jsx)(W,{children:`Decision`})]})}),(0,L.jsx)(Gt,{children:t.map((t,n)=>{let r=t.project,i=t.best_match?.row||{},a=t.manual_match?.match_status||``,o=r?.id||i.row_key||[i.section,i.Client].filter(Boolean).join(`:`)||`wiki-match-${n}`;return(0,L.jsxs)(Kt,{children:[(0,L.jsx)(G,{children:r?.display_name||`Unknown`}),(0,L.jsxs)(G,{children:[(0,L.jsx)(`strong`,{children:i.Client||`No match`}),(0,L.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:[i.DRI,i.Members].filter(Boolean).join(` | `)})]}),(0,L.jsx)(G,{children:(0,L.jsx)(R,{variant:t.best_match?.confidence===`high`?`succeeded`:t.best_match?.confidence===`medium`?`running`:`neutral`,children:t.best_match?`${t.best_match.confidence} ${t.best_match.score}`:`none`})}),(0,L.jsx)(G,{children:i.section||``}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[a?(0,L.jsx)(R,{variant:a===`confirmed`?`succeeded`:`neutral`,children:a===`no_row`?`No wiki row`:`Confirmed`}):null,e.canWrite&&r?.id?(0,L.jsxs)(L.Fragment,{children:[i.row_key?(0,L.jsx)(z,{type:`button`,variant:`outline`,size:`sm`,disabled:e.loading[`project:${r.id}:wiki`],onClick:()=>void e.onUpdateWikiMatch(r.id,`confirmed`,i.row_key),children:`Confirm`}):null,(0,L.jsx)(z,{type:`button`,variant:`outline`,size:`sm`,disabled:e.loading[`project:${r.id}:wiki`],onClick:()=>void e.onUpdateWikiMatch(r.id,`no_row`),children:`No row`})]}):null]})})]},o)})})]})})]}):null]})}function Yn(e){let[t,n]=(0,l.useState)(``),[r,i]=(0,l.useState)(`new`),[a,o]=(0,l.useState)(``),[s,c]=(0,l.useState)(``),[u,d]=(0,l.useState)(``),[f,p]=(0,l.useState)([]),[m,h]=(0,l.useState)(``),[g,_]=(0,l.useState)(``),[v,y]=(0,l.useState)([]),[b,x]=(0,l.useState)(`USD`),[ee,te]=(0,l.useState)(``),[S,C]=(0,l.useState)(``),[ne,re]=(0,l.useState)(``),[ie,ae]=(0,l.useState)(``),[oe,se]=(0,l.useState)(``),[ce,w]=(0,l.useState)(``),[ue,T]=(0,l.useState)(`United States`),[de,fe]=(0,l.useState)(``),[pe,me]=(0,l.useState)(`new`),[E,D]=(0,l.useState)(``),[O,he]=(0,l.useState)(``),[k,ge]=(0,l.useState)([]),[_e,ve]=(0,l.useState)(``),[ye,be]=(0,l.useState)(``),[xe,Se]=(0,l.useState)(``),[Ce,we]=(0,l.useState)(``),[Te,Ee]=(0,l.useState)(``),[De,Oe]=(0,l.useState)(!1),[ke,Ae]=(0,l.useState)([{name:`Projects - 5`,cost_center_name:`Projects`}]),[je,Me]=(0,l.useState)(`Projects - 5`),[Ne,Pe]=(0,l.useState)(``),[Fe,Ie]=(0,l.useState)(!1),Le=(0,l.useRef)(e.onSearchCustomers),Re=(0,l.useRef)(e.onSearchContacts),ze=(0,l.useRef)(e.onSearchAccountManagers),Be=(0,l.useRef)(e.onLoadCostCenters),Ve=(0,l.useRef)(0),He=(0,l.useRef)(0),Ue=(0,l.useRef)(0),We=(0,l.useRef)(0),Ge=t.trim()?`Engineering for ${t.trim()}`.slice(0,140):``,Ke=[ne,ie,oe,ce,de].some(e=>e.trim()),qe=[_e,ye,xe,Ce,Te].some(e=>e.trim()),Je=t.trim()&&(r===`new`?a.trim():u.trim())&&!e.loading;(0,l.useEffect)(()=>{Le.current=e.onSearchCustomers},[e.onSearchCustomers]),(0,l.useEffect)(()=>{Re.current=e.onSearchContacts},[e.onSearchContacts]),(0,l.useEffect)(()=>{ze.current=e.onSearchAccountManagers},[e.onSearchAccountManagers]),(0,l.useEffect)(()=>{Be.current=e.onLoadCostCenters},[e.onLoadCostCenters]),(0,l.useEffect)(()=>{let e=!0,t=Ve.current+1;return Ve.current=t,Be.current().then(n=>{!e||Ve.current!==t||(Ae(n),Me(e=>n.some(t=>t.name===e)?e:`Projects - 5`))}),()=>{e=!1}},[]),(0,l.useEffect)(()=>{if(r!==`existing`){He.current+=1,p([]);return}let e=!0,t=He.current+1;He.current=t;let n=window.setTimeout(()=>{Le.current(s).then(n=>{!e||He.current!==t||p(n)})},250);return()=>{e=!1,window.clearTimeout(n)}},[r,s]),(0,l.useEffect)(()=>{if(r!==`new`){Ue.current+=1,y([]);return}let e=!0,t=Ue.current+1;Ue.current=t;let n=window.setTimeout(()=>{ze.current(m).then(n=>{!e||Ue.current!==t||y(n)})},250);return()=>{e=!1,window.clearTimeout(n)}},[r,m]),(0,l.useEffect)(()=>{if(r!==`new`||pe!==`existing`){We.current+=1,ge([]);return}let e=!0,t=We.current+1;We.current=t;let n=window.setTimeout(()=>{Re.current(E).then(n=>{!e||We.current!==t||ge(n)})},250);return()=>{e=!1,window.clearTimeout(n)}},[r,pe,E]);async function Ye(){Je&&await e.onCreateProject({project_name:t.trim(),customer_mode:r,customer_name:r===`new`?a.trim():void 0,customer:r===`existing`?u.trim():void 0,account_manager:r===`new`&&g.trim()||void 0,default_billing_currency:r===`new`?b.trim()||`USD`:void 0,default_cost_center:je.trim()||`Projects - 5`,activity_type:Fe&&Ne.trim()||void 0,customer_details:r===`new`&&ee.trim()||void 0,customer_website:r===`new`&&S.trim()||void 0,address_line1:r===`new`&&ne.trim()||void 0,address_line2:r===`new`&&ie.trim()||void 0,address_city:r===`new`&&oe.trim()||void 0,address_state:r===`new`&&ce.trim()||void 0,address_country:r===`new`&&ne.trim()?ue.trim()||`United States`:void 0,address_postal_code:r===`new`&&de.trim()||void 0,contact:r===`new`&&pe===`existing`&&O.trim()||void 0,contact_first_name:r===`new`&&pe===`new`&&_e.trim()||void 0,contact_last_name:r===`new`&&pe===`new`&&ye.trim()||void 0,contact_email:r===`new`&&pe===`new`&&xe.trim()||void 0,contact_phone:r===`new`&&pe===`new`&&Ce.trim()||void 0,contact_mobile:r===`new`&&pe===`new`&&Te.trim()||void 0})&&e.onClose()}return(0,L.jsxs)(`div`,{className:`fixed inset-0 z-50 grid place-items-center p-4`,"aria-labelledby":`createProjectTitle`,"aria-modal":`true`,role:`dialog`,children:[(0,L.jsx)(`button`,{type:`button`,className:`absolute inset-0 cursor-default bg-black/45`,"aria-label":`Close project creation`,onClick:e.onClose}),(0,L.jsxs)(`div`,{className:`relative grid max-h-[90vh] w-full max-w-2xl gap-4 overflow-y-auto rounded-md border bg-background p-5 shadow-2xl`,children:[(0,L.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`strong`,{id:`createProjectTitle`,className:`block text-base`,children:`New ERP project`}),(0,L.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:`Creates a project and links a new or existing customer.`})]}),(0,L.jsx)(z,{type:`button`,variant:`ghost`,size:`icon`,"aria-label":`Close project creation`,onClick:e.onClose,children:(0,L.jsx)(le,{})})]}),(0,L.jsxs)(`div`,{className:`grid gap-3`,children:[(0,L.jsxs)(U,{children:[`Project name *`,(0,L.jsx)(H,{value:t,autoComplete:`off`,maxLength:140,placeholder:`Acme Portal`,onChange:e=>n(e.target.value)})]}),(0,L.jsxs)(`div`,{className:`grid gap-2`,children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Customer`}),(0,L.jsx)(`div`,{className:`grid grid-cols-2 gap-2`,children:[`new`,`existing`].map(e=>(0,L.jsx)(z,{type:`button`,variant:r===e?`default`:`outline`,onClick:()=>i(e),children:e===`new`?`New customer`:`Existing customer`},e))})]}),r===`new`?(0,L.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-2`,children:[(0,L.jsxs)(U,{className:`md:col-span-2`,children:[`Customer name *`,(0,L.jsx)(H,{value:a,autoComplete:`off`,maxLength:140,placeholder:`Acme`,onChange:e=>o(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Account manager`,(0,L.jsx)(H,{value:m,autoComplete:`off`,placeholder:`Search @508.dev user`,onChange:e=>{h(e.target.value),_(``)}})]}),m.trim().length>=2?(0,L.jsx)(`div`,{className:`grid max-h-40 gap-2 overflow-y-auto rounded-md border p-2 md:col-span-2`,children:v.length?v.map(e=>{let t=e.email||e.name||``;return(0,L.jsxs)(`label`,{className:`flex cursor-pointer items-start gap-2 rounded-sm px-2 py-1.5 hover:bg-secondary`,children:[(0,L.jsx)(`input`,{type:`radio`,name:`erpAccountManager`,value:t,checked:g===t,onChange:()=>{_(t),h(t)}}),(0,L.jsxs)(`span`,{className:`grid gap-0.5 text-sm`,children:[(0,L.jsx)(`strong`,{children:e.full_name||t}),(0,L.jsx)(`span`,{className:`text-muted-foreground`,children:t})]})]},t)}):(0,L.jsx)(`span`,{className:`px-2 py-3 text-sm text-muted-foreground`,children:`No enabled @508.dev users found.`})}):null]}):(0,L.jsxs)(`div`,{className:`grid gap-3`,children:[(0,L.jsxs)(U,{children:[`Find customer *`,(0,L.jsx)(H,{value:s,autoComplete:`off`,placeholder:`Search customer`,onChange:e=>c(e.target.value)})]}),(0,L.jsx)(`div`,{className:`grid max-h-48 gap-2 overflow-y-auto rounded-md border p-2`,children:f.length?f.map(e=>{let t=e.name||e.customer_name||``;return(0,L.jsxs)(`label`,{className:`flex cursor-pointer items-start gap-2 rounded-sm px-2 py-1.5 hover:bg-secondary`,children:[(0,L.jsx)(`input`,{type:`radio`,name:`erpCustomer`,value:t,checked:u===t,onChange:()=>d(t)}),(0,L.jsxs)(`span`,{className:`grid gap-0.5 text-sm`,children:[(0,L.jsx)(`strong`,{children:e.customer_name||t}),(0,L.jsx)(`span`,{className:`text-muted-foreground`,children:[t,e.default_currency].filter(Boolean).join(` | `)})]})]},t)}):(0,L.jsx)(`span`,{className:`px-2 py-3 text-sm text-muted-foreground`,children:`Search at least two characters.`})})]}),r===`new`?(0,L.jsxs)(L.Fragment,{children:[(0,L.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-2`,children:[(0,L.jsxs)(U,{className:`md:col-span-2`,children:[`Customer details`,(0,L.jsx)(`textarea`,{value:ee,className:`min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground shadow-xs transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]`,maxLength:2e3,placeholder:`More information`,onChange:e=>te(e.target.value)})]}),(0,L.jsxs)(U,{className:`md:col-span-2`,children:[`Website`,(0,L.jsx)(H,{value:S,autoComplete:`url`,placeholder:`https://example.com`,onChange:e=>C(e.target.value)})]})]}),(0,L.jsxs)(`div`,{className:`grid gap-3`,children:[(0,L.jsxs)(`div`,{className:`flex items-center justify-between gap-3`,children:[(0,L.jsx)(`strong`,{className:`text-sm text-foreground`,children:`Contact`}),(0,L.jsx)(`div`,{className:`grid grid-cols-2 gap-2`,children:[`new`,`existing`].map(e=>(0,L.jsx)(z,{type:`button`,size:`sm`,variant:pe===e?`default`:`outline`,onClick:()=>me(e),children:e===`new`?`New`:`Existing`},e))})]}),pe===`new`?(0,L.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-2`,children:[(0,L.jsxs)(U,{children:[`First name `,qe?`*`:``,(0,L.jsx)(H,{value:_e,autoComplete:`given-name`,onChange:e=>ve(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Last name`,(0,L.jsx)(H,{value:ye,autoComplete:`family-name`,onChange:e=>be(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Email`,(0,L.jsx)(H,{value:xe,type:`email`,autoComplete:`email`,onChange:e=>Se(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Phone`,(0,L.jsx)(H,{value:Ce,type:`tel`,autoComplete:`tel`,onChange:e=>we(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Mobile`,(0,L.jsx)(H,{value:Te,type:`tel`,autoComplete:`tel`,onChange:e=>Ee(e.target.value)})]})]}):(0,L.jsxs)(`div`,{className:`grid gap-3`,children:[(0,L.jsxs)(U,{children:[`Find contact`,(0,L.jsx)(H,{value:E,autoComplete:`off`,placeholder:`Search name or email`,onChange:e=>D(e.target.value)})]}),(0,L.jsx)(`div`,{className:`grid max-h-48 gap-2 overflow-y-auto rounded-md border p-2`,children:k.length?k.map(e=>{let t=e.name||``,n=e.full_name||t,r=[{key:`company`,value:e.company_name},{key:`email`,value:e.email_id},{key:`phone`,value:e.phone},{key:`mobile`,value:e.mobile_no}].filter(e=>!!e.value);return(0,L.jsxs)(`label`,{className:`flex cursor-pointer items-start gap-2 rounded-sm px-2 py-1.5 hover:bg-secondary`,children:[(0,L.jsx)(`input`,{type:`radio`,name:`erpContact`,value:t,checked:O===t,onChange:()=>he(t)}),(0,L.jsxs)(`span`,{className:`grid gap-0.5 text-sm`,children:[(0,L.jsx)(`strong`,{children:(0,L.jsx)(Nn,{value:n,query:E})}),r.length?(0,L.jsx)(`span`,{className:`text-muted-foreground`,children:r.map((e,t)=>(0,L.jsxs)(`span`,{children:[t>0?` | `:``,(0,L.jsx)(Nn,{value:e.value,query:E})]},e.key))}):null]})]},t)}):(0,L.jsx)(`span`,{className:`px-2 py-3 text-sm text-muted-foreground`,children:`Search at least two characters.`})})]})]}),(0,L.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-2`,children:[(0,L.jsx)(`strong`,{className:`text-sm text-foreground md:col-span-2`,children:`Address`}),(0,L.jsxs)(U,{className:`md:col-span-2`,children:[`Address line 1 `,Ke?`*`:``,(0,L.jsx)(H,{value:ne,autoComplete:`address-line1`,onChange:e=>re(e.target.value)})]}),(0,L.jsxs)(U,{className:`md:col-span-2`,children:[`Address line 2`,(0,L.jsx)(H,{value:ie,autoComplete:`address-line2`,onChange:e=>ae(e.target.value)})]}),(0,L.jsxs)(U,{children:[`City`,(0,L.jsx)(H,{value:oe,autoComplete:`address-level2`,onChange:e=>se(e.target.value)})]}),(0,L.jsxs)(U,{children:[`State`,(0,L.jsx)(H,{value:ce,autoComplete:`address-level1`,onChange:e=>w(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Postal code`,(0,L.jsx)(H,{value:de,autoComplete:`postal-code`,onChange:e=>fe(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Country`,(0,L.jsx)(H,{value:ue,autoComplete:`country-name`,onChange:e=>T(e.target.value)})]})]})]}):null,(0,L.jsxs)(`div`,{className:`grid gap-3 rounded-md border p-3`,children:[(0,L.jsx)(z,{type:`button`,variant:`outline`,onClick:()=>Oe(e=>!e),children:De?`Hide advanced`:`Show advanced`}),De?(0,L.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-2`,children:[r===`new`?(0,L.jsxs)(U,{children:[`Billing currency`,(0,L.jsx)(H,{value:b,autoComplete:`off`,maxLength:3,onChange:e=>x(e.target.value.toUpperCase())})]}):null,(0,L.jsxs)(U,{children:[`Cost center`,(0,L.jsx)(Ht,{value:je,onChange:e=>Me(e.target.value),children:ke.map(e=>{let t=e.name||``;return(0,L.jsx)(`option`,{value:t,children:[t,e.company].filter(Boolean).join(` | `)},t)})})]}),(0,L.jsxs)(U,{children:[`Activity type`,(0,L.jsx)(H,{value:Fe?Ne:Ge,autoComplete:`off`,maxLength:140,placeholder:Ge||`Engineering for project`,onChange:e=>{Ie(!0),Pe(e.target.value)}})]})]}):null]})]}),(0,L.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-2`,children:[(0,L.jsx)(z,{type:`button`,variant:`outline`,onClick:e.onClose,children:`Cancel`}),(0,L.jsx)(z,{type:`button`,disabled:!Je,onClick:()=>void Ye(),children:`Create project`})]})]})]})}function Xn(e){let t=e.project,n=t.roster_members||[],[r,i]=(0,l.useState)(``),[a,o]=(0,l.useState)([]),[s,c]=(0,l.useState)(``),[u,d]=(0,l.useState)(``),[f,p]=(0,l.useState)(``),[m,g]=(0,l.useState)(``),_=[t.actual_start_date||t.expected_start_date,t.actual_end_date||t.expected_end_date].filter(Boolean).map(e=>Gn(e)).join(` to `)||`Not set`,v=typeof t.percent_complete==`number`?`${Math.round(t.percent_complete)}%`:`Not set`,b=a.find(e=>e.candidate_id===s),x=r.trim().includes(`@`)?r.trim().length>=5:r.trim().length>=3,ee=!!(u.trim()||f.trim()||m.trim()),te=qn(f),S=qn(m),C=!!((f.trim()||m.trim())&&!u.trim()),ne=!!(u.trim()&&(!f.trim()||!m.trim())),re=!!(f.trim()&&te===void 0)||!!(m.trim()&&S===void 0),ie=C||ne||re||te!==void 0&&te<0||S!==void 0&&S<0,ae=ee&&!ie?{activity_type:u.trim(),billing_rate:te,costing_rate:S}:void 0;(0,l.useEffect)(()=>{if(!e.canWrite)return;let t=r.trim();if(s&&b&&t===(b.email||b.label||``))return;if(s&&c(``),!(t.includes(`@`)?t.length>=5:t.length>=3)){o([]);return}let n=new AbortController,i=window.setTimeout(()=>{K(`/dashboard/api/project-member-candidates?query=${encodeURIComponent(t)}`,{signal:n.signal}).then(e=>o(e)).catch(e=>{e instanceof DOMException&&e.name===`AbortError`||o([])})},500);return()=>{n.abort(),window.clearTimeout(i)}},[r,e.canWrite,b,s]);function se(e){c(e.candidate_id),i(e.email||e.label||e.full_name||r)}return(0,L.jsxs)(L.Fragment,{children:[(0,L.jsxs)(B,{children:[(0,L.jsx)(V,{children:(0,L.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-[auto_minmax(0,1fr)_auto] md:items-start`,children:[(0,L.jsxs)(z,{type:`button`,variant:`outline`,onClick:e.onBack,children:[(0,L.jsx)(h,{}),`Projects`]}),(0,L.jsxs)(`div`,{className:`min-w-0`,children:[(0,L.jsx)(Bt,{children:t.display_name}),(0,L.jsxs)(`div`,{className:`mt-2 flex flex-wrap items-center gap-2 text-sm text-muted-foreground`,children:[(0,L.jsx)(R,{variant:Wn(t.source_status),children:t.source_status||`Unknown`}),t.erpnext_project_id?(0,L.jsx)(`span`,{className:`font-mono`,children:t.erpnext_project_id}):null,t.last_synced_at?(0,L.jsxs)(`span`,{children:[`Synced `,Jt(t.last_synced_at)]}):null]})]}),(0,L.jsxs)(`div`,{className:`flex flex-wrap justify-start gap-2 md:justify-end`,children:[e.canWrite?(0,L.jsxs)(Ht,{className:`w-[160px]`,"aria-label":`Status for ${t.display_name}`,value:t.source_status||``,disabled:e.loading[`project:${t.id}:status`],onChange:n=>e.onUpdateStatus(t.id,n.target.value),children:[(0,L.jsx)(`option`,{value:`Open`,children:`Open`}),(0,L.jsx)(`option`,{value:`Completed`,children:`Completed`}),(0,L.jsx)(`option`,{value:`Cancelled`,children:`Cancelled`})]}):null,t.erpnext_project_url?(0,L.jsxs)(`a`,{className:`inline-flex min-h-9 items-center justify-center gap-2 rounded-md border bg-secondary px-3 text-sm font-semibold`,href:t.erpnext_project_url,target:`_blank`,rel:`noreferrer`,children:[(0,L.jsx)(y,{className:`size-4`}),`ERP project`]}):null,t.customer_erpnext_url?(0,L.jsxs)(`a`,{className:`inline-flex min-h-9 items-center justify-center gap-2 rounded-md border bg-secondary px-3 text-sm font-semibold`,href:t.customer_erpnext_url,target:`_blank`,rel:`noreferrer`,children:[(0,L.jsx)(y,{className:`size-4`}),`ERP customer`]}):null]})]})}),(0,L.jsxs)(Vt,{className:`grid gap-4 md:grid-cols-2 lg:grid-cols-4`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Customer`}),(0,L.jsx)(`strong`,{className:`block`,children:t.customer||`None`})]}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Timeline`}),(0,L.jsx)(`strong`,{className:`block`,children:_})]}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Progress`}),(0,L.jsx)(`strong`,{className:`block`,children:v})]}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Linked Gigs`}),(0,L.jsx)(`strong`,{className:`block`,children:t.linked_engagement_count||0})]}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`ERP Type`}),(0,L.jsx)(`div`,{className:`mt-1`,children:t.project_type?(0,L.jsx)(R,{variant:`neutral`,children:t.project_type}):(0,L.jsx)(`strong`,{className:`block`,children:`Not set`})})]}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`ERP Modified`}),(0,L.jsx)(`strong`,{className:`block`,children:Jt(t.source_modified_at)})]}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Cache ID`}),(0,L.jsx)(`strong`,{className:`block break-all font-mono text-xs`,children:t.id})]})]})]}),(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`Project roster`}),(0,L.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:n.length?`${n.length} synced ERP user${n.length===1?``:`s`}`:`No ERP roster`})]}),e.canWrite?(0,L.jsxs)(Vt,{className:`grid gap-3 border-b md:grid-cols-[minmax(260px,1fr)_minmax(180px,.7fr)_minmax(130px,.45fr)_minmax(130px,.45fr)_auto_auto] md:items-end`,children:[(0,L.jsxs)(`div`,{className:`relative`,children:[(0,L.jsxs)(U,{children:[`Person search`,(0,L.jsx)(H,{value:r,autoComplete:`off`,placeholder:`Search @508.dev person`,onChange:e=>i(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),a.length===1&&se(a[0]))}})]}),x&&!s?(0,L.jsx)(`div`,{className:`absolute z-20 mt-1 max-h-64 w-full overflow-auto rounded-md border bg-background shadow-lg`,children:a.length?a.map(e=>(0,L.jsxs)(`button`,{type:`button`,className:`grid w-full gap-0.5 px-3 py-2 text-left hover:bg-secondary focus:bg-secondary focus:outline-none`,onClick:()=>se(e),children:[(0,L.jsx)(`span`,{className:`truncate text-sm font-bold`,children:e.label||e.full_name||e.email||`Person`}),(0,L.jsx)(`span`,{className:`truncate text-xs text-muted-foreground`,children:[e.email,e.sources?.join(`, `)].filter(Boolean).join(` | `)})]},e.candidate_id)):(0,L.jsx)(`div`,{className:`px-3 py-2 text-sm text-muted-foreground`,children:`No verified @508.dev results`})}):null]}),(0,L.jsxs)(U,{children:[`Activity Type`,(0,L.jsx)(H,{value:u,autoComplete:`off`,placeholder:`Optional rate step`,onChange:e=>d(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Billing rate`,(0,L.jsx)(H,{value:f,inputMode:`decimal`,autoComplete:`off`,placeholder:`USD/hr`,onChange:e=>p(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Costing rate`,(0,L.jsx)(H,{value:m,inputMode:`decimal`,autoComplete:`off`,placeholder:`USD/hr`,onChange:e=>g(e.target.value)})]}),(0,L.jsxs)(z,{type:`button`,variant:`outline`,disabled:e.loading[`project:${t.id}:user`]||!s||!b?.email||ie,onClick:()=>void e.onAddUser(t.id,b?.email||r,s,ae).then(e=>{e&&(i(``),o([]),c(``),d(``),p(``),g(``))}),children:[(0,L.jsx)(ce,{}),`Add ERP user`]}),(0,L.jsxs)(z,{type:`button`,variant:`outline`,disabled:e.loading[`project:${t.id}:historical`]||!r.trim(),onClick:()=>void e.onAddHistoricalMember(t.id,r).then(e=>{e&&i(``)}),children:[(0,L.jsx)(ce,{}),`Add historical`]})]}):null,(0,L.jsx)(`div`,{className:`overflow-x-auto`,children:(0,L.jsxs)(Ut,{className:`min-w-[860px]`,"aria-label":`Project roster`,children:[(0,L.jsx)(Wt,{children:(0,L.jsxs)(Kt,{children:[(0,L.jsx)(W,{children:`Name`}),(0,L.jsx)(W,{children:`Email`}),(0,L.jsx)(W,{children:`ERP user`}),(0,L.jsx)(W,{children:`Links`}),(0,L.jsx)(W,{children:`Source`}),(0,L.jsx)(W,{children:`Last seen`}),e.canWrite?(0,L.jsx)(W,{children:`Actions`}):null]})}),(0,L.jsx)(Gt,{children:n.length?n.map(n=>{let r=Kn(n),i=n.source_user_id||n.email||``,a=n.roster_kind===`historical`||n.source===`manual`;return(0,L.jsxs)(Kt,{children:[(0,L.jsx)(G,{children:(0,L.jsx)(`strong`,{children:n.full_name||n.email||n.source_user_id})}),(0,L.jsx)(G,{children:n.email||`None`}),(0,L.jsx)(G,{className:`font-mono text-xs`,children:n.erpnext_user_url?(0,L.jsxs)(`a`,{className:`inline-flex items-center gap-1 font-semibold text-primary underline-offset-4 hover:underline`,href:n.erpnext_user_url,target:`_blank`,rel:`noreferrer`,children:[n.source_user_id||`ERP user`,(0,L.jsx)(y,{className:`size-3.5`})]}):n.source_user_id||`Unknown`}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`flex flex-wrap gap-2`,children:[n.supplier_erpnext_url?(0,L.jsxs)(`a`,{className:`inline-flex items-center gap-1 font-semibold text-primary underline-offset-4 hover:underline`,href:n.supplier_erpnext_url,target:`_blank`,rel:`noreferrer`,children:[`Supplier`,(0,L.jsx)(y,{className:`size-3.5`})]}):null,n.crm_contact_id&&e.crmContactUrl(n.crm_contact_id)?(0,L.jsxs)(`a`,{className:`inline-flex items-center gap-1 font-semibold text-primary underline-offset-4 hover:underline`,href:e.crmContactUrl(n.crm_contact_id),target:`_blank`,rel:`noreferrer`,children:[`CRM`,(0,L.jsx)(y,{className:`size-3.5`})]}):null,!n.supplier_erpnext_url&&!(n.crm_contact_id&&e.crmContactUrl(n.crm_contact_id))?(0,L.jsx)(`span`,{className:`text-muted-foreground`,children:`None`}):null]})}),(0,L.jsx)(G,{children:n.roster_kind||n.source||`ERP`}),(0,L.jsx)(G,{children:Jt(n.last_seen_at)}),e.canWrite?(0,L.jsx)(G,{children:(0,L.jsxs)(z,{type:`button`,variant:`outline`,size:`sm`,disabled:!i||e.loading[`project:${t.id}:${a?`historical`:`user`}`],onClick:()=>{window.confirm(`Remove ${r} from this project roster?`)&&(a?e.onRemoveHistoricalMember(t.id,i):e.onRemoveUser(t.id,i))},children:[(0,L.jsx)(oe,{}),`Remove`]})}):null]},`${n.source||``}:${n.source_user_id||n.email}`)}):(0,L.jsx)(Kt,{children:(0,L.jsx)(G,{colSpan:e.canWrite?7:6,className:`text-sm text-muted-foreground`,children:`No roster rows have been synced for this project.`})})})]})})]})]})}function Zn(e){let t=e.gigs.reduce((t,n)=>(t.total+=1,t.applications+=Number(n.application_count||0),t.interested+=Number(n.interested_count||0),Un(n,e.staleDays)!==null&&(t.stale+=1),t),{total:0,applications:0,interested:0,stale:0}),n=(0,L.jsxs)(B,{className:`grid gap-3 p-4 md:grid-cols-[minmax(140px,.75fr)_minmax(220px,1.25fr)_auto_auto_auto] md:items-end`,children:[(0,L.jsxs)(U,{children:[`Status`,(0,L.jsxs)(Ht,{id:`gigStatus`,value:e.status,onChange:t=>e.setStatus(t.target.value),children:[(0,L.jsx)(`option`,{value:``,children:`Any status`}),zn.map(e=>(0,L.jsx)(`option`,{value:e,children:Vn(e)},e))]})]}),(0,L.jsxs)(U,{children:[`Search gigs`,(0,L.jsx)(H,{id:`gigQuery`,value:e.query,autoComplete:`off`,placeholder:`Title, gig text, #tag, @poster`,onChange:t=>e.setQuery(t.target.value),onKeyDown:t=>t.key===`Enter`&&e.onRefresh()})]}),e.canIncludeHistorical?(0,L.jsxs)(`label`,{className:`flex min-h-9 items-center gap-2 text-xs font-bold text-muted-foreground`,children:[(0,L.jsx)(`input`,{type:`checkbox`,checked:e.includeHistorical,onChange:t=>e.setIncludeHistorical(t.target.checked)}),`Include historical`]}):null,(0,L.jsxs)(z,{id:`searchGigs`,type:`button`,onClick:e.onRefresh,disabled:e.loading.gigs,children:[(0,L.jsx)(ne,{}),`Search`]}),(0,L.jsxs)(z,{id:`refreshGigs`,type:`button`,variant:`outline`,onClick:e.onRefresh,disabled:e.loading.gigs,children:[(0,L.jsx)(C,{}),`Refresh`]}),e.gigs.length>=e.limit?(0,L.jsx)(z,{type:`button`,variant:`outline`,onClick:()=>e.setLimit(Math.min(e.limit+100,500)),disabled:e.loading.gigs||e.limit>=500,children:`Load more`}):null]}),r=e.selectedGigId?e.loading[`gig:${e.selectedGigId}:detail`]:!1;return e.selectedGigId&&!e.selectedGig&&(e.loading.gigs||r)?(0,L.jsxs)(L.Fragment,{children:[n,(0,L.jsxs)(B,{children:[(0,L.jsx)(V,{children:(0,L.jsx)(Bt,{children:`Gig detail`})}),(0,L.jsx)(Vt,{className:`text-sm text-muted-foreground`,children:`Loading gig.`})]})]}):e.selectedGigId&&!e.selectedGig?(0,L.jsxs)(L.Fragment,{children:[n,(0,L.jsxs)(B,{children:[(0,L.jsx)(V,{children:(0,L.jsx)(Bt,{children:`Gig detail`})}),(0,L.jsxs)(Vt,{className:`grid gap-3`,children:[(0,L.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`This gig is not in the current result set. Clear filters or refresh the gig list.`}),(0,L.jsxs)(z,{type:`button`,variant:`outline`,onClick:e.onCloseGig,children:[(0,L.jsx)(h,{}),`Back to gigs`]})]})]})]}):e.selectedGig?(0,L.jsxs)(L.Fragment,{children:[n,(0,L.jsx)(er,{gig:e.selectedGig,loading:e.loading,canWrite:e.canWrite,crmContactUrl:e.crmContactUrl,crmAttachmentUrl:e.crmAttachmentUrl,staleDays:e.staleDays,onBack:e.onCloseGig,onUpdateStatus:e.onUpdateStatus,onAddApplication:e.onAddApplication,onUpdateApplicationStatus:e.onUpdateApplicationStatus})]}):(0,L.jsxs)(L.Fragment,{children:[n,(0,L.jsxs)(`section`,{className:`grid gap-3 md:grid-cols-4`,"aria-label":`Gig summary`,children:[(0,L.jsx)(jn,{id:`gigMetricTotal`,label:`Gigs`,value:t.total}),(0,L.jsx)(jn,{id:`gigMetricCandidates`,label:`Candidates`,value:t.applications}),(0,L.jsx)(jn,{id:`gigMetricInterested`,label:`Interested`,value:t.interested}),(0,L.jsx)(jn,{id:`gigMetricStale`,label:`Stale recruiting`,value:t.stale})]}),(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`Discord gigs`}),(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center justify-end gap-2`,children:[(0,L.jsxs)(z,{type:`button`,variant:`ghost`,size:`sm`,onClick:()=>e.onSort(`activity`),"aria-label":`Sort gigs by activity`,children:[`Activity`,` `,e.sort.key===`activity`?e.sort.direction===`asc`?`↑`:`↓`:``]}),(0,L.jsxs)(z,{type:`button`,variant:`ghost`,size:`sm`,onClick:()=>e.onSort(`title`),"aria-label":`Sort gigs by title`,children:[`Title `,e.sort.key===`title`?e.sort.direction===`asc`?`↑`:`↓`:``]}),(0,L.jsx)(`span`,{id:`gigsStatus`,className:`text-sm text-muted-foreground`,children:e.loading.gigs?`Loading`:`${e.gigs.length} shown`})]})]}),(0,L.jsx)(Mn,{hidden:e.gigs.length!==0,children:`No gigs match this view.`}),(0,L.jsx)(`div`,{id:`gigsBody`,className:I(`grid gap-3 p-4`,e.gigs.length===0&&`hidden`),children:e.gigs.map(t=>(0,L.jsx)(Qn,{gig:t,loading:e.loading,canWrite:e.canWrite,staleDays:e.staleDays,onOpenGig:e.onOpenGig,onUpdateStatus:e.onUpdateStatus},t.id))})]})]})}function Qn({gig:e,loading:t,canWrite:n,onOpenGig:r,onUpdateStatus:i,staleDays:a}){let o=Array.isArray(e.applications)?e.applications:[],s=e.status===`recruiting`,c=e.discord_guild_id&&e.discord_thread_id?`https://discord.com/channels/${encodeURIComponent(e.discord_guild_id)}/${encodeURIComponent(e.discord_thread_id)}`:``,l=Un(e,a);return(0,L.jsxs)(`article`,{className:I(`grid gap-4 rounded-md border bg-background p-4 lg:grid-cols-[minmax(0,1fr)_220px_180px] lg:items-start`,!s&&`border-l-4 border-l-muted-foreground/60 bg-secondary/45`),children:[(0,L.jsxs)(`div`,{className:`min-w-0`,children:[(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,L.jsx)(`a`,{className:`text-base font-extrabold text-primary`,href:`/dashboard/gigs/${encodeURIComponent(e.id)}`,onClick:t=>{t.preventDefault(),r(e.id)},children:e.title||`Untitled gig`}),(0,L.jsx)(R,{variant:e.status===`filled`?`succeeded`:e.status===`lost`?`failed`:s?`queued`:`neutral`,children:e.status_label||Vn(e.status)}),s?null:(0,L.jsx)(R,{variant:`neutral`,children:`Not recruiting`}),l===null?null:(0,L.jsxs)(R,{variant:`running`,children:[l,`d stale`]})]}),(0,L.jsxs)(`div`,{className:`mt-2 flex flex-wrap gap-1.5`,children:[e.posting_type?(0,L.jsx)(R,{variant:`neutral`,children:Vn(e.posting_type)}):null,e.discord_channel_name?(0,L.jsxs)(R,{variant:`neutral`,children:[`#`,e.discord_channel_name]}):null,(e.required_skills||[]).slice(0,5).map(e=>(0,L.jsx)(R,{variant:`queued`,children:e},e)),(e.preferred_skills||[]).slice(0,3).map(e=>(0,L.jsx)(R,{variant:`neutral`,children:e},e))]}),(0,L.jsxs)(`div`,{className:`mt-3 flex flex-wrap gap-x-4 gap-y-1 text-sm text-muted-foreground`,children:[(0,L.jsxs)(`span`,{children:[`Activity `,Jt(Hn(e))||`unknown`]}),(0,L.jsxs)(`span`,{children:[`Posted `,Jt(e.posted_at)||`unknown`]}),c?(0,L.jsx)(`a`,{className:`font-extrabold text-primary`,href:c,target:`_blank`,rel:`noreferrer`,children:`Open Discord thread`}):null]})]}),(0,L.jsxs)(`div`,{className:`grid grid-cols-2 gap-2 text-sm lg:grid-cols-1`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`block text-xs font-bold text-muted-foreground`,children:`People`}),(0,L.jsx)(`strong`,{children:e.application_count||o.length}),(0,L.jsxs)(`span`,{className:`ml-2 text-muted-foreground`,children:[Number(e.interested_count||0),` interested`]})]}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`block text-xs font-bold text-muted-foreground`,children:`Top candidates`}),(0,L.jsx)(`span`,{className:`text-muted-foreground`,children:o.slice(0,3).map(e=>$n(e)).join(`, `)||`None yet`})]})]}),(0,L.jsxs)(`div`,{className:`grid gap-2`,children:[n?(0,L.jsx)(Ht,{"aria-label":`Status for ${e.title||`gig`}`,value:e.status,disabled:t[`gig:${e.id}:status`],onChange:t=>i(e.id,t.target.value),children:zn.map(e=>(0,L.jsx)(`option`,{value:e,children:Vn(e)},e))}):null,(0,L.jsx)(z,{type:`button`,onClick:()=>r(e.id),children:`Manage people`})]})]})}function $n(e){return e.name||e.email_508||e.discord_username||(typeof e.evaluation?.discord_username==`string`?e.evaluation.discord_username:``)||`Candidate`}function er({gig:e,loading:t,canWrite:n,crmContactUrl:r,crmAttachmentUrl:i,staleDays:a,onBack:o,onUpdateStatus:s,onAddApplication:c,onUpdateApplicationStatus:u}){let[d,f]=(0,l.useState)(``),p=Array.isArray(e.applications)?e.applications:[],m=e.status===`recruiting`,g=e.discord_guild_id&&e.discord_thread_id?`https://discord.com/channels/${encodeURIComponent(e.discord_guild_id)}/${encodeURIComponent(e.discord_thread_id)}`:``,_=Un(e,a);return(0,L.jsxs)(`div`,{className:`grid gap-5`,children:[(0,L.jsxs)(B,{className:I(!m&&`border-l-4 border-l-muted-foreground/60 bg-secondary/35`),children:[(0,L.jsxs)(V,{className:`items-start`,children:[(0,L.jsxs)(`div`,{className:`grid gap-2`,children:[(0,L.jsxs)(z,{type:`button`,variant:`ghost`,size:`sm`,className:`w-fit`,onClick:o,children:[(0,L.jsx)(h,{}),`Back to gigs`]}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(Bt,{className:`text-xl`,children:e.title||`Untitled gig`}),(0,L.jsxs)(`div`,{className:`mt-2 flex flex-wrap gap-1.5`,children:[(0,L.jsx)(R,{variant:e.status===`filled`?`succeeded`:e.status===`lost`?`failed`:m?`queued`:`neutral`,children:e.status_label||Vn(e.status)}),m?null:(0,L.jsx)(R,{variant:`neutral`,children:`Not recruiting`}),_===null?null:(0,L.jsxs)(R,{variant:`running`,children:[_,`d stale`]}),e.posting_type?(0,L.jsx)(R,{variant:`neutral`,children:Vn(e.posting_type)}):null,e.discord_channel_name?(0,L.jsxs)(R,{variant:`neutral`,children:[`#`,e.discord_channel_name]}):null,(e.required_skills||[]).map(e=>(0,L.jsx)(R,{variant:`queued`,children:e},e)),(e.preferred_skills||[]).map(e=>(0,L.jsx)(R,{variant:`neutral`,children:e},e))]})]})]}),(0,L.jsxs)(`div`,{className:`grid min-w-[190px] gap-2`,children:[n?(0,L.jsxs)(U,{children:[`Gig status`,(0,L.jsx)(Ht,{"aria-label":`Status for ${e.title||`gig`}`,value:e.status,disabled:t[`gig:${e.id}:status`],onChange:t=>s(e.id,t.target.value),children:zn.map(e=>(0,L.jsx)(`option`,{value:e,children:Vn(e)},e))})]}):null,g?(0,L.jsxs)(`a`,{className:`inline-flex min-h-9 items-center justify-center gap-2 rounded-md border bg-secondary px-3 text-sm font-semibold`,href:g,target:`_blank`,rel:`noreferrer`,children:[(0,L.jsx)(y,{className:`size-4`}),`Discord thread`]}):null]})]}),(0,L.jsxs)(Vt,{className:`grid gap-4 lg:grid-cols-[1fr_1fr_1fr]`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Activity`}),(0,L.jsx)(`strong`,{className:`block`,children:Jt(Hn(e))||`unknown`}),(0,L.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[`Posted `,Jt(e.posted_at)||`unknown`]})]}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`People`}),(0,L.jsx)(`strong`,{className:`block`,children:e.application_count||p.length}),(0,L.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[Number(e.interested_count||0),` interested`]})]}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Discord`}),(0,L.jsx)(`strong`,{className:`block`,children:e.discord_channel_name||`No channel`}),(0,L.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:e.discord_thread_id?`Thread ${e.discord_thread_id}`:`No thread`})]})]})]}),(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`People`}),(0,L.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[p.length,` candidate`,p.length===1?``:`s`]})]}),n?(0,L.jsxs)(`form`,{className:`grid gap-2 border-t p-4 md:grid-cols-[minmax(220px,1fr)_auto]`,onSubmit:t=>{t.preventDefault(),c(e.id,d).then(e=>{e&&f(``)})},children:[(0,L.jsxs)(U,{className:`min-w-0`,children:[`CRM profile`,(0,L.jsx)(H,{value:d,onChange:e=>f(e.target.value),placeholder:`https://crm.508.dev/#Contact/view/...`,"aria-label":`CRM profile for candidate`})]}),(0,L.jsxs)(z,{type:`submit`,className:`self-end`,disabled:t[`gig:${e.id}:addCandidate`]||!d.trim(),children:[(0,L.jsx)(se,{}),`Add candidate`]})]}):null,(0,L.jsx)(Mn,{hidden:p.length!==0,children:`No suggested or interested people yet.`}),(0,L.jsx)(`div`,{className:I(`grid gap-3 p-4`,p.length===0&&`hidden`),children:p.map(a=>(0,L.jsx)(tr,{gigId:e.id,application:a,loading:t,canWrite:n,crmContactUrl:r,crmAttachmentUrl:i,onUpdateApplicationStatus:u},a.id))})]})]})}function tr({gigId:e,application:t,loading:n,canWrite:r,crmContactUrl:i,crmAttachmentUrl:a,onUpdateApplicationStatus:o}){let s=$n(t),c=i(t.crm_contact_id),l=a(t.latest_resume_id),u=typeof t.fit_score==`number`?`${Math.round(t.fit_score)}/100`:typeof t.match_score==`number`?t.match_score.toFixed(1):``,d=typeof t.evaluation?.llm_summary==`string`?t.evaluation.llm_summary:``;return(0,L.jsxs)(`div`,{className:`grid gap-2 rounded-md border bg-background p-2`,children:[(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[c?(0,L.jsx)(`a`,{className:`font-extrabold text-primary`,href:c,target:`_blank`,rel:`noopener noreferrer`,children:s}):(0,L.jsx)(`strong`,{children:s}),(0,L.jsx)(R,{variant:t.status===`interested`?`succeeded`:`neutral`,children:Vn(t.status)}),(0,L.jsx)(R,{variant:`neutral`,children:Vn(t.source||`manual_add`)}),u?(0,L.jsxs)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:[`Fit `,u]}):null,c?(0,L.jsx)(`a`,{className:`text-xs font-extrabold text-primary`,href:c,target:`_blank`,rel:`noopener noreferrer`,"aria-label":`Open ${s} CRM profile`,children:`CRM profile`}):null,l?(0,L.jsx)(`a`,{className:`text-xs font-extrabold text-primary`,href:l,target:`_blank`,rel:`noopener noreferrer`,children:`Resume`}):null]}),d?(0,L.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:d}):null,r?(0,L.jsx)(Ht,{"aria-label":`Candidate status for ${s}`,value:t.status||`suggested`,disabled:n[`application:${t.id}:status`],onChange:n=>o(e,t.id,n.target.value),children:Bn.map(e=>(0,L.jsx)(`option`,{value:e,children:Vn(e)},e))}):null]})}function nr(e){let t=mn[e.peopleFilterKind]?.options||[];return(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`People lookup`}),(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center justify-end gap-2`,children:[e.canSync?(0,L.jsxs)(z,{id:`syncPeople`,"data-permission":`people:sync`,type:`button`,onClick:e.onSync,disabled:e.loading.syncPeople,children:[(0,L.jsx)(C,{}),`Sync people`]}):null,e.crmBaseUrl?(0,L.jsx)(`a`,{id:`crmHomeLink`,className:`text-sm font-extrabold text-primary`,href:e.crmBaseUrl,target:`_blank`,rel:`noreferrer`,children:`Open CRM`}):null,(0,L.jsx)(`span`,{id:`peopleStatus`,className:`text-sm text-muted-foreground`,children:e.loading.people?`Loading`:`${e.people.length} shown`})]})]}),(0,L.jsxs)(`div`,{className:`grid gap-3 border-b p-4 md:grid-cols-[minmax(0,1fr)_auto]`,children:[(0,L.jsxs)(U,{children:[`Search CRM people cache`,(0,L.jsx)(H,{id:`peopleQuery`,value:e.peopleQuery,autoComplete:`off`,placeholder:`Name, email, CRM id, Discord, resume`,onChange:t=>e.setPeopleQuery(t.target.value),onKeyDown:t=>{t.key===`Enter`&&e.onSearch()}})]}),(0,L.jsxs)(z,{id:`searchPeople`,type:`button`,onClick:e.onSearch,disabled:e.loading.people,children:[(0,L.jsx)(ne,{}),`Search`]})]}),(0,L.jsxs)(`div`,{className:`grid gap-3 border-b bg-background p-4 md:grid-cols-[minmax(120px,.7fr)_minmax(150px,1fr)_minmax(150px,1fr)_auto]`,children:[(0,L.jsxs)(U,{children:[`Member`,(0,L.jsxs)(Ht,{id:`peopleMember`,value:e.peopleMember,onChange:t=>e.setPeopleMember(t.target.value),children:[(0,L.jsx)(`option`,{value:``,children:`Any`}),(0,L.jsx)(`option`,{value:`true`,children:`Member`}),(0,L.jsx)(`option`,{value:`false`,children:`Not member`})]})]}),(0,L.jsxs)(U,{children:[`Add filter`,(0,L.jsx)(Ht,{id:`peopleFilterKind`,value:e.peopleFilterKind,disabled:e.peopleFilterKeys.length===0,onChange:t=>e.setPeopleFilterKind(t.target.value),children:e.peopleFilterKeys.map(e=>(0,L.jsx)(`option`,{value:e,children:mn[e].label},e))})]}),(0,L.jsxs)(U,{children:[`Value`,(0,L.jsx)(Ht,{id:`peopleFilterValue`,value:e.peopleFilterValue,onChange:t=>e.setPeopleFilterValue(t.target.value),children:t.map(([e,t])=>(0,L.jsx)(`option`,{value:e,children:t},e))})]}),(0,L.jsx)(z,{id:`addPeopleFilter`,type:`button`,onClick:e.addFilter,disabled:e.peopleFilterKeys.length===0,children:`Add filter`}),(0,L.jsx)(`div`,{id:`activePeopleFilters`,className:`md:col-span-4`,children:(0,L.jsx)(Rn,{filters:e.peopleFilters,onRemove:e.removeFilter})})]}),(0,L.jsx)(Mn,{hidden:e.people.length!==0,children:`No people match this lookup.`}),(0,L.jsx)(`div`,{className:`overflow-x-auto`,children:(0,L.jsxs)(Ut,{id:`peopleTable`,className:I(`min-w-[900px]`,e.people.length===0&&`hidden`),"aria-label":`People lookup results`,children:[(0,L.jsx)(Wt,{children:(0,L.jsxs)(Kt,{children:[(0,L.jsx)(q,{className:`w-[27%]`,label:`Name`,scope:`people`,sort:e.sort,sortKey:`name`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[28%]`,label:`Status`,scope:`people`,sort:e.sort,sortKey:`status`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[20%]`,label:`Discord`,scope:`people`,sort:e.sort,sortKey:`discord`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[25%]`,label:`Resume / skills`,scope:`people`,sort:e.sort,sortKey:`resume`,onSort:(t,n)=>e.onSort(n)})]})}),(0,L.jsx)(Gt,{id:`peopleBody`,children:e.people.map(t=>{let n=t.name||t.email_508||t.email||`CRM contact`,r=e.crmContactUrl(t.crm_contact_id),i=t.profile_status||{},a=Number(i.skills_count||0),o=e.crmAttachmentUrl(t.latest_resume_id);return(0,L.jsxs)(Kt,{children:[(0,L.jsxs)(G,{children:[r?(0,L.jsx)(`a`,{className:`font-extrabold text-primary`,href:r,target:`_blank`,rel:`noreferrer`,"aria-label":`Open ${n} in CRM`,children:n}):(0,L.jsx)(`strong`,{children:n}),(0,L.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:[t.email_508||t.email,t.contact_type].filter(Boolean).join(` | `)})]}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`flex flex-wrap gap-1.5`,children:[i.crm_active?null:(0,L.jsx)(R,{variant:`missing`,children:t.sync_status||`CRM sync issue`}),(0,L.jsx)(R,{variant:i.is_member?`succeeded`:`missing`,children:i.is_member?`Member`:`Missing Member`}),(0,L.jsx)(R,{variant:i.discord_linked?`succeeded`:`missing`,children:i.discord_linked?`Discord`:`Missing Discord`}),(0,L.jsx)(R,{variant:i.email_508?`succeeded`:`missing`,children:i.email_508?`508 email`:`Missing 508 email`}),i.latest_resume?null:(0,L.jsx)(R,{variant:`missing`,children:`Missing Resume`})]})}),(0,L.jsx)(G,{children:[t.discord_username,t.discord_user_id].filter(Boolean).join(` | `)||`Not linked`}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5`,children:[o?(0,L.jsx)(`a`,{className:`inline-flex min-h-7 items-center rounded-md border bg-secondary px-2 text-xs font-extrabold`,href:o,target:`_blank`,rel:`noreferrer`,"aria-label":`Open ${n} resume`,children:`Resume`}):(0,L.jsx)(`span`,{children:t.latest_resume_name||t.latest_resume_id||`No resume`}),(0,L.jsx)(R,{variant:a>0?`succeeded`:`missing`,children:a>0?`Skills parsed`:`Skills not parsed`})]})})]},t.crm_contact_id||n)})})]})})]})}function rr(e){let t=e.status?.latest_job||null,n=e.status?.active_suppressed_email_count,r=e.status?.active_suppression_count,i=wn(t),a=e.providerFilter?[...new Set([...e.providerOptions,e.providerFilter])].sort((e,t)=>e.localeCompare(t)):e.providerOptions,o=e.suppressions.reduce((e,t)=>{let n=t.source_provider||`unknown`;return e[n]=[...e[n]||[],t],e},{}),s=Object.entries(o).sort(([e],[t])=>e.localeCompare(t));return(0,L.jsxs)(L.Fragment,{children:[(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`Newsletter sync`}),(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center justify-end gap-2`,children:[(0,L.jsxs)(z,{id:`refreshNewsletter`,type:`button`,variant:`outline`,onClick:e.onRefresh,disabled:e.loading.newsletterStatus||e.loading.newsletterSuppressions,children:[(0,L.jsx)(C,{}),`Refresh`]}),e.canSync?(0,L.jsxs)(z,{id:`syncNewsletters`,"data-permission":`people:sync`,type:`button`,onClick:e.onSync,disabled:e.loading.syncNewsletters,children:[(0,L.jsx)(C,{}),`Sync now`]}):null]})]}),(0,L.jsxs)(Vt,{children:[(0,L.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-4`,children:[(0,L.jsxs)(`div`,{className:`rounded-md border bg-background p-4`,children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Scheduler`}),(0,L.jsx)(`strong`,{id:`newsletterScheduler`,className:`block text-2xl`,children:e.status?.scheduler_enabled?`Enabled`:`Disabled`})]}),(0,L.jsxs)(`div`,{className:`rounded-md border bg-background p-4`,children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Interval`}),(0,L.jsx)(`strong`,{id:`newsletterInterval`,className:`block text-2xl`,children:Cn(e.status?.interval_seconds)})]}),(0,L.jsxs)(`div`,{className:`rounded-md border bg-background p-4`,children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Suppressed emails`}),(0,L.jsx)(`strong`,{id:`newsletterSuppressedEmails`,className:`block text-2xl`,children:n??`Loading`})]}),(0,L.jsxs)(`div`,{className:`rounded-md border bg-background p-4`,children:[(0,L.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Suppression rows`}),(0,L.jsx)(`strong`,{id:`newsletterSuppressionRows`,className:`block text-2xl`,children:r??`Loading`})]})]}),(0,L.jsxs)(`div`,{className:`mt-4 grid gap-2 rounded-md border bg-secondary p-3 text-sm`,children:[(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,L.jsx)(`span`,{className:`font-extrabold`,children:`Latest sync`}),t?(0,L.jsx)(R,{variant:t.status||`neutral`,children:t.status}):(0,L.jsx)(R,{variant:`neutral`,children:`No job found`})]}),t?(0,L.jsxs)(L.Fragment,{children:[(0,L.jsxs)(`div`,{className:`text-muted-foreground`,children:[t.type,` | updated `,Jt(t.updated_at),` | attempts`,` `,t.attempts,`/`,t.max_attempts]}),t.last_error?(0,L.jsx)(`div`,{className:`text-red-700 dark:text-red-300`,children:t.last_error}):null,(0,L.jsx)(`div`,{className:`grid gap-2 md:grid-cols-2`,children:i.length?i.map(([e,t])=>{let n=t.statuses||{},r=Object.entries(n).sort(([e],[t])=>e.localeCompare(t)),i=t.synced??t.would_sync??0;return(0,L.jsxs)(`div`,{className:`grid gap-2 rounded-md border bg-background p-3`,children:[(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-2`,children:[(0,L.jsx)(`strong`,{children:e}),(0,L.jsxs)(`div`,{className:`flex flex-wrap gap-1`,children:[(0,L.jsxs)(R,{variant:`succeeded`,children:[i,` synced`]}),(0,L.jsxs)(R,{variant:`neutral`,children:[t.skipped||0,` skipped`]}),t.failed?(0,L.jsxs)(R,{variant:`failed`,children:[t.failed,` failed`]}):null]})]}),r.length?(0,L.jsx)(`div`,{className:`flex flex-wrap gap-1`,children:r.map(([e,t])=>(0,L.jsxs)(R,{variant:`neutral`,children:[e.replaceAll(`_`,` `),`: `,t]},e))}):null]},e)}):(0,L.jsx)(`div`,{className:`rounded-md border bg-background p-3 text-muted-foreground`,children:`No provider status details recorded for the latest sync.`})}),t.result?(0,L.jsx)(`pre`,{className:`max-h-40 overflow-auto rounded-md bg-background p-3 text-xs`,children:Xt(t.result)}):null]}):(0,L.jsx)(`div`,{className:`text-muted-foreground`,children:`No 508 members newsletter sync job was found in the last 90 days.`})]})]})]}),(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`Newsletter suppressions`}),(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center justify-end gap-3`,children:[(0,L.jsxs)(U,{className:`min-w-44`,children:[`Provider`,(0,L.jsxs)(Ht,{id:`newsletterProviderFilter`,value:e.providerFilter,onChange:t=>e.onProviderFilterChange(t.target.value),children:[(0,L.jsx)(`option`,{value:``,children:`All providers`}),a.map(e=>(0,L.jsx)(`option`,{value:e,children:e},e))]})]}),(0,L.jsx)(`span`,{id:`newsletterSuppressionsStatus`,className:`text-sm text-muted-foreground`,children:e.loading.newsletterSuppressions?`Loading`:`${e.suppressions.length} shown`})]})]}),(0,L.jsx)(Mn,{hidden:e.suppressions.length!==0,children:`No active newsletter suppressions recorded.`}),(0,L.jsx)(`div`,{className:I(`grid gap-4`,e.suppressions.length===0&&`hidden`),children:s.map(([t,n])=>(0,L.jsxs)(`section`,{className:`grid gap-2`,"aria-label":`${t} suppressions`,children:[(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2 px-4 pt-4`,children:[(0,L.jsx)(R,{variant:`neutral`,children:t}),(0,L.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[n.length,` suppression`,n.length===1?``:`s`]})]}),(0,L.jsx)(`div`,{className:`overflow-x-auto`,children:(0,L.jsxs)(Ut,{id:`newsletterSuppressionsTable-${t}`,className:`min-w-[900px]`,"aria-label":`${t} newsletter suppressions`,children:[(0,L.jsx)(Wt,{children:(0,L.jsxs)(Kt,{children:[(0,L.jsx)(q,{className:`w-[34%]`,label:`Email`,scope:`newsletter`,sort:e.sort,sortKey:`email`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[14%]`,label:`Source`,scope:`newsletter`,sort:e.sort,sortKey:`source_provider`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[22%]`,label:`Reason`,scope:`newsletter`,sort:e.sort,sortKey:`reason`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[15%]`,label:`First seen`,scope:`newsletter`,sort:e.sort,sortKey:`first_seen_at`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[15%]`,label:`Last seen`,scope:`newsletter`,sort:e.sort,sortKey:`last_seen_at`,onSort:(t,n)=>e.onSort(n)})]})}),(0,L.jsx)(Gt,{id:`newsletterSuppressionsBody-${t}`,children:n.map(e=>(0,L.jsxs)(Kt,{children:[(0,L.jsx)(G,{className:`font-mono text-sm`,children:e.email}),(0,L.jsx)(G,{children:(0,L.jsx)(R,{variant:`neutral`,children:e.source_provider})}),(0,L.jsx)(G,{children:e.reason.replaceAll(`_`,` `)}),(0,L.jsx)(G,{children:Jt(e.first_seen_at)}),(0,L.jsx)(G,{children:Jt(e.last_seen_at||e.updated_at)})]},`${e.email}-${e.source_provider}`))})]})})]},t))})]})]})}function ir(e){let t=mn[e.onboardingFilterKind]?.options||[];return(0,L.jsxs)(L.Fragment,{children:[e.canWrite?(0,L.jsx)(ur,{loading:e.loading.engineerSetup,onSetup:e.onSetupEngineer}):null,(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`Onboarding queue`}),(0,L.jsx)(`span`,{id:`onboardingStatus`,className:`text-sm text-muted-foreground`,children:e.loading.onboarding?`Loading`:`${e.people.length} shown`})]}),(0,L.jsxs)(`div`,{className:`grid gap-3 border-b p-4 md:grid-cols-[minmax(0,1fr)_auto]`,children:[(0,L.jsxs)(U,{children:[`Search prospects`,(0,L.jsx)(H,{id:`onboardingQuery`,value:e.onboardingQuery,autoComplete:`off`,placeholder:`Name, email, Discord, onboarder`,onChange:t=>e.setOnboardingQuery(t.target.value),onKeyDown:t=>t.key===`Enter`&&e.onSearch()})]}),(0,L.jsxs)(z,{id:`searchOnboarding`,type:`button`,onClick:e.onSearch,disabled:e.loading.onboarding,children:[(0,L.jsx)(ne,{}),`Search`]})]}),(0,L.jsxs)(`div`,{className:`grid gap-3 border-b bg-background p-4 md:grid-cols-[minmax(140px,.8fr)_minmax(150px,1fr)_minmax(150px,1fr)_minmax(120px,.7fr)_auto]`,children:[(0,L.jsxs)(U,{children:[`Status`,(0,L.jsxs)(Ht,{id:`onboardingState`,value:e.onboardingState,onChange:t=>e.setOnboardingState(t.target.value),children:[(0,L.jsx)(`option`,{value:``,children:`Any state`}),gn.map(([e,t])=>(0,L.jsx)(`option`,{value:e,children:t},e))]})]}),(0,L.jsxs)(U,{children:[`Onboarder`,(0,L.jsx)(H,{id:`onboarderFilter`,value:e.onboarderFilter,autoComplete:`off`,placeholder:`Any onboarder`,onChange:t=>e.setOnboarderFilter(t.target.value),onKeyDown:t=>t.key===`Enter`&&e.onSearch()})]}),(0,L.jsxs)(U,{children:[`Add filter`,(0,L.jsx)(Ht,{id:`onboardingFilterKind`,value:e.onboardingFilterKind,disabled:e.onboardingFilterKeys.length===0,onChange:t=>e.setOnboardingFilterKind(t.target.value),children:e.onboardingFilterKeys.map(e=>(0,L.jsx)(`option`,{value:e,children:mn[e].label},e))})]}),(0,L.jsxs)(U,{children:[`Value`,(0,L.jsx)(Ht,{id:`onboardingFilterValue`,value:e.onboardingFilterValue,onChange:t=>e.setOnboardingFilterValue(t.target.value),children:t.map(([e,t])=>(0,L.jsx)(`option`,{value:e,children:t},e))})]}),(0,L.jsx)(z,{id:`addOnboardingFilter`,type:`button`,onClick:e.addFilter,disabled:e.onboardingFilterKeys.length===0,children:`Add filter`}),(0,L.jsx)(`div`,{id:`activeOnboardingFilters`,className:`md:col-span-5`,children:(0,L.jsx)(Rn,{filters:e.onboardingFilters,onRemove:e.removeFilter,suffix:`onboarding filter`})})]}),(0,L.jsx)(Mn,{hidden:e.people.length!==0,children:`No prospects match this queue view.`}),(0,L.jsx)(`div`,{className:`overflow-x-auto`,children:(0,L.jsxs)(Ut,{id:`onboardingTable`,className:I(`min-w-[1340px]`,e.people.length===0&&`hidden`),"aria-label":`Onboarding queue`,children:[(0,L.jsx)(Wt,{children:(0,L.jsxs)(Kt,{children:[(0,L.jsx)(q,{className:`w-[18%]`,label:`Name`,scope:`onboarding`,sort:e.sort,sortKey:`name`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[12%]`,label:`Status`,scope:`onboarding`,sort:e.sort,sortKey:`onboarding_state`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[20%]`,label:`Onboarder`,scope:`onboarding`,sort:e.sort,sortKey:`onboarder`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[12%]`,label:`Updated`,scope:`onboarding`,sort:e.sort,sortKey:`updated`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(W,{className:`w-[15%]`,children:`Email`}),(0,L.jsx)(W,{className:`w-[12%]`,children:`Links`}),(0,L.jsx)(q,{className:`w-[11%]`,label:`Needs`,scope:`onboarding`,sort:e.sort,sortKey:`profile_gaps`,onSort:(t,n)=>e.onSort(n)})]})}),(0,L.jsx)(Gt,{id:`onboardingBody`,children:e.people.map(t=>(0,L.jsx)(dr,{person:t,loading:e.loading,canWrite:e.canWrite,onAssign:e.onAssign,onStatusChange:e.onStatusChange,onDraftEmail:e.onDraftEmail,onSendEmail:e.onSendEmail,crmContactUrl:e.crmContactUrl,crmAttachmentUrl:e.crmAttachmentUrl,canConfigure:e.canConfigure,onOpenConfiguration:e.onOpenConfiguration},t.crm_contact_id||t.name))})]})})]})]})}var ar=[`Female`,`Genderqueer`,`Male`,`Non-Conforming`,`Other`,`Prefer not to say`,`Transgender`],or=[`Company Email`,`Personal Email`,`User ID`];function sr(e){let t=(e||``).trim().split(/\s+/).filter(Boolean);return t.length===0?{first:``,middle:``,last:``}:t.length===1?{first:t[0],middle:``,last:``}:t.length===2?{first:t[0],middle:``,last:t[1]}:{first:t[0],middle:t.slice(1,-1).join(` `),last:t[t.length-1]}}function cr(e){let t=(e.email||``).trim();return!t||t.toLowerCase().endsWith(`@508.dev`)?``:t}function lr(e){let t=(e.email_508||``).trim();if(t)return t;let n=(e.email||``).trim();return n.toLowerCase().endsWith(`@508.dev`)?n:``}function ur({loading:e,onSetup:t}){let[n,r]=(0,l.useState)(``),[i,a]=(0,l.useState)([]),[o,s]=(0,l.useState)(!1),[c,u]=(0,l.useState)(``),[d,f]=(0,l.useState)(``),[p,m]=(0,l.useState)(``),[h,g]=(0,l.useState)(``),[_,v]=(0,l.useState)(``),[y,b]=(0,l.useState)(``),[x,ee]=(0,l.useState)(``),[te,S]=(0,l.useState)(``),[C,re]=(0,l.useState)(``),[ie,ae]=(0,l.useState)(``),[oe,ce]=(0,l.useState)(``);function le(e){let t=sr(e.name);m(t.first),g(t.middle),v(t.last),f(lr(e)),ae(cr(e)),b(e.address_country||``),r(e.name||e.email_508||e.email||``),a([]),u(``)}async function w(){let e=n.trim();if(e){s(!0),u(``);try{a(await K(`/dashboard/api/people?${new URLSearchParams({limit:`8`,query:e}).toString()}`))}catch(e){u(xn(e,`Unable to search people`)),a([])}finally{s(!1)}}}async function ue(){let e={email:d,first_name:p,middle_name:h,last_name:_,country:y,personal_email:ie};x.trim()&&(e.gender=x),te.trim()&&(e.date_of_birth=te),C.trim()&&(e.date_of_joining=C),oe.trim()&&(e.prefered_email=oe),await t(e)&&(r(``),a([]),f(``),m(``),g(``),v(``),b(``),ee(``),S(``),re(``),ae(``),ce(``))}return(0,L.jsxs)(B,{children:[(0,L.jsx)(V,{children:(0,L.jsx)(Bt,{children:`Engineer setup`})}),(0,L.jsx)(Vt,{children:(0,L.jsxs)(`form`,{className:`grid gap-3`,onSubmit:e=>{e.preventDefault(),ue()},children:[(0,L.jsxs)(`div`,{className:`grid gap-3 border-b pb-3 md:grid-cols-[minmax(0,1fr)_auto]`,children:[(0,L.jsxs)(U,{children:[`CRM person`,(0,L.jsx)(H,{value:n,autoComplete:`off`,placeholder:`Search name or email`,onChange:e=>r(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),w())}})]}),(0,L.jsxs)(z,{type:`button`,onClick:w,disabled:o||!n.trim(),children:[(0,L.jsx)(ne,{}),`Search`]}),c?(0,L.jsx)(`span`,{className:`text-sm font-semibold text-destructive`,children:c}):null,i.length>0?(0,L.jsx)(`div`,{className:`grid gap-2 md:col-span-2`,children:i.map(e=>{let t=e.name||e.email_508||e.email||e.crm_contact_id,n=[e.email_508||e.email,e.contact_type].filter(Boolean).join(` | `);return(0,L.jsxs)(`button`,{type:`button`,className:`grid rounded-md border bg-background px-3 py-2 text-left text-sm hover:border-primary`,onClick:()=>le(e),children:[(0,L.jsx)(`strong`,{children:t}),n?(0,L.jsx)(`span`,{className:`text-muted-foreground`,children:n}):null]},e.crm_contact_id||t)})}):null]}),(0,L.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(130px,.6fr)]`,children:[(0,L.jsxs)(U,{children:[`Company email`,(0,L.jsx)(H,{value:d,autoComplete:`off`,placeholder:`engineer@508.dev`,onChange:e=>f(e.target.value)})]}),(0,L.jsxs)(U,{children:[`First name`,(0,L.jsx)(H,{value:p,autoComplete:`off`,placeholder:`First`,onChange:e=>m(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Middle name`,(0,L.jsx)(H,{value:h,autoComplete:`off`,placeholder:`Optional`,onChange:e=>g(e.target.value)})]})]}),(0,L.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-[minmax(0,1fr)_minmax(130px,.6fr)]`,children:[(0,L.jsxs)(U,{children:[`Last name`,(0,L.jsx)(H,{value:_,autoComplete:`off`,placeholder:`Last`,onChange:e=>v(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Country`,(0,L.jsx)(H,{value:y,autoComplete:`off`,placeholder:`Taiwan`,onChange:e=>b(e.target.value)})]})]}),(0,L.jsxs)(`details`,{className:`rounded-md border bg-background p-3`,children:[(0,L.jsx)(`summary`,{className:`cursor-pointer text-sm font-extrabold`,children:`Advanced options`}),(0,L.jsxs)(`div`,{className:`mt-3 grid gap-3 md:grid-cols-2`,children:[(0,L.jsxs)(U,{children:[`Gender`,(0,L.jsxs)(Ht,{value:x,onChange:e=>ee(e.target.value),children:[(0,L.jsx)(`option`,{value:``,children:`Default`}),ar.map(e=>(0,L.jsx)(`option`,{value:e,children:e},e))]})]}),(0,L.jsxs)(U,{children:[`Date of birth`,(0,L.jsx)(H,{value:te,type:`date`,autoComplete:`off`,onChange:e=>S(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Date of joining`,(0,L.jsx)(H,{value:C,type:`date`,autoComplete:`off`,onChange:e=>re(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Personal email`,(0,L.jsx)(H,{value:ie,type:`email`,autoComplete:`off`,placeholder:`Optional`,onChange:e=>ae(e.target.value)})]}),(0,L.jsxs)(U,{children:[`Preferred contact email`,(0,L.jsxs)(Ht,{value:oe,onChange:e=>ce(e.target.value),children:[(0,L.jsx)(`option`,{value:``,children:`Default`}),or.map(e=>(0,L.jsx)(`option`,{value:e,children:e},e))]})]})]})]}),(0,L.jsx)(`div`,{className:`flex flex-wrap items-center justify-between gap-3`,children:(0,L.jsxs)(z,{id:`setupEngineer`,type:`submit`,disabled:e||!d.trim()||!p.trim(),children:[(0,L.jsx)(se,{}),`Set up engineer`]})})]})})]})}function dr({person:e,loading:t,canWrite:n,onAssign:r,onStatusChange:i,onDraftEmail:a,onSendEmail:o,crmContactUrl:s,crmAttachmentUrl:c,canConfigure:u,onOpenConfiguration:d}){let f=e.name||e.email_508||e.email||`CRM contact`,[p,m]=(0,l.useState)(en(e.onboarder)),[h,g]=(0,l.useState)(!1),[_,v]=(0,l.useState)(null),[y,b]=(0,l.useState)(null),[x,ee]=(0,l.useState)({has_contributed:vn(Zt(e))===`onboarded`,discord_joined:e.discord_user_id?`yes`:`unknown`,agreement_signed:`unknown`});(0,l.useEffect)(()=>m(en(e.onboarder)),[e.onboarder]);let S=vn(Zt(e)),ne=e.profile_status||{},ae=[[`Discord`,ne.discord_linked],[`Resume`,ne.latest_resume],[`Skills`,Number(ne.skills_count||0)>0]].filter(([,e])=>!e),oe=s(e.crm_contact_id),se=c(e.latest_resume_id),ce=_?.onboarding_email_sent_at||e.onboarding_email_sent_at,le=_?.onboarding_email_sent_by||e.onboarding_email_sent_by,w=_?.onboarding_email_recipient||e.onboarding_email_recipient,ue=!_||y!==null&&y.has_contributed===x.has_contributed&&y.discord_joined===x.discord_joined&&y.agreement_signed===x.agreement_signed,T=_&&!_.onboarding_email_sent_at?ue?_.can_send?``:_.recipient_email?_.reply_to_email?`Send disabled: onboarding email SMTP is not configured.`:`Send disabled: your Reply-To email is missing.`:`Send disabled: candidate email is missing.`:`Send disabled: regenerate after changing draft options.`:``,de=T.includes(`SMTP`),fe=!!t[`onboarding-email-draft:${e.crm_contact_id}`],pe=!!t[`onboarding-email-send:${e.crm_contact_id}`],me=_?.markdown_body||``;async function E(t=x){let n=await a(e.crm_contact_id,t);n&&(v(n),b({...t}),g(!0))}async function D(){if(!_||!y||!ue)return;let t=await o(e.crm_contact_id,y,_.markdown_body);t&&v(t)}return(0,L.jsxs)(L.Fragment,{children:[(0,L.jsxs)(Kt,{children:[(0,L.jsxs)(G,{children:[oe?(0,L.jsx)(`a`,{className:`font-extrabold text-primary`,href:oe,target:`_blank`,rel:`noreferrer`,"aria-label":`Open ${f} in CRM`,children:f}):(0,L.jsx)(`strong`,{children:f}),(0,L.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:e.email_508||e.email||``})]}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`grid max-w-56 gap-2`,children:[(0,L.jsx)(R,{variant:$t(Zt(e)),children:e.onboarding_status_label||Qt(Zt(e))}),n?(0,L.jsxs)(Ht,{"aria-label":`Onboarding status for ${f}`,value:S,disabled:t[`onboarding-status:${e.crm_contact_id}`],onChange:t=>i(e.crm_contact_id,t.target.value),children:[S?null:(0,L.jsx)(`option`,{value:``,disabled:!0,children:`No status`}),hn.map(([e,t])=>(0,L.jsx)(`option`,{value:e,children:t},e))]}):null]})}),(0,L.jsx)(G,{children:(0,L.jsxs)(`form`,{className:`grid max-w-64 grid-cols-[minmax(100px,1fr)_auto] items-center gap-2`,onSubmit:t=>{t.preventDefault(),r(e.crm_contact_id,p)},children:[(0,L.jsx)(H,{"aria-label":`Onboarder for ${f}`,value:p,placeholder:`508 username`,onChange:e=>m(e.target.value)}),(0,L.jsx)(z,{type:`submit`,size:`sm`,"aria-label":`Save onboarder for ${f}`,disabled:t[`onboarder:${e.crm_contact_id}`],children:`Save`})]})}),(0,L.jsx)(G,{children:Jt(e.onboarding_updated_at)}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`grid gap-2`,children:[ce?(0,L.jsxs)(R,{variant:`succeeded`,children:[`Sent `,Jt(ce)]}):(0,L.jsx)(R,{variant:`neutral`,children:`Not sent`}),w?(0,L.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:w}):null,le?(0,L.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`By `,le]}):null,n?(0,L.jsxs)(z,{type:`button`,size:`sm`,variant:h?`outline`:`secondary`,onClick:()=>{if(h){g(!1);return}g(!0),_||E()},disabled:fe,children:[(0,L.jsx)(te,{}),_?`Edit draft`:`Draft email`]}):null]})}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`flex flex-wrap gap-1.5`,children:[se?(0,L.jsx)(`a`,{className:`inline-flex min-h-7 items-center rounded-md border bg-secondary px-2 text-xs font-extrabold`,href:se,target:`_blank`,rel:`noreferrer`,"aria-label":`Open ${f} resume`,children:`Resume`}):null,on(e.linkedin)?(0,L.jsx)(`a`,{className:`inline-flex min-h-7 items-center rounded-md border bg-secondary px-2 text-xs font-extrabold`,href:on(e.linkedin),target:`_blank`,rel:`noreferrer`,"aria-label":`Open ${f} LinkedIn`,children:`LinkedIn`}):null,sn(e.github_username)?(0,L.jsx)(`a`,{className:`inline-flex min-h-7 items-center rounded-md border bg-secondary px-2 text-xs font-extrabold`,href:sn(e.github_username),target:`_blank`,rel:`noreferrer`,"aria-label":`Open ${f} GitHub`,children:e.github_username||`GitHub`}):null,!se&&!on(e.linkedin)&&!sn(e.github_username)?`None`:null]})}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`flex flex-wrap gap-1.5`,children:[ae.map(([e])=>(0,L.jsxs)(R,{variant:`missing`,children:[`Missing `,e]},String(e))),ae.length===0?`None`:null]})})]}),h?(0,L.jsx)(Kt,{children:(0,L.jsx)(G,{colSpan:7,className:`bg-secondary/30`,children:(0,L.jsxs)(`div`,{className:`grid gap-3 rounded-md border bg-background p-4`,children:[(0,L.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-[auto_minmax(150px,220px)_minmax(150px,220px)_auto] md:items-end`,children:[(0,L.jsxs)(`label`,{className:`flex min-h-9 items-center gap-2 text-sm font-semibold`,children:[(0,L.jsx)(`input`,{type:`checkbox`,checked:x.has_contributed,onChange:e=>{ee({...x,has_contributed:e.target.checked})}}),`Contribution done`]}),(0,L.jsxs)(U,{children:[`Discord`,(0,L.jsxs)(Ht,{value:x.discord_joined,onChange:e=>ee({...x,discord_joined:e.target.value}),children:[(0,L.jsx)(`option`,{value:`unknown`,children:`Unknown`}),(0,L.jsx)(`option`,{value:`yes`,children:`Joined`}),(0,L.jsx)(`option`,{value:`no`,children:`Not joined`})]})]}),(0,L.jsxs)(U,{children:[`Agreement`,(0,L.jsxs)(Ht,{value:x.agreement_signed,onChange:e=>ee({...x,agreement_signed:e.target.value}),children:[(0,L.jsx)(`option`,{value:`unknown`,children:`Unknown`}),(0,L.jsx)(`option`,{value:`yes`,children:`Signed`}),(0,L.jsx)(`option`,{value:`no`,children:`Not signed`})]})]}),(0,L.jsxs)(z,{type:`button`,variant:`outline`,onClick:()=>E(),disabled:fe,children:[(0,L.jsx)(C,{}),`Regenerate`]})]}),_?(0,L.jsxs)(L.Fragment,{children:[(0,L.jsxs)(`div`,{className:`grid gap-2 text-sm md:grid-cols-3`,children:[(0,L.jsxs)(`span`,{children:[(0,L.jsx)(`strong`,{children:`To:`}),` `,_.recipient_email||`Missing`]}),(0,L.jsxs)(`span`,{children:[(0,L.jsx)(`strong`,{children:`Reply-To:`}),` `,_.reply_to_email||`Missing`]}),(0,L.jsxs)(`span`,{children:[(0,L.jsx)(`strong`,{children:`From:`}),` `,_.sender_display_name||`onboarding`]})]}),(0,L.jsxs)(U,{children:[`Subject`,(0,L.jsx)(H,{value:_.subject,readOnly:!0})]}),(0,L.jsxs)(U,{children:[`Draft`,(0,L.jsx)(`textarea`,{value:me,className:`min-h-64 w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-sm text-foreground shadow-xs transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]`,onChange:e=>v({..._,markdown_body:e.target.value})})]}),(0,L.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,L.jsxs)(z,{type:`button`,variant:`default`,onClick:D,disabled:pe||!_.can_send||!ue||!me.trim(),title:T||void 0,children:[(0,L.jsx)(re,{}),pe?`Sending`:`Send`]}),T?(0,L.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[T,de&&u?(0,L.jsxs)(z,{type:`button`,size:`sm`,variant:`ghost`,className:`ml-2`,onClick:d,children:[(0,L.jsx)(ie,{}),`Configure`]}):null]}):null,_.marker_status===`error`?(0,L.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[`Marker not saved: `,_.marker_error||`unknown`]}):null]})]}):(0,L.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:fe?`Drafting email`:`No draft loaded`})]})})}):null]})}function fr(e){return(0,L.jsxs)(L.Fragment,{children:[(0,L.jsxs)(B,{className:`grid gap-3 p-4 md:grid-cols-4 md:items-end`,children:[(0,L.jsxs)(U,{children:[`Window`,(0,L.jsxs)(Ht,{id:`minutes`,value:e.minutes,onChange:t=>e.setMinutes(t.target.value),children:[(0,L.jsx)(`option`,{value:`15`,children:`15 minutes`}),(0,L.jsx)(`option`,{value:`60`,children:`1 hour`}),(0,L.jsx)(`option`,{value:`360`,children:`6 hours`}),(0,L.jsx)(`option`,{value:`1440`,children:`24 hours`})]})]}),(0,L.jsxs)(U,{children:[`Status`,(0,L.jsxs)(Ht,{id:`status`,value:e.status,onChange:t=>e.setStatus(t.target.value),children:[(0,L.jsx)(`option`,{value:``,children:`Any status`}),(0,L.jsx)(`option`,{value:`queued`,children:`Queued`}),(0,L.jsx)(`option`,{value:`running`,children:`Running`}),(0,L.jsx)(`option`,{value:`succeeded`,children:`Succeeded`}),(0,L.jsx)(`option`,{value:`failed`,children:`Failed`}),(0,L.jsx)(`option`,{value:`dead`,children:`Dead`}),(0,L.jsx)(`option`,{value:`canceled`,children:`Canceled`})]})]}),(0,L.jsxs)(U,{children:[`Type`,(0,L.jsx)(H,{id:`jobType`,value:e.jobType,autoComplete:`off`,placeholder:`Any type`,onChange:t=>e.setJobType(t.target.value),onKeyDown:t=>t.key===`Enter`&&e.onSearch()})]}),(0,L.jsxs)(z,{id:`refreshJobs`,type:`button`,onClick:e.onSearch,disabled:e.loading.jobs,children:[(0,L.jsx)(C,{}),`Refresh background tasks`]})]}),(0,L.jsxs)(`section`,{className:`grid gap-3 md:grid-cols-4`,"aria-label":`Background task summary`,children:[(0,L.jsx)(jn,{id:`metricTotal`,label:`Total`,value:e.jobs.length}),(0,L.jsx)(jn,{id:`metricQueued`,label:`Queued`,value:e.jobCounts.queued||0}),(0,L.jsx)(jn,{id:`metricRunning`,label:`Running`,value:e.jobCounts.running||0}),(0,L.jsx)(jn,{id:`metricFailed`,label:`Failed`,value:(e.jobCounts.failed||0)+(e.jobCounts.dead||0)})]}),(0,L.jsxs)(B,{children:[(0,L.jsx)(V,{children:(0,L.jsx)(Bt,{children:`Recent background tasks`})}),(0,L.jsx)(Mn,{hidden:e.jobs.length!==0,children:`No background tasks match these filters.`}),(0,L.jsx)(`div`,{className:`overflow-x-auto`,children:(0,L.jsxs)(Ut,{id:`jobsTable`,className:I(`min-w-[980px]`,e.jobs.length===0&&`hidden`),"aria-label":`Recent background tasks`,children:[(0,L.jsx)(Wt,{children:(0,L.jsxs)(Kt,{children:[(0,L.jsx)(q,{className:`w-[22%]`,label:`Task id`,scope:`jobs`,sort:e.sort,sortKey:`job_id`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[24%]`,label:`Task type`,scope:`jobs`,sort:e.sort,sortKey:`type`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[12%]`,label:`Status`,scope:`jobs`,sort:e.sort,sortKey:`status`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[12%]`,label:`Attempts`,scope:`jobs`,sort:e.sort,sortKey:`attempts`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[18%]`,label:`Updated`,scope:`jobs`,sort:e.sort,sortKey:`updated_at`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(W,{children:`Actions`})]})}),(0,L.jsx)(Gt,{id:`jobsBody`,children:e.jobs.map(t=>(0,L.jsxs)(Kt,{children:[(0,L.jsx)(G,{className:`font-mono`,children:t.job_id}),(0,L.jsx)(G,{children:t.type}),(0,L.jsx)(G,{children:(0,L.jsx)(R,{variant:t.status||`neutral`,children:t.status})}),(0,L.jsxs)(G,{children:[t.attempts,`/`,t.max_attempts]}),(0,L.jsx)(G,{children:Jt(t.updated_at)}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-2`,children:[(0,L.jsx)(z,{type:`button`,size:`sm`,variant:`outline`,"aria-label":`View details for ${t.type} task ${t.job_id}`,onClick:()=>e.onDetail(t.job_id),disabled:e.loading[`detail:${t.job_id}`],children:`Details`}),e.canWrite?(0,L.jsx)(z,{type:`button`,size:`sm`,"aria-label":`Rerun ${t.type} task ${t.job_id}`,onClick:()=>e.onRerun(t.job_id),disabled:e.loading[`rerun:${t.job_id}`],children:`Rerun`}):null]})})]},t.job_id))})]})})]}),e.jobDetail?(0,L.jsxs)(B,{id:`jobDetailPanel`,children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`Task detail`}),(0,L.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:e.jobDetail.job_id})]}),(0,L.jsxs)(Vt,{className:`grid gap-4`,children:[(0,L.jsx)(`div`,{className:`grid gap-3 md:grid-cols-2`,children:[[`Task type`,e.jobDetail.type],[`Status`,e.jobDetail.status],[`Attempts`,`${e.jobDetail.attempts}/${e.jobDetail.max_attempts}`],[`Updated`,Jt(e.jobDetail.updated_at)],[`Created`,Jt(e.jobDetail.created_at)],[`Run after`,Jt(e.jobDetail.run_after)],[`Locked by`,e.jobDetail.locked_by||`None`],[`Last error`,e.jobDetail.last_error||`None`]].map(([e,t])=>(0,L.jsxs)(`div`,{className:`grid gap-1 rounded-md border bg-background p-3`,children:[(0,L.jsx)(`span`,{className:`text-[11px] font-extrabold uppercase text-muted-foreground`,children:e}),(0,L.jsx)(`strong`,{className:`break-words text-sm`,children:t})]},e))}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`h2`,{className:`mb-2 text-[15px] font-bold`,children:`Payload`}),(0,L.jsx)(`pre`,{className:`max-h-64 overflow-auto whitespace-pre-wrap break-words rounded-md border bg-background p-3 font-mono text-xs`,children:Xt(e.jobDetail.payload)||`No payload`})]}),(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`h2`,{className:`mb-2 text-[15px] font-bold`,children:`Result`}),(0,L.jsx)(`pre`,{className:`max-h-64 overflow-auto whitespace-pre-wrap break-words rounded-md border bg-background p-3 font-mono text-xs`,children:Xt(e.jobDetail.result)||`No result`})]})]})]}):null]})}function pr(e){return(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`Recent audit`}),(0,L.jsxs)(z,{id:`refreshAudit`,type:`button`,variant:`outline`,onClick:e.onRefresh,disabled:e.loading.audit,children:[(0,L.jsx)(C,{}),`Refresh`]})]}),(0,L.jsx)(Mn,{hidden:e.events.length!==0,children:`No audit events found.`}),(0,L.jsx)(`div`,{className:`overflow-x-auto`,children:(0,L.jsxs)(Ut,{id:`auditTable`,className:I(`min-w-[760px]`,e.events.length===0&&`hidden`),"aria-label":`Recent audit events`,children:[(0,L.jsx)(Wt,{children:(0,L.jsxs)(Kt,{children:[(0,L.jsx)(q,{className:`w-[24%]`,label:`Time`,scope:`audit`,sort:e.sort,sortKey:`occurred_at`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[28%]`,label:`Actor`,scope:`audit`,sort:e.sort,sortKey:`actor`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[28%]`,label:`Action`,scope:`audit`,sort:e.sort,sortKey:`action`,onSort:(t,n)=>e.onSort(n)}),(0,L.jsx)(q,{className:`w-[20%]`,label:`Result`,scope:`audit`,sort:e.sort,sortKey:`result`,onSort:(t,n)=>e.onSort(n)})]})}),(0,L.jsx)(Gt,{id:`auditBody`,children:e.events.map(e=>(0,L.jsxs)(Kt,{children:[(0,L.jsx)(G,{children:Jt(e.occurred_at)}),(0,L.jsx)(G,{children:e.actor_display_name||e.actor_subject||e.actor_provider}),(0,L.jsx)(G,{children:e.action}),(0,L.jsx)(G,{children:(0,L.jsx)(R,{variant:e.result===`success`?`succeeded`:`failed`,children:e.result})})]},e.id||`${e.occurred_at||``}-${e.actor_subject||``}-${e.action||``}`))})]})})]})}function mr({report:e,loading:t,onRefresh:n}){let r=e?.summary||{},i=[[`Status`,e?.status_counts||{}],[`Intent`,e?.intent_counts||{}],[`Planner`,e?.planner_counts||{}]].flatMap(([e,t])=>Object.entries(t).map(([t,n])=>({label:e,value:t,count:n}))).sort((e,t)=>t.count-e.count||e.label.localeCompare(t.label)),a=Array.isArray(e?.recent_unsupported)?e.recent_unsupported:[];return(0,L.jsxs)(L.Fragment,{children:[(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`Agent requests`}),(0,L.jsxs)(z,{id:`refreshAgent`,type:`button`,variant:`outline`,onClick:n,disabled:t.agent,children:[(0,L.jsx)(C,{}),`Refresh`]})]}),(0,L.jsxs)(Vt,{className:`grid gap-3 md:grid-cols-5`,children:[(0,L.jsx)(jn,{id:`agentMetricTotal`,label:`Total`,value:r.total||0}),(0,L.jsx)(jn,{id:`agentMetricHandled`,label:`Handled`,value:r.handled||0}),(0,L.jsx)(jn,{id:`agentMetricConfirmations`,label:`Confirmations`,value:r.requires_confirmation||0}),(0,L.jsx)(jn,{id:`agentMetricClarifications`,label:`Clarifications`,value:r.needs_clarification||0}),(0,L.jsx)(jn,{id:`agentMetricUnsupported`,label:`Not understood`,value:r.unsupported||0})]})]}),(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`Request mix`}),(0,L.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:`Recent agent.request audit events.`})]}),(0,L.jsx)(Mn,{hidden:i.length!==0,children:`No agent request data found.`}),(0,L.jsx)(`div`,{className:`overflow-x-auto`,children:(0,L.jsxs)(Ut,{id:`agentBreakdownTable`,className:I(`min-w-[860px]`,i.length===0&&`hidden`),"aria-label":`Agent request breakdown`,children:[(0,L.jsx)(Wt,{children:(0,L.jsxs)(Kt,{children:[(0,L.jsx)(W,{children:`Dimension`}),(0,L.jsx)(W,{children:`Value`}),(0,L.jsx)(W,{children:`Count`})]})}),(0,L.jsx)(Gt,{id:`agentBreakdownBody`,children:i.map(e=>(0,L.jsxs)(Kt,{children:[(0,L.jsx)(G,{children:e.label}),(0,L.jsx)(G,{children:e.value}),(0,L.jsx)(G,{children:e.count})]},`${e.label}-${e.value}`))})]})})]}),(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`Not understood`}),(0,L.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:`Sanitized request text only.`})]}),(0,L.jsx)(Mn,{hidden:a.length!==0,children:`No unsupported agent requests found.`}),(0,L.jsx)(`div`,{className:`overflow-x-auto`,children:(0,L.jsxs)(Ut,{id:`agentUnsupportedTable`,className:I(`min-w-[860px]`,a.length===0&&`hidden`),"aria-label":`Unsupported agent requests`,children:[(0,L.jsx)(Wt,{children:(0,L.jsxs)(Kt,{children:[(0,L.jsx)(W,{children:`Time`}),(0,L.jsx)(W,{children:`Actor`}),(0,L.jsx)(W,{children:`Message`}),(0,L.jsx)(W,{children:`Result`})]})}),(0,L.jsx)(Gt,{id:`agentUnsupportedBody`,children:a.map(e=>(0,L.jsxs)(Kt,{children:[(0,L.jsx)(G,{children:Jt(e.occurred_at)}),(0,L.jsx)(G,{children:e.actor}),(0,L.jsx)(G,{children:e.message_sanitized}),(0,L.jsx)(G,{children:(0,L.jsx)(R,{variant:e.result===`success`?`succeeded`:`failed`,children:e.result||`unknown`})})]},`${e.occurred_at||``}-${e.actor||``}-${e.message_sanitized||``}`))})]})})]})]})}function hr({items:e,loading:t,canWrite:n,onRefresh:r,onSave:i,onClear:a,focusCategory:o,focusNonce:s}){let[c,u]=(0,l.useState)(`All`),[d,f]=(0,l.useState)(``),[p,m]=(0,l.useState)({}),h=(0,l.useMemo)(()=>{let t=new Set(e.map(e=>e.category)),n=cn.filter(e=>t.has(e.category)),r=Array.from(t).filter(e=>!ln.has(e)).sort().map(e=>({category:e,label:e,description:`Additional runtime settings.`}));return n.concat(r)},[e]),g=(0,l.useMemo)(()=>{let t=new Map;for(let n of e){if(c!==`All`&&n.category!==c)continue;let e=t.get(n.category)??[];e.push(n),t.set(n.category,e)}return Array.from(t.entries()).map(([e,t])=>{let n=ln.get(e),r=t.sort((e,t)=>Number(!dn(e))-Number(!dn(t))||e.label.localeCompare(t.label)),i=r.filter(dn),a=r.filter(e=>!dn(e));return{category:e,label:n?.label??e,description:n?.description??`Additional runtime settings.`,order:n?.index??cn.length,primaryItems:i.length?i:r,advancedItems:i.length?a:[],items:r}}).sort((e,t)=>e.order-t.order||e.label.localeCompare(t.label))},[e,c]),_=(0,l.useMemo)(()=>({configured:e.filter(e=>e.configured).length,envLocked:e.filter(e=>e.env_locked).length,missing:e.filter(e=>!e.configured).length}),[e]),v=g.reduce((e,t)=>e+t.items.length,0);(0,l.useEffect)(()=>{m(Object.fromEntries(e.map(e=>[e.key,e.is_secret?``:String(e.value??``)])))},[e]),(0,l.useEffect)(()=>{c!==`All`&&!e.some(e=>e.category===c)&&u(`All`)},[e,c]),(0,l.useEffect)(()=>{if(!o||!e.some(e=>e.category===o))return;u(o),f(o);let t=window.requestAnimationFrame?.(()=>{document.getElementById(un(o))?.scrollIntoView({block:`start`,behavior:`smooth`})}),n=window.setTimeout(()=>f(``),4e3);return()=>{t!==void 0&&window.cancelAnimationFrame?.(t),window.clearTimeout(n)}},[o,s,e]);function y(e){return e.source===`env`?`ENV`:e.source===`database`?`DB`:`Default`}function b(e){let r=p[e.key]??``,i=!n||e.env_locked||t[`configuration:${e.key}`];return e.value_type===`bool`?(0,L.jsxs)(Ht,{"aria-label":`${e.label} value`,value:r,disabled:i,onChange:t=>m(n=>({...n,[e.key]:t.target.value})),children:[(0,L.jsx)(`option`,{value:``,children:`Default`}),(0,L.jsx)(`option`,{value:`true`,children:`True`}),(0,L.jsx)(`option`,{value:`false`,children:`False`})]}):(0,L.jsx)(H,{"aria-label":`${e.label} value`,value:r,type:e.is_secret?`password`:e.value_type===`int`?`number`:`text`,inputMode:e.value_type===`int`||e.value_type===`float`?`numeric`:`text`,placeholder:e.is_secret?`Set new value`:``,autoComplete:`off`,disabled:i,onChange:t=>m(n=>({...n,[e.key]:t.target.value}))})}function x(e,t,n){return(0,L.jsx)(`div`,{className:`overflow-x-auto`,children:(0,L.jsxs)(Ut,{id:`configurationTable-${n}`,className:`min-w-[980px]`,"aria-label":`${e} configuration settings`,children:[(0,L.jsx)(Wt,{children:(0,L.jsxs)(Kt,{children:[(0,L.jsx)(W,{className:`w-[26%]`,children:`Setting`}),(0,L.jsx)(W,{className:`w-[12%]`,children:`Source`}),(0,L.jsx)(W,{className:`w-[18%]`,children:`Active`}),(0,L.jsx)(W,{className:`w-[29%]`,children:`Value`}),(0,L.jsx)(W,{className:`w-[15%]`,children:`Actions`})]})}),(0,L.jsx)(Gt,{id:`configurationBody-${n}`,children:t.map(e=>ee(e))})]})})}function ee(e){let r=t[`configuration:${e.key}`],o=n&&!e.env_locked&&!r,s=p[e.key]??``,c=!e.is_secret&&!s.trim();return(0,L.jsxs)(Kt,{children:[(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`grid gap-1`,children:[(0,L.jsx)(`strong`,{children:e.label}),(0,L.jsx)(`span`,{className:`font-mono text-xs text-muted-foreground`,children:e.key}),(0,L.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:e.description}),e.restart_required?(0,L.jsx)(`div`,{children:(0,L.jsx)(R,{variant:`running`,children:`Restart`})}):null]})}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`grid gap-1.5`,children:[(0,L.jsx)(R,{variant:e.source===`env`?`running`:`neutral`,children:y(e)}),e.env_locked?(0,L.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`Environment locked`}):null]})}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`grid gap-1`,children:[(0,L.jsx)(R,{variant:e.configured?`succeeded`:`missing`,children:e.configured?`Configured`:`Missing`}),e.is_secret?(0,L.jsx)(`span`,{className:`font-mono text-xs text-muted-foreground`,children:e.masked_value||(e.configured?`Hidden`:`No secret`)}):(0,L.jsx)(`span`,{className:`break-words text-xs text-muted-foreground`,children:String(e.value??``)||`Default`}),e.is_secret&&e.secret_encryption_configured===!1?(0,L.jsx)(`span`,{className:`text-xs text-red-300`,children:`Encryption key missing`}):null]})}),(0,L.jsx)(G,{children:b(e)}),(0,L.jsx)(G,{children:(0,L.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-2`,children:[(0,L.jsx)(z,{type:`button`,size:`sm`,onClick:()=>i(e.key,s),disabled:!o||e.is_secret&&!s.trim()||c||e.is_secret&&e.secret_encryption_configured===!1,children:`Save`}),(0,L.jsx)(z,{type:`button`,size:`sm`,variant:`outline`,onClick:()=>a(e.key),disabled:!o||e.source!==`database`,children:`Clear`})]})})]},e.key)}return(0,L.jsxs)(`div`,{className:`grid gap-4`,children:[(0,L.jsxs)(B,{children:[(0,L.jsxs)(V,{children:[(0,L.jsx)(Bt,{children:`Configuration`}),(0,L.jsxs)(z,{id:`refreshConfiguration`,type:`button`,variant:`outline`,onClick:r,disabled:t.configuration,children:[(0,L.jsx)(C,{}),`Refresh`]})]}),(0,L.jsxs)(Vt,{className:`grid gap-4`,children:[(0,L.jsx)(`section`,{className:`grid gap-3 sm:grid-cols-2 lg:grid-cols-4`,"aria-label":`Configuration summary`,children:[[`Total`,e.length],[`Configured`,_.configured],[`Missing`,_.missing],[`Env locked`,_.envLocked]].map(([e,t])=>(0,L.jsxs)(`div`,{className:`rounded-md border bg-background p-3`,children:[(0,L.jsx)(`span`,{className:`text-[11px] font-extrabold uppercase text-muted-foreground`,children:e}),(0,L.jsx)(`strong`,{className:`mt-1 block text-xl`,children:t})]},e))}),(0,L.jsxs)(`section`,{className:`flex flex-wrap gap-2`,"aria-label":`Configuration groups`,children:[(0,L.jsxs)(z,{type:`button`,size:`sm`,variant:c===`All`?`default`:`outline`,"aria-pressed":c===`All`,onClick:()=>u(`All`),children:[`All groups`,(0,L.jsx)(`span`,{className:`font-mono text-[11px]`,children:e.length})]}),h.map(t=>{let n=e.filter(e=>e.category===t.category).length;return(0,L.jsxs)(z,{type:`button`,size:`sm`,variant:c===t.category?`default`:`outline`,"aria-pressed":c===t.category,onClick:()=>u(t.category),children:[t.label,(0,L.jsx)(`span`,{className:`font-mono text-[11px]`,children:n})]},t.category)})]})]})]}),(0,L.jsx)(Mn,{hidden:v!==0,children:`No configuration entries found.`}),g.map(e=>{let t=e.items.filter(e=>e.configured).length,n=e.items.length-t,r=e.items.some(e=>e.restart_required);return(0,L.jsxs)(B,{id:un(e.category),className:I(`scroll-mt-4 transition-shadow`,d===e.category&&`ring-2 ring-primary ring-offset-2 ring-offset-background`),children:[(0,L.jsxs)(V,{className:`items-start`,children:[(0,L.jsxs)(`div`,{className:`grid gap-1`,children:[(0,L.jsx)(Bt,{children:e.label}),(0,L.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:e.description})]}),(0,L.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-1.5`,children:[(0,L.jsxs)(R,{variant:`neutral`,children:[e.items.length,` settings`]}),(0,L.jsxs)(R,{variant:n?`missing`:`succeeded`,children:[t,` configured`]}),r?(0,L.jsx)(R,{variant:`running`,children:`Restart`}):null]})]}),x(e.label,e.primaryItems,e.category),e.advancedItems.length?(0,L.jsxs)(`details`,{className:`border-t bg-background/40`,children:[(0,L.jsxs)(`summary`,{className:`flex min-h-11 cursor-pointer items-center justify-between gap-3 px-4 py-3 text-sm font-extrabold`,children:[(0,L.jsx)(`span`,{children:`Advanced`}),(0,L.jsxs)(`span`,{className:`font-mono text-xs text-muted-foreground`,children:[e.advancedItems.length,` settings`]})]}),(0,L.jsx)(`div`,{className:`border-t`,children:x(`${e.label} advanced`,e.advancedItems,`${e.category}-advanced`)})]}):null]},e.category)})]})}var gr=document.getElementById(`root`);if(gr)(0,pe.createRoot)(gr).render((0,L.jsx)(l.StrictMode,{children:(0,L.jsx)(Pn,{})}));else throw Error(`Missing #root container`); \ No newline at end of file diff --git a/apps/api/src/five08/backend/static/dashboard/assets/index-BoK8s4aw.css b/apps/api/src/five08/backend/static/dashboard/assets/index-BoK8s4aw.css deleted file mode 100644 index 43dcde91..00000000 --- a/apps/api/src/five08/backend/static/dashboard/assets/index-BoK8s4aw.css +++ /dev/null @@ -1,2 +0,0 @@ -/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-100:oklch(93.6% .032 17.717);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-teal-200:oklch(91% .096 180.426);--color-teal-400:oklch(77.7% .152 181.912);--color-teal-500:oklch(70.4% .14 182.503);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--leading-tight:1.25;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){*{outline-color:color-mix(in oklab, var(--ring) 50%, transparent)}}body{margin:calc(var(--spacing) * 0);min-width:calc(var(--spacing) * 80);background-color:var(--background);color:var(--foreground);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}button{cursor:pointer}button:disabled{cursor:not-allowed}}@layer components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing) * 0)}.-top-1{top:calc(var(--spacing) * -1)}.top-0{top:calc(var(--spacing) * 0)}.-right-1{right:calc(var(--spacing) * -1)}.right-0{right:calc(var(--spacing) * 0)}.right-5{right:calc(var(--spacing) * 5)}.bottom-5{bottom:calc(var(--spacing) * 5)}.left-5{left:calc(var(--spacing) * 5)}.z-20{z-index:20}.z-40{z-index:40}.z-50{z-index:50}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing) * 0)}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.ml-2{margin-left:calc(var(--spacing) * 2)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.table-cell{display:table-cell}.table-row{display:table-row}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-full{height:100%}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-56{max-height:calc(var(--spacing) * 56)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-\[78vh\]{max-height:78vh}.max-h-\[90vh\]{max-height:90vh}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-5{min-height:calc(var(--spacing) * 5)}.min-h-7{min-height:calc(var(--spacing) * 7)}.min-h-9{min-height:calc(var(--spacing) * 9)}.min-h-10{min-height:calc(var(--spacing) * 10)}.min-h-11{min-height:calc(var(--spacing) * 11)}.min-h-20{min-height:calc(var(--spacing) * 20)}.min-h-64{min-height:calc(var(--spacing) * 64)}.min-h-\[22px\]{min-height:22px}.w-\[10\%\]{width:10%}.w-\[11\%\]{width:11%}.w-\[12\%\]{width:12%}.w-\[14\%\]{width:14%}.w-\[15\%\]{width:15%}.w-\[16\%\]{width:16%}.w-\[18\%\]{width:18%}.w-\[20\%\]{width:20%}.w-\[22\%\]{width:22%}.w-\[24\%\]{width:24%}.w-\[25\%\]{width:25%}.w-\[26\%\]{width:26%}.w-\[27\%\]{width:27%}.w-\[28\%\]{width:28%}.w-\[29\%\]{width:29%}.w-\[48px\]{width:48px}.w-\[160px\]{width:160px}.w-\[min\(48rem\,calc\(100vw-2\.5rem\)\)\]{width:min(48rem,100vw - 2.5rem)}.w-fit{width:fit-content}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-56{max-width:calc(var(--spacing) * 56)}.max-w-64{max-width:calc(var(--spacing) * 64)}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-5{min-width:calc(var(--spacing) * 5)}.min-w-\[190px\]{min-width:190px}.min-w-\[760px\]{min-width:760px}.min-w-\[860px\]{min-width:860px}.min-w-\[900px\]{min-width:900px}.min-w-\[920px\]{min-width:920px}.min-w-\[980px\]{min-width:980px}.min-w-\[1100px\]{min-width:1100px}.min-w-\[1340px\]{min-width:1340px}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.scroll-mt-4{scroll-margin-top:calc(var(--spacing) * 4)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[minmax\(100px\,1fr\)_auto\]{grid-template-columns:minmax(100px,1fr) auto}.grid-rows-\[auto_minmax\(0\,1fr\)\]{grid-template-rows:auto minmax(0,1fr)}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.content-start{align-content:flex-start}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-1{row-gap:calc(var(--spacing) * 1)}.self-end{align-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-400\/40{border-color:#fcbb0066}@supports (color:color-mix(in lab, red, red)){.border-amber-400\/40{border-color:color-mix(in oklab, var(--color-amber-400) 40%, transparent)}}.border-amber-500\/40{border-color:#f99c0066}@supports (color:color-mix(in lab, red, red)){.border-amber-500\/40{border-color:color-mix(in oklab, var(--color-amber-500) 40%, transparent)}}.border-border{border-color:var(--border)}.border-destructive{border-color:var(--destructive)}.border-emerald-400\/35{border-color:#00d29459}@supports (color:color-mix(in lab, red, red)){.border-emerald-400\/35{border-color:color-mix(in oklab, var(--color-emerald-400) 35%, transparent)}}.border-emerald-500\/40{border-color:#00bb7f66}@supports (color:color-mix(in lab, red, red)){.border-emerald-500\/40{border-color:color-mix(in oklab, var(--color-emerald-500) 40%, transparent)}}.border-input{border-color:var(--input)}.border-primary{border-color:var(--primary)}.border-red-400\/40{border-color:#ff656866}@supports (color:color-mix(in lab, red, red)){.border-red-400\/40{border-color:color-mix(in oklab, var(--color-red-400) 40%, transparent)}}.border-red-500\/25{border-color:#fb2c3640}@supports (color:color-mix(in lab, red, red)){.border-red-500\/25{border-color:color-mix(in oklab, var(--color-red-500) 25%, transparent)}}.border-red-500\/40{border-color:#fb2c3666}@supports (color:color-mix(in lab, red, red)){.border-red-500\/40{border-color:color-mix(in oklab, var(--color-red-500) 40%, transparent)}}.border-teal-400\/40{border-color:#00d3bd66}@supports (color:color-mix(in lab, red, red)){.border-teal-400\/40{border-color:color-mix(in oklab, var(--color-teal-400) 40%, transparent)}}.border-transparent{border-color:#0000}.border-l-muted-foreground\/60{border-left-color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.border-l-muted-foreground\/60{border-left-color:color-mix(in oklab, var(--muted-foreground) 60%, transparent)}}.bg-accent{background-color:var(--accent)}.bg-amber-200{background-color:var(--color-amber-200)}.bg-amber-500\/15{background-color:#f99c0026}@supports (color:color-mix(in lab, red, red)){.bg-amber-500\/15{background-color:color-mix(in oklab, var(--color-amber-500) 15%, transparent)}}.bg-background,.bg-background\/40{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.bg-background\/40{background-color:color-mix(in oklab, var(--background) 40%, transparent)}}.bg-background\/90{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.bg-background\/90{background-color:color-mix(in oklab, var(--background) 90%, transparent)}}.bg-black\/20{background-color:#0003}@supports (color:color-mix(in lab, red, red)){.bg-black\/20{background-color:color-mix(in oklab, var(--color-black) 20%, transparent)}}.bg-black\/30{background-color:#0000004d}@supports (color:color-mix(in lab, red, red)){.bg-black\/30{background-color:color-mix(in oklab, var(--color-black) 30%, transparent)}}.bg-black\/45{background-color:#00000073}@supports (color:color-mix(in lab, red, red)){.bg-black\/45{background-color:color-mix(in oklab, var(--color-black) 45%, transparent)}}.bg-card{background-color:var(--card)}.bg-destructive{background-color:var(--destructive)}.bg-emerald-500\/15{background-color:#00bb7f26}@supports (color:color-mix(in lab, red, red)){.bg-emerald-500\/15{background-color:color-mix(in oklab, var(--color-emerald-500) 15%, transparent)}}.bg-primary{background-color:var(--primary)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-500\/5{background-color:#fb2c360d}@supports (color:color-mix(in lab, red, red)){.bg-red-500\/5{background-color:color-mix(in oklab, var(--color-red-500) 5%, transparent)}}.bg-red-500\/15{background-color:#fb2c3626}@supports (color:color-mix(in lab, red, red)){.bg-red-500\/15{background-color:color-mix(in oklab, var(--color-red-500) 15%, transparent)}}.bg-secondary,.bg-secondary\/30{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.bg-secondary\/30{background-color:color-mix(in oklab, var(--secondary) 30%, transparent)}}.bg-secondary\/35{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.bg-secondary\/35{background-color:color-mix(in oklab, var(--secondary) 35%, transparent)}}.bg-secondary\/45{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.bg-secondary\/45{background-color:color-mix(in oklab, var(--secondary) 45%, transparent)}}.bg-teal-500\/15{background-color:#00baa726}@supports (color:color-mix(in lab, red, red)){.bg-teal-500\/15{background-color:color-mix(in oklab, var(--color-teal-500) 15%, transparent)}}.p-0{padding:calc(var(--spacing) * 0)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-7{padding-block:calc(var(--spacing) * 7)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-\[inherit\]{font-family:inherit}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[11px\]{font-size:11px}.text-\[15px\]{font-size:15px}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent-foreground{color:var(--accent-foreground)}.text-amber-200{color:var(--color-amber-200)}.text-amber-300{color:var(--color-amber-300)}.text-card-foreground{color:var(--card-foreground)}.text-destructive{color:var(--destructive)}.text-emerald-300{color:var(--color-emerald-300)}.text-foreground{color:var(--foreground)}.text-inherit{color:inherit}.text-muted-foreground{color:var(--muted-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-red-100{color:var(--color-red-100)}.text-red-300{color:var(--color-red-300)}.text-secondary-foreground{color:var(--secondary-foreground)}.text-teal-200{color:var(--color-teal-200)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.underline-offset-4{text-underline-offset:4px}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-\[0_18px_44px_rgb\(0_0_0\/0\.22\)\]{--tw-shadow:0 18px 44px var(--tw-shadow-color,#00000038);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-primary{--tw-ring-color:var(--primary)}.ring-offset-2{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-background{--tw-ring-offset-color:var(--background)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.running{animation-play-state:running}.placeholder\:text-muted-foreground::placeholder{color:var(--muted-foreground)}@media (hover:hover){.hover\:border-border:hover{border-color:var(--border)}.hover\:border-primary:hover{border-color:var(--primary)}.hover\:bg-accent:hover{background-color:var(--accent)}.hover\:bg-destructive\/90:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab, var(--destructive) 90%, transparent)}}.hover\:bg-muted\/45:hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/45:hover{background-color:color-mix(in oklab, var(--muted) 45%, transparent)}}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab, var(--primary) 90%, transparent)}}.hover\:bg-secondary:hover,.hover\:bg-secondary\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab, var(--secondary) 80%, transparent)}}.hover\:text-accent-foreground:hover{color:var(--accent-foreground)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:bg-secondary:focus{background-color:var(--secondary)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width>=48rem){.md\:sticky{position:sticky}.md\:top-24{top:calc(var(--spacing) * 24)}.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:col-span-5{grid-column:span 5/span 5}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:grid-cols-\[7rem_minmax\(0\,1fr\)\]{grid-template-columns:7rem minmax(0,1fr)}.md\:grid-cols-\[190px_minmax\(0\,1fr\)\]{grid-template-columns:190px minmax(0,1fr)}.md\:grid-cols-\[auto_minmax\(0\,1fr\)_auto\]{grid-template-columns:auto minmax(0,1fr) auto}.md\:grid-cols-\[auto_minmax\(150px\,220px\)_minmax\(150px\,220px\)_auto\]{grid-template-columns:auto minmax(150px,220px) minmax(150px,220px) auto}.md\:grid-cols-\[minmax\(0\,1fr\)_180px_auto_auto_auto\]{grid-template-columns:minmax(0,1fr) 180px auto auto auto}.md\:grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.md\:grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,1fr\)_minmax\(130px\,\.6fr\)\]{grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(130px,.6fr)}.md\:grid-cols-\[minmax\(0\,1fr\)_minmax\(130px\,\.6fr\)\]{grid-template-columns:minmax(0,1fr) minmax(130px,.6fr)}.md\:grid-cols-\[minmax\(120px\,\.7fr\)_minmax\(150px\,1fr\)_minmax\(150px\,1fr\)_auto\]{grid-template-columns:minmax(120px,.7fr) minmax(150px,1fr) minmax(150px,1fr) auto}.md\:grid-cols-\[minmax\(140px\,\.8fr\)_minmax\(150px\,1fr\)_minmax\(150px\,1fr\)_minmax\(120px\,\.7fr\)_auto\]{grid-template-columns:minmax(140px,.8fr) minmax(150px,1fr) minmax(150px,1fr) minmax(120px,.7fr) auto}.md\:grid-cols-\[minmax\(140px\,\.75fr\)_minmax\(220px\,1\.25fr\)_auto_auto_auto\]{grid-template-columns:minmax(140px,.75fr) minmax(220px,1.25fr) auto auto auto}.md\:grid-cols-\[minmax\(220px\,1fr\)_auto\]{grid-template-columns:minmax(220px,1fr) auto}.md\:grid-cols-\[minmax\(260px\,1fr\)_minmax\(180px\,\.7fr\)_minmax\(130px\,\.45fr\)_minmax\(130px\,\.45fr\)_auto_auto\]{grid-template-columns:minmax(260px,1fr) minmax(180px,.7fr) minmax(130px,.45fr) minmax(130px,.45fr) auto auto}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:items-end{align-items:flex-end}.md\:items-start{align-items:flex-start}.md\:justify-between{justify-content:space-between}.md\:justify-end{justify-content:flex-end}}@media (width>=64rem){.lg\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-\[1fr_1fr_1fr\]{grid-template-columns:1fr 1fr 1fr}.lg\:grid-cols-\[minmax\(0\,1fr\)_220px_180px\]{grid-template-columns:minmax(0,1fr) 220px 180px}.lg\:items-start{align-items:flex-start}}.dark\:bg-amber-500\/35:is(.dark *){background-color:#f99c0059}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-500\/35:is(.dark *){background-color:color-mix(in oklab, var(--color-amber-500) 35%, transparent)}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--radius:.5rem;--background:oklch(16% .006 160);--foreground:oklch(94% .008 125);--card:oklch(22% .007 155);--card-foreground:oklch(94% .008 125);--popover:oklch(22% .007 155);--popover-foreground:oklch(94% .008 125);--primary:oklch(73% .11 178);--primary-foreground:oklch(17% .018 178);--secondary:oklch(28% .012 155);--secondary-foreground:oklch(94% .008 125);--muted:oklch(28% .012 155);--muted-foreground:oklch(71% .016 135);--accent:oklch(30% .042 178);--accent-foreground:oklch(88% .064 178);--destructive:oklch(67% .18 23);--border:oklch(100% 0 0/.14);--input:oklch(100% 0 0/.16);--ring:oklch(73% .11 178)}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false} diff --git a/apps/api/src/five08/backend/static/dashboard/assets/index-DUbmN0NW.js b/apps/api/src/five08/backend/static/dashboard/assets/index-DUbmN0NW.js deleted file mode 100644 index 38e1a73c..00000000 --- a/apps/api/src/five08/backend/static/dashboard/assets/index-DUbmN0NW.js +++ /dev/null @@ -1,9 +0,0 @@ -var e=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var t=e((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var ee=Array.isArray;function te(){}var S={H:null,A:null,T:null,S:null},C=Object.prototype.hasOwnProperty;function ne(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function re(e,t){return ne(e.type,t,e.props)}function ie(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function ae(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var oe=/\/+/g;function se(e,t){return typeof e==`object`&&e&&e.key!=null?ae(``+e.key):t.toString(36)}function ce(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(te,te):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function w(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,w(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+se(e,0):a,ee(o)?(i=``,c!=null&&(i=c.replace(oe,`$&/`)+`/`),w(o,r,i,``,function(e){return e})):o!=null&&(ie(o)&&(o=re(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(oe,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(ee(e))for(var u=0;u{n.exports=t()})),r=(...e)=>e.filter((e,t,n)=>!!e&&e.trim()!==``&&n.indexOf(e)===t).join(` `).trim(),i=e=>e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).toLowerCase(),a=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,t,n)=>n?n.toUpperCase():t.toLowerCase()),o=e=>{let t=a(e);return t.charAt(0).toUpperCase()+t.slice(1)},s={xmlns:`http://www.w3.org/2000/svg`,width:24,height:24,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:2,strokeLinecap:`round`,strokeLinejoin:`round`},c=e=>{for(let t in e)if(t.startsWith(`aria-`)||t===`role`||t===`title`)return!0;return!1},l=n(),u=(0,l.createContext)({}),d=()=>(0,l.useContext)(u),f=(0,l.forwardRef)(({color:e,size:t,strokeWidth:n,absoluteStrokeWidth:i,className:a=``,children:o,iconNode:u,...f},p)=>{let{size:m=24,strokeWidth:h=2,absoluteStrokeWidth:g=!1,color:_=`currentColor`,className:v=``}=d()??{},y=i??g?Number(n??h)*24/Number(t??m):n??h;return(0,l.createElement)(`svg`,{ref:p,...s,width:t??m??s.width,height:t??m??s.height,stroke:e??_,strokeWidth:y,className:r(`lucide`,v,a),...!o&&!c(f)&&{"aria-hidden":`true`},...f},[...u.map(([e,t])=>(0,l.createElement)(e,t)),...Array.isArray(o)?o:[o]])}),p=(e,t)=>{let n=(0,l.forwardRef)(({className:n,...a},s)=>(0,l.createElement)(f,{ref:s,iconNode:t,className:r(`lucide-${i(o(e))}`,`lucide-${e}`,n),...a}));return n.displayName=o(e),n},m=p(`activity`,[[`path`,{d:`M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2`,key:`169zse`}]]),h=p(`arrow-left`,[[`path`,{d:`m12 19-7-7 7-7`,key:`1l729n`}],[`path`,{d:`M19 12H5`,key:`x3x0zl`}]]),g=p(`bell`,[[`path`,{d:`M10.268 21a2 2 0 0 0 3.464 0`,key:`vwvbt9`}],[`path`,{d:`M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326`,key:`11g9vi`}]]),_=p(`briefcase-business`,[[`path`,{d:`M12 12h.01`,key:`1mp3jc`}],[`path`,{d:`M16 6V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2`,key:`1ksdt3`}],[`path`,{d:`M22 13a18.15 18.15 0 0 1-20 0`,key:`12hx5q`}],[`rect`,{width:`20`,height:`14`,x:`2`,y:`6`,rx:`2`,key:`i6l2r4`}]]),v=p(`clipboard-list`,[[`rect`,{width:`8`,height:`4`,x:`8`,y:`2`,rx:`1`,ry:`1`,key:`tgr4d6`}],[`path`,{d:`M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2`,key:`116196`}],[`path`,{d:`M12 11h4`,key:`1jrz19`}],[`path`,{d:`M12 16h4`,key:`n85exb`}],[`path`,{d:`M8 11h.01`,key:`1dfujw`}],[`path`,{d:`M8 16h.01`,key:`18s6g9`}]]),y=p(`external-link`,[[`path`,{d:`M15 3h6v6`,key:`1q9fwt`}],[`path`,{d:`M10 14 21 3`,key:`gplh6r`}],[`path`,{d:`M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6`,key:`a6xqqp`}]]),b=p(`file-clock`,[[`path`,{d:`M16 22h2a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v2.85`,key:`ryk6xj`}],[`path`,{d:`M14 2v5a1 1 0 0 0 1 1h5`,key:`wfsgrz`}],[`path`,{d:`M8 14v2.2l1.6 1`,key:`6m4bie`}],[`circle`,{cx:`8`,cy:`16`,r:`6`,key:`10v15b`}]]),x=p(`folder-kanban`,[[`path`,{d:`M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z`,key:`1fr9dc`}],[`path`,{d:`M8 10v4`,key:`tgpxqk`}],[`path`,{d:`M12 10v2`,key:`hh53o1`}],[`path`,{d:`M16 10v6`,key:`1d6xys`}]]),ee=p(`log-out`,[[`path`,{d:`m16 17 5-5-5-5`,key:`1bji2h`}],[`path`,{d:`M21 12H9`,key:`dn1m92`}],[`path`,{d:`M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4`,key:`1uf3rs`}]]),te=p(`mail`,[[`path`,{d:`m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7`,key:`132q7q`}],[`rect`,{x:`2`,y:`4`,width:`20`,height:`16`,rx:`2`,key:`izxlao`}]]),S=p(`plus`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`M12 5v14`,key:`s699le`}]]),C=p(`refresh-cw`,[[`path`,{d:`M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8`,key:`v9h5vc`}],[`path`,{d:`M21 3v5h-5`,key:`1q7to0`}],[`path`,{d:`M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16`,key:`3uifl3`}],[`path`,{d:`M8 16H3v5`,key:`1cv678`}]]),ne=p(`search`,[[`path`,{d:`m21 21-4.34-4.34`,key:`14j7rj`}],[`circle`,{cx:`11`,cy:`11`,r:`8`,key:`4ej97u`}]]),re=p(`send`,[[`path`,{d:`M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z`,key:`1ffxy3`}],[`path`,{d:`m21.854 2.147-10.94 10.939`,key:`12cjpa`}]]),ie=p(`settings`,[[`path`,{d:`M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915`,key:`1i5ecw`}],[`circle`,{cx:`12`,cy:`12`,r:`3`,key:`1v7zrd`}]]),ae=p(`shield-check`,[[`path`,{d:`M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z`,key:`oel41y`}],[`path`,{d:`m9 12 2 2 4-4`,key:`dzmm74`}]]),oe=p(`user-minus`,[[`path`,{d:`M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2`,key:`1yyitq`}],[`circle`,{cx:`9`,cy:`7`,r:`4`,key:`nufk8`}],[`line`,{x1:`22`,x2:`16`,y1:`11`,y2:`11`,key:`1shjgl`}]]),se=p(`user-plus`,[[`path`,{d:`M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2`,key:`1yyitq`}],[`circle`,{cx:`9`,cy:`7`,r:`4`,key:`nufk8`}],[`line`,{x1:`19`,x2:`19`,y1:`8`,y2:`14`,key:`1bvyxn`}],[`line`,{x1:`22`,x2:`16`,y1:`11`,y2:`11`,key:`1shjgl`}]]),ce=p(`users`,[[`path`,{d:`M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2`,key:`1yyitq`}],[`path`,{d:`M16 3.128a4 4 0 0 1 0 7.744`,key:`16gr8j`}],[`path`,{d:`M22 21v-2a4 4 0 0 0-3-3.87`,key:`kshegd`}],[`circle`,{cx:`9`,cy:`7`,r:`4`,key:`nufk8`}]]),w=p(`x`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]),T=e((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,ee||(ee=!0,ie());else{var t=n(l);t!==null&&se(x,t.startTime-e)}}var ee=!1,te=-1,S=5,C=-1;function ne(){return g?!0:!(e.unstable_now()-Ct&&ne());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&se(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?ie():ee=!1}}}var ie;if(typeof y==`function`)ie=function(){y(re)};else if(typeof MessageChannel<`u`){var ae=new MessageChannel,oe=ae.port2;ae.port1.onmessage=re,ie=function(){oe.postMessage(null)}}else ie=function(){_(re,0)};function se(t,n){te=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(te),te=-1):h=!0,se(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,ee||(ee=!0,ie()))),r},e.unstable_shouldYield=ne,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),le=e(((e,t)=>{t.exports=T()})),E=e((e=>{var t=n();function r(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=E()})),de=e((e=>{var t=le(),r=n(),i=ue();function a(e){var t=`https://react.dev/errors/`+e;if(1pe||(e.current=fe[pe],fe[pe]=null,pe--)}function k(e,t){pe++,fe[pe]=e.current,e.current=t}var me=D(null),A=D(null),he=D(null),ge=D(null);function _e(e,t){switch(k(he,t),k(A,e),k(me,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}O(me),k(me,e)}function ve(){O(me),O(A),O(he)}function ye(e){e.memoizedState!==null&&k(ge,e);var t=me.current,n=Hd(t,e.type);t!==n&&(k(A,e),k(me,n))}function be(e){A.current===e&&(O(me),O(A)),ge.current===e&&(O(ge),Qf._currentValue=de)}var xe,Se;function Ce(e){if(xe===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);xe=t&&t[1]||``,Se=-1)`:-1i||c[r]!==l[i]){var u=` -`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{we=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?Ce(n):``}function Ee(e,t){switch(e.tag){case 26:case 27:case 5:return Ce(e.type);case 16:return Ce(`Lazy`);case 13:return e.child!==t&&t!==null?Ce(`Suspense Fallback`):Ce(`Suspense`);case 19:return Ce(`SuspenseList`);case 0:case 15:return Te(e.type,!1);case 11:return Te(e.type.render,!1);case 1:return Te(e.type,!0);case 31:return Ce(`Activity`);default:return``}}function De(e){try{var t=``,n=null;do t+=Ee(e,n),n=e,e=e.return;while(e);return t}catch(e){return` -Error generating stack: `+e.message+` -`+e.stack}}var Oe=Object.prototype.hasOwnProperty,ke=t.unstable_scheduleCallback,Ae=t.unstable_cancelCallback,je=t.unstable_shouldYield,Me=t.unstable_requestPaint,Ne=t.unstable_now,Pe=t.unstable_getCurrentPriorityLevel,Fe=t.unstable_ImmediatePriority,Ie=t.unstable_UserBlockingPriority,Le=t.unstable_NormalPriority,Re=t.unstable_LowPriority,ze=t.unstable_IdlePriority,Be=t.log,Ve=t.unstable_setDisableYieldValue,He=null,Ue=null;function We(e){if(typeof Be==`function`&&Ve(e),Ue&&typeof Ue.setStrictMode==`function`)try{Ue.setStrictMode(He,e)}catch{}}var Ge=Math.clz32?Math.clz32:Je,Ke=Math.log,qe=Math.LN2;function Je(e){return e>>>=0,e===0?32:31-(Ke(e)/qe|0)|0}var Ye=256,Xe=262144,Ze=4194304;function Qe(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function $e(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Qe(n))):i=Qe(o):i=Qe(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Qe(n))):i=Qe(o)):i=Qe(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function et(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function tt(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function nt(){var e=Ze;return Ze<<=1,!(Ze&62914560)&&(Ze=4194304),e}function j(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function rt(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function it(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),tn=!1;if(en)try{var nn={};Object.defineProperty(nn,`passive`,{get:function(){tn=!0}}),window.addEventListener(`test`,nn,nn),window.removeEventListener(`test`,nn,nn)}catch{tn=!1}var rn=null,an=null,on=null;function sn(){if(on)return on;var e,t=an,n=t.length,r,i=`value`in rn?rn.value:rn.textContent,a=i.length;for(e=0;e=Rn),Vn=` `,Hn=!1;function Un(e,t){switch(e){case`keyup`:return In.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function Wn(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var Gn=!1;function Kn(e,t){switch(e){case`compositionend`:return Wn(t);case`keypress`:return t.which===32?(Hn=!0,Vn):null;case`textInput`:return e=t.data,e===Vn&&Hn?null:e;default:return null}}function qn(e,t){if(Gn)return e===`compositionend`||!Ln&&Un(e,t)?(e=sn(),on=an=rn=null,Gn=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=hr(n)}}function _r(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?_r(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function vr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=R(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=R(e.document)}return t}function yr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var br=en&&`documentMode`in document&&11>=document.documentMode,xr=null,Sr=null,Cr=null,wr=!1;function Tr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;wr||xr==null||xr!==R(r)||(r=xr,`selectionStart`in r&&yr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Cr&&mr(Cr,r)||(Cr=r,r=Td(Sr,`onSelect`),0>=o,i-=o,_i=1<<32-Ge(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),J&&yi(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(i,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(i,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(i,h),J&&yi(i,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(i,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return J&&yi(i,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,i,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(i,e)}),J&&yi(i,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===_&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case h:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===_){if(r.tag===7){n(e,r.sibling),c=i(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===ne&&ya(l)===r.type){n(e,r.sibling),c=i(r,o.props),Ea(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===_?(c=ii(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=ri(o.type,o.key,o.props,null,e.mode,c),Ea(c,o),c.return=e,e=c)}return s(e);case g:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=i(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=si(o,e.mode,c),c.return=e,e=c}return s(e);case ne:return o=ya(o),b(e,r,o,c)}if(w(o))return v(e,r,o,c);if(oe(o)){if(l=oe(o),typeof l!=`function`)throw Error(a(150));return o=l.call(o),y(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Ta(o),c);if(o.$$typeof===x)return b(e,r,qi(e,o),c);Da(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=i(r,o),c.return=e,e=c):(n(e,r),c=ai(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{wa=0;var i=b(e,t,n,r);return Ca=null,i}catch(t){if(t===pa||t===ha)throw t;var a=$r(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var ka=Oa(!0),Aa=Oa(!1),ja=!1;function Ma(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Na(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Pa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Fa(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,X&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=Xr(e),Yr(e,null,n),t}return Kr(e,r,t,n),Xr(e)}function Ia(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ot(e,n)}}function La(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ra=!1;function za(){if(Ra){var e=ia;if(e!==null)throw e}}function Ba(e,t,n,r){Ra=!1;var i=e.updateQueue;ja=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,m=f!==s.lane;if(m?(Q&f)===f:(r&f)===f){f!==0&&f===ra&&(Ra=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=p({},d,f);break a;case 2:ja=!0}}f=s.callback,f!==null&&(e.flags|=64,m&&(e.flags|=8192),m=i.callbacks,m===null?i.callbacks=[f]:m.push(f))}else m={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=m,c=d):u=u.next=m,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;m=s,s=m.next,m.next=null,i.lastBaseUpdate=m,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Ul|=o,e.lanes=o,e.memoizedState=d}}function Va(e,t){if(typeof e!=`function`)throw Error(a(191,e));e.call(t)}function Ha(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=T.T,s={};T.T=s,Os(e,!1,t,n);try{var c=i(),l=T.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Ds(e,t,sa(c,r),du(e)):Ds(e,t,r,du(e))}catch(n){Ds(e,t,{then:function(){},status:`rejected`,reason:n},du())}finally{E.p=a,o!==null&&s.types!==null&&(o.types=s.types),T.T=o}}function _s(){}function vs(e,t,n,r){if(e.tag!==5)throw Error(a(476));var i=ys(e).queue;gs(e,i,t,de,n===null?_s:function(){return bs(e),n(r)})}function ys(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:de,baseState:de,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ko,lastRenderedState:de},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ko,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function bs(e){var t=ys(e);t.next===null&&(t=e.alternate.memoizedState),Ds(e,t.next.queue,{},du())}function xs(){return Ki(Qf)}function Ss(){return wo().memoizedState}function Cs(){return wo().memoizedState}function ws(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=du();e=Pa(n);var r=Fa(t,e,n);r!==null&&(pu(r,t,n),Ia(r,t,n)),t={cache:$i()},e.payload=t;return}t=t.return}}function Ts(e,t,n){var r=du();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},ks(e)?As(t,n):(n=qr(e,t,n,r),n!==null&&(pu(n,e,r),js(n,t,r)))}function Es(e,t,n){Ds(e,t,n,du())}function Ds(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(ks(e))As(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,pr(s,o))return Kr(e,t,i,0),Fl===null&&Gr(),!1}catch{}if(n=qr(e,t,i,r),n!==null)return pu(n,e,r),js(n,t,r),!0}return!1}function Os(e,t,n,r){if(r={lane:2,revertLane:ud(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},ks(e)){if(t)throw Error(a(479))}else t=qr(e,n,r,2),t!==null&&pu(t,e,2)}function ks(e){var t=e.alternate;return e===Y||t!==null&&t===Y}function As(e,t){so=oo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function js(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ot(e,n)}}var Ms={readContext:Ki,use:Do,useCallback:mo,useContext:mo,useEffect:mo,useImperativeHandle:mo,useLayoutEffect:mo,useInsertionEffect:mo,useMemo:mo,useReducer:mo,useRef:mo,useState:mo,useDebugValue:mo,useDeferredValue:mo,useTransition:mo,useSyncExternalStore:mo,useId:mo,useHostTransitionStatus:mo,useFormState:mo,useActionState:mo,useOptimistic:mo,useMemoCache:mo,useCacheRefresh:mo};Ms.useEffectEvent=mo;var Ns={readContext:Ki,use:Do,useCallback:function(e,t){return Co().memoizedState=[e,t===void 0?null:t],e},useContext:Ki,useEffect:rs,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),ts(4194308,4,ls.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ts(4194308,4,e,t)},useInsertionEffect:function(e,t){ts(4,2,e,t)},useMemo:function(e,t){var n=Co();t=t===void 0?null:t;var r=e();if(co){We(!0);try{e()}finally{We(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Co();if(n!==void 0){var i=n(t);if(co){We(!0);try{n(t)}finally{We(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Ts.bind(null,Y,e),[r.memoizedState,e]},useRef:function(e){var t=Co();return e={current:e},t.memoizedState=e},useState:function(e){e=zo(e);var t=e.queue,n=Es.bind(null,Y,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:ds,useDeferredValue:function(e,t){return ms(Co(),e,t)},useTransition:function(){var e=zo(!1);return e=gs.bind(null,Y,e.queue,!0,!1),Co().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=Y,i=Co();if(J){if(n===void 0)throw Error(a(407));n=n()}else{if(n=t(),Fl===null)throw Error(a(349));Q&127||Po(r,t,n)}i.memoizedState=n;var o={value:n,getSnapshot:t};return i.queue=o,rs(Io.bind(null,r,o,e),[e]),r.flags|=2048,$o(9,{destroy:void 0},Fo.bind(null,r,o,n,t),null),n},useId:function(){var e=Co(),t=Fl.identifierPrefix;if(J){var n=vi,r=_i;n=(r&~(1<<32-Ge(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=lo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(i,{is:r.is}):s.createElement(i)}}o[pt]=t,o[M]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Pd(o,i,r),i){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Dc(t)}}return Mc(t),Oc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Dc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(a(166));if(e=he.current,Mi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,i=wi,i!==null)switch(i.tag){case 27:case 5:r=i.memoizedProps}e[pt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||jd(e.nodeValue,n)),e||ki(t,!0)}else e=Bd(e).createTextNode(r),e[pt]=t,t.stateNode=e}return Mc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Mi(t),n!==null){if(e===null){if(!r)throw Error(a(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(a(557));e[pt]=t}else Ni(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Mc(t),e=!1}else n=Pi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(eo(t),t):(eo(t),null);if(t.flags&128)throw Error(a(558))}return Mc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(i=Mi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!i)throw Error(a(318));if(i=t.memoizedState,i=i===null?null:i.dehydrated,!i)throw Error(a(317));i[pt]=t}else Ni(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Mc(t),i=!1}else i=Pi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=i),i=!0;if(!i)return t.flags&256?(eo(t),t):(eo(t),null)}return eo(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,i=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(i=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==i&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Ac(t,t.updateQueue),Mc(t),null);case 4:return ve(),e===null&&xd(t.stateNode.containerInfo),Mc(t),null;case 10:return Bi(t.type),Mc(t),null;case 19:if(O(to),r=t.memoizedState,r===null)return Mc(t),null;if(i=(t.flags&128)!=0,o=r.rendering,o===null)if(i)jc(r,!1);else{if(Hl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=no(e),o!==null){for(t.flags|=128,jc(r,!1),e=o.updateQueue,t.updateQueue=e,Ac(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)ni(n,e),n=n.sibling;return k(to,to.current&1|2),J&&yi(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Ne()>$l&&(t.flags|=128,i=!0,jc(r,!1),t.lanes=4194304)}else{if(!i)if(e=no(o),e!==null){if(t.flags|=128,i=!0,e=e.updateQueue,t.updateQueue=e,Ac(t,e),jc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!J)return Mc(t),null}else 2*Ne()-r.renderingStartTime>$l&&n!==536870912&&(t.flags|=128,i=!0,jc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(Mc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Ne(),e.sibling=null,n=to.current,k(to,i?n&1|2:n&1),J&&yi(t,r.treeForkCount),e);case 22:case 23:return eo(t),qa(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(Mc(t),t.subtreeFlags&6&&(t.flags|=8192)):Mc(t),n=t.updateQueue,n!==null&&Ac(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&O(la),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Bi(Qi),Mc(t),null;case 25:return null;case 30:return null}throw Error(a(156,t.tag))}function Pc(e,t){switch(Si(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Bi(Qi),ve(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return be(t),null;case 31:if(t.memoizedState!==null){if(eo(t),t.alternate===null)throw Error(a(340));Ni()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(eo(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(a(340));Ni()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return O(to),null;case 4:return ve(),null;case 10:return Bi(t.type),null;case 22:case 23:return eo(t),qa(),e!==null&&O(la),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Bi(Qi),null;case 25:return null;default:return null}}function Fc(e,t){switch(Si(t),t.tag){case 3:Bi(Qi),ve();break;case 26:case 27:case 5:be(t);break;case 4:ve();break;case 31:t.memoizedState!==null&&eo(t);break;case 13:eo(t);break;case 19:O(to);break;case 10:Bi(t.type);break;case 22:case 23:eo(t),qa(),e!==null&&O(la);break;case 24:Bi(Qi)}}function Ic(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Uu(t,t.return,e)}}function Lc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Uu(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Uu(t,t.return,e)}}function Rc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Ha(t,n)}catch(t){Uu(e,e.return,t)}}}function zc(e,t,n){n.props=Bs(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Uu(e,t,n)}}function Bc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Uu(e,t,n)}}function Vc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Uu(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Uu(e,t,n)}else n.current=null}function Hc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Uu(e,e.return,t)}}function Uc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[M]=t}catch(t){Uu(e,e.return,t)}}function Wc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function Gc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Wc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Kc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=K));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Kc(e,t,n),e=e.sibling;e!==null;)Kc(e,t,n),e=e.sibling}function qc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(qc(e,t,n),e=e.sibling;e!==null;)qc(e,t,n),e=e.sibling}function Jc(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[pt]=e,t[M]=n}catch(t){Uu(e,e.return,t)}}var Yc=!1,Xc=!1,Zc=!1,Qc=typeof WeakSet==`function`?WeakSet:Set,$c=null;function el(e,t){if(e=e.containerInfo,Rd=sp,e=vr(e),yr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var i=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||i!==0&&f.nodeType!==3||(c=s+i),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===i&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,$c=t;$c!==null;)if(t=$c,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,$c=e;else for(;$c!==null;){switch(t=$c,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(o,r,n),o[pt]=e,P(o),r=o;break a;case`link`:var s=Vf(`link`,`href`,i).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=gr(s,h),v=gr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,T.T=null,n=su,su=null;var o=ru,s=au;if(nu=0,iu=ru=null,au=0,X&6)throw Error(a(331));var c=X;if(X|=4,Al(o.current),Sl(o,o.current,s,n),X=c,rd(0,!1),Ue&&typeof Ue.onPostCommitFiberRoot==`function`)try{Ue.onPostCommitFiberRoot(He,o)}catch{}return!0}finally{E.p=i,T.T=r,zu(e,t)}}function Hu(e,t,n){t=li(n,t),t=Ks(e.stateNode,t,2),e=Fa(e,t,2),e!==null&&(rt(e,2),nd(e))}function Uu(e,t,n){if(e.tag===3)Hu(e,e,n);else for(;t!==null;){if(t.tag===3){Hu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(tu===null||!tu.has(r))){e=li(n,e),n=qs(2),r=Fa(t,n,2),r!==null&&(Js(n,r,t,e),rt(r,2),nd(r));break}}t=t.return}}function Wu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Pl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Bl=!0,i.add(n),e=Gu.bind(null,e,t,n),t.then(e,e))}function Gu(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,Fl===e&&(Q&n)===n&&(Hl===4||Hl===3&&(Q&62914560)===Q&&300>Ne()-Zl?!(X&2)&&bu(e,0):Gl|=n,ql===Q&&(ql=0)),nd(e)}function Ku(e,t){t===0&&(t=nt()),e=Jr(e,t),e!==null&&(rt(e,t),nd(e))}function qu(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Ku(e,n)}function Ju(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,i=e.memoizedState;i!==null&&(n=i.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(a(314))}r!==null&&r.delete(t),Ku(e,n)}function Yu(e,t){return ke(e,t)}var Xu=null,Zu=null,Qu=!1,$u=!1,ed=!1,td=0;function nd(e){e!==Zu&&e.next===null&&(Zu===null?Xu=Zu=e:Zu=Zu.next=e),$u=!0,Qu||(Qu=!0,ld())}function rd(e,t){if(!ed&&$u){ed=!0;do for(var n=!1,r=Xu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Ge(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,cd(r,a))}else a=Q,a=$e(r,r===Fl?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||et(r,a)||(n=!0,cd(r,a));r=r.next}while(n);ed=!1}}function id(){ad()}function ad(){$u=Qu=!1;var e=0;td!==0&&Gd()&&(e=td);for(var t=Ne(),n=null,r=Xu;r!==null;){var i=r.next,a=od(r,t);a===0?(r.next=null,n===null?Xu=i:n.next=i,i===null&&(Zu=n)):(n=r,(e!==0||a&3)&&($u=!0)),r=i}nu!==0&&nu!==5||rd(e,!1),td!==0&&(td=0)}function od(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=z(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),P(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+z(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+z(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+z(n.imageSizes)+`"]`)):i+=`[href="`+z(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=p({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),P(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+z(r)+`"][href="`+z(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=p({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),P(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=Ct(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=p({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);P(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=Ct(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=p({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),P(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=Ct(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=p({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),P(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var i=(i=he.current)?gf(i):null;if(!i)throw Error(a(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=Ct(i).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var o=Ct(i).hoistableStyles,s=o.get(e);if(s||(i=i.ownerDocument||i,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=i.querySelector(jf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),o||Nf(i,e,n,s.state))),t&&r===null)throw Error(a(528,``));return s}if(t&&r!==null)throw Error(a(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=Ct(i).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(a(444,e))}}function Af(e){return`href="`+z(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return p({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),P(t),e.head.appendChild(t))}function Pf(e){return`[src="`+z(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+z(n.href)+`"]`);if(r)return t.instance=r,P(r),r;var i=p({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),P(r),Pd(r,`style`,i),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:i=Af(n.href);var o=e.querySelector(jf(i));if(o)return t.state.loading|=4,t.instance=o,P(o),o;r=Mf(n),(i=mf.get(i))&&Rf(r,i),o=(e.ownerDocument||e).createElement(`link`),P(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Pd(o,`link`,r),t.state.loading|=4,Lf(o,n.precedence,e),t.instance=o;case`script`:return o=Pf(n.src),(i=e.querySelector(Ff(o)))?(t.instance=i,P(i),i):(r=n,(i=mf.get(o))&&(r=p({},n),zf(r,i)),e=e.ownerDocument||e,i=e.createElement(`script`),P(i),Pd(i,`link`,r),e.head.appendChild(i),t.instance=i);case`void`:return null;default:throw Error(a(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,P(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),P(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=de()}))();function pe(e){var t,n,r=``;if(typeof e==`string`||typeof e==`number`)r+=e;else if(typeof e==`object`)if(Array.isArray(e)){var i=e.length;for(t=0;ttypeof e==`boolean`?`${e}`:e===0?`0`:e,k=D,me=(e,t)=>n=>{if(t?.variants==null)return k(e,n?.class,n?.className);let{variants:r,defaultVariants:i}=t,a=Object.keys(r).map(e=>{let t=n?.[e],a=i?.[e];if(t===null)return null;let o=O(t)||O(a);return r[e][o]}),o=n&&Object.entries(n).reduce((e,t)=>{let[n,r]=t;return r===void 0||(e[n]=r),e},{});return k(e,a,t?.compoundVariants?.reduce((e,t)=>{let{class:n,className:r,...a}=t;return Object.entries(a).every(e=>{let[t,n]=e;return Array.isArray(n)?n.includes({...i,...o}[t]):{...i,...o}[t]===n})?[...e,n,r]:e},[]),n?.class,n?.className)},A=(e,t)=>{let n=Array(e.length+t.length);for(let t=0;t({classGroupId:e,validator:t}),ge=(e=new Map,t=null,n)=>({nextPart:e,validators:t,classGroupId:n}),_e=`-`,ve=[],ye=`arbitrary..`,be=e=>{let t=Ce(e),{conflictingClassGroups:n,conflictingClassGroupModifiers:r}=e;return{getClassGroupId:e=>{if(e.startsWith(`[`)&&e.endsWith(`]`))return Se(e);let n=e.split(_e);return xe(n,+(n[0]===``&&n.length>1),t)},getConflictingClassGroupIds:(e,t)=>{if(t){let t=r[e],i=n[e];return t?i?A(i,t):t:i||ve}return n[e]||ve}}},xe=(e,t,n)=>{if(e.length-t===0)return n.classGroupId;let r=e[t],i=n.nextPart.get(r);if(i){let n=xe(e,t+1,i);if(n)return n}let a=n.validators;if(a===null)return;let o=t===0?e.join(_e):e.slice(t).join(_e),s=a.length;for(let e=0;ee.slice(1,-1).indexOf(`:`)===-1?void 0:(()=>{let t=e.slice(1,-1),n=t.indexOf(`:`),r=t.slice(0,n);return r?ye+r:void 0})(),Ce=e=>{let{theme:t,classGroups:n}=e;return we(n,t)},we=(e,t)=>{let n=ge();for(let r in e){let i=e[r];Te(i,n,r,t)}return n},Te=(e,t,n,r)=>{let i=e.length;for(let a=0;a{if(typeof e==`string`){De(e,t,n);return}if(typeof e==`function`){Oe(e,t,n,r);return}ke(e,t,n,r)},De=(e,t,n)=>{let r=e===``?t:Ae(t,e);r.classGroupId=n},Oe=(e,t,n,r)=>{if(je(e)){Te(e(r),t,n,r);return}t.validators===null&&(t.validators=[]),t.validators.push(he(n,e))},ke=(e,t,n,r)=>{let i=Object.entries(e),a=i.length;for(let e=0;e{let n=e,r=t.split(_e),i=r.length;for(let e=0;e`isThemeGetter`in e&&e.isThemeGetter===!0,Me=e=>{if(e<1)return{get:()=>void 0,set:()=>{}};let t=0,n=Object.create(null),r=Object.create(null),i=(i,a)=>{n[i]=a,t++,t>e&&(t=0,r=n,n=Object.create(null))};return{get(e){let t=n[e];if(t!==void 0)return t;if((t=r[e])!==void 0)return i(e,t),t},set(e,t){e in n?n[e]=t:i(e,t)}}},Ne=`!`,Pe=`:`,Fe=[],Ie=(e,t,n,r,i)=>({modifiers:e,hasImportantModifier:t,baseClassName:n,maybePostfixModifierPosition:r,isExternal:i}),Le=e=>{let{prefix:t,experimentalParseClassName:n}=e,r=e=>{let t=[],n=0,r=0,i=0,a,o=e.length;for(let s=0;si?a-i:void 0;return Ie(t,l,c,u)};if(t){let e=t+Pe,n=r;r=t=>t.startsWith(e)?n(t.slice(e.length)):Ie(Fe,!1,t,void 0,!0)}if(n){let e=r;r=t=>n({className:t,parseClassName:e})}return r},Re=e=>{let t=new Map;return e.orderSensitiveModifiers.forEach((e,n)=>{t.set(e,1e6+n)}),e=>{let n=[],r=[];for(let i=0;i0&&(r.sort(),n.push(...r),r=[]),n.push(a)):r.push(a)}return r.length>0&&(r.sort(),n.push(...r)),n}},ze=e=>({cache:Me(e.cacheSize),parseClassName:Le(e),sortModifiers:Re(e),postfixLookupClassGroupIds:Be(e),...be(e)}),Be=e=>{let t=Object.create(null),n=e.postfixLookupClassGroups;if(n)for(let e=0;e{let{parseClassName:n,getClassGroupId:r,getConflictingClassGroupIds:i,sortModifiers:a,postfixLookupClassGroupIds:o}=t,s=[],c=e.trim().split(Ve),l=``;for(let e=c.length-1;e>=0;--e){let t=c[e],{isExternal:u,modifiers:d,hasImportantModifier:f,baseClassName:p,maybePostfixModifierPosition:m}=n(t);if(u){l=t+(l.length>0?` `+l:l);continue}let h=!!m,g;if(h){g=r(p.substring(0,m));let e=g&&o[g]?r(p):void 0;e&&e!==g&&(g=e,h=!1)}else g=r(p);if(!g){if(!h){l=t+(l.length>0?` `+l:l);continue}if(g=r(p),!g){l=t+(l.length>0?` `+l:l);continue}h=!1}let _=d.length===0?``:d.length===1?d[0]:a(d).join(`:`),v=f?_+Ne:_,y=v+g;if(s.indexOf(y)>-1)continue;s.push(y);let b=i(g,h);for(let e=0;e0?` `+l:l)}return l},Ue=(...e)=>{let t=0,n,r,i=``;for(;t{if(typeof e==`string`)return e;let t,n=``;for(let r=0;r{let n,r,i,a,o=o=>(n=ze(t.reduce((e,t)=>t(e),e())),r=n.cache.get,i=n.cache.set,a=s,s(o)),s=e=>{let t=r(e);if(t)return t;let a=He(e,n);return i(e,a),a};return a=o,(...e)=>a(Ue(...e))},Ke=[],qe=e=>{let t=t=>t[e]||Ke;return t.isThemeGetter=!0,t},Je=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,Ye=/^\((?:(\w[\w-]*):)?(.+)\)$/i,Xe=/^\d+(?:\.\d+)?\/\d+(?:\.\d+)?$/,Ze=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,Qe=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,$e=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,et=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,tt=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,nt=e=>Xe.test(e),j=e=>!!e&&!Number.isNaN(Number(e)),rt=e=>!!e&&Number.isInteger(Number(e)),it=e=>e.endsWith(`%`)&&j(e.slice(0,-1)),at=e=>Ze.test(e),ot=()=>!0,st=e=>Qe.test(e)&&!$e.test(e),ct=()=>!1,lt=e=>et.test(e),ut=e=>tt.test(e),dt=e=>!M(e)&&!N(e),ft=e=>e.startsWith(`@container`)&&(e[10]===`/`&&e[11]!==void 0||e[11]===`s`&&e[16]!==void 0&&e.startsWith(`-size/`,10)||e[11]===`n`&&e[18]!==void 0&&e.startsWith(`-normal/`,10)),pt=e=>Tt(e,kt,ct),M=e=>Je.test(e),mt=e=>Tt(e,At,st),ht=e=>Tt(e,jt,j),gt=e=>Tt(e,Nt,ot),_t=e=>Tt(e,Mt,ct),vt=e=>Tt(e,Dt,ct),yt=e=>Tt(e,Ot,ut),bt=e=>Tt(e,Pt,lt),N=e=>Ye.test(e),xt=e=>Et(e,At),St=e=>Et(e,Mt),Ct=e=>Et(e,Dt),P=e=>Et(e,kt),F=e=>Et(e,Ot),I=e=>Et(e,Pt,!0),wt=e=>Et(e,Nt,!0),Tt=(e,t,n)=>{let r=Je.exec(e);return r?r[1]?t(r[1]):n(r[2]):!1},Et=(e,t,n=!1)=>{let r=Ye.exec(e);return r?r[1]?t(r[1]):n:!1},Dt=e=>e===`position`||e===`percentage`,Ot=e=>e===`image`||e===`url`,kt=e=>e===`length`||e===`size`||e===`bg-size`,At=e=>e===`length`,jt=e=>e===`number`,Mt=e=>e===`family-name`,Nt=e=>e===`number`||e===`weight`,Pt=e=>e===`shadow`,Ft=Ge(()=>{let e=qe(`color`),t=qe(`font`),n=qe(`text`),r=qe(`font-weight`),i=qe(`tracking`),a=qe(`leading`),o=qe(`breakpoint`),s=qe(`container`),c=qe(`spacing`),l=qe(`radius`),u=qe(`shadow`),d=qe(`inset-shadow`),f=qe(`text-shadow`),p=qe(`drop-shadow`),m=qe(`blur`),h=qe(`perspective`),g=qe(`aspect`),_=qe(`ease`),v=qe(`animate`),y=()=>[`auto`,`avoid`,`all`,`avoid-page`,`page`,`left`,`right`,`column`],b=()=>[`center`,`top`,`bottom`,`left`,`right`,`top-left`,`left-top`,`top-right`,`right-top`,`bottom-right`,`right-bottom`,`bottom-left`,`left-bottom`],x=()=>[...b(),N,M],ee=()=>[`auto`,`hidden`,`clip`,`visible`,`scroll`],te=()=>[`auto`,`contain`,`none`],S=()=>[N,M,c],C=()=>[nt,`full`,`auto`,...S()],ne=()=>[rt,`none`,`subgrid`,N,M],re=()=>[`auto`,{span:[`full`,rt,N,M]},rt,N,M],ie=()=>[rt,`auto`,N,M],ae=()=>[`auto`,`min`,`max`,`fr`,N,M],oe=()=>[`start`,`end`,`center`,`between`,`around`,`evenly`,`stretch`,`baseline`,`center-safe`,`end-safe`],se=()=>[`start`,`end`,`center`,`stretch`,`center-safe`,`end-safe`],ce=()=>[`auto`,...S()],w=()=>[nt,`auto`,`full`,`dvw`,`dvh`,`lvw`,`lvh`,`svw`,`svh`,`min`,`max`,`fit`,...S()],T=()=>[nt,`screen`,`full`,`dvw`,`lvw`,`svw`,`min`,`max`,`fit`,...S()],le=()=>[nt,`screen`,`full`,`lh`,`dvh`,`lvh`,`svh`,`min`,`max`,`fit`,...S()],E=()=>[e,N,M],ue=()=>[...b(),Ct,vt,{position:[N,M]}],de=()=>[`no-repeat`,{repeat:[``,`x`,`y`,`space`,`round`]}],fe=()=>[`auto`,`cover`,`contain`,P,pt,{size:[N,M]}],pe=()=>[it,xt,mt],D=()=>[``,`none`,`full`,l,N,M],O=()=>[``,j,xt,mt],k=()=>[`solid`,`dashed`,`dotted`,`double`],me=()=>[`normal`,`multiply`,`screen`,`overlay`,`darken`,`lighten`,`color-dodge`,`color-burn`,`hard-light`,`soft-light`,`difference`,`exclusion`,`hue`,`saturation`,`color`,`luminosity`],A=()=>[j,it,Ct,vt],he=()=>[``,`none`,m,N,M],ge=()=>[`none`,j,N,M],_e=()=>[`none`,j,N,M],ve=()=>[j,N,M],ye=()=>[nt,`full`,...S()];return{cacheSize:500,theme:{animate:[`spin`,`ping`,`pulse`,`bounce`],aspect:[`video`],blur:[at],breakpoint:[at],color:[ot],container:[at],"drop-shadow":[at],ease:[`in`,`out`,`in-out`],font:[dt],"font-weight":[`thin`,`extralight`,`light`,`normal`,`medium`,`semibold`,`bold`,`extrabold`,`black`],"inset-shadow":[at],leading:[`none`,`tight`,`snug`,`normal`,`relaxed`,`loose`],perspective:[`dramatic`,`near`,`normal`,`midrange`,`distant`,`none`],radius:[at],shadow:[at],spacing:[`px`,j],text:[at],"text-shadow":[at],tracking:[`tighter`,`tight`,`normal`,`wide`,`wider`,`widest`]},classGroups:{aspect:[{aspect:[`auto`,`square`,nt,M,N,g]}],container:[`container`],"container-type":[{"@container":[``,`normal`,`size`,N,M]}],"container-named":[ft],columns:[{columns:[j,M,N,s]}],"break-after":[{"break-after":y()}],"break-before":[{"break-before":y()}],"break-inside":[{"break-inside":[`auto`,`avoid`,`avoid-page`,`avoid-column`]}],"box-decoration":[{"box-decoration":[`slice`,`clone`]}],box:[{box:[`border`,`content`]}],display:[`block`,`inline-block`,`inline`,`flex`,`inline-flex`,`table`,`inline-table`,`table-caption`,`table-cell`,`table-column`,`table-column-group`,`table-footer-group`,`table-header-group`,`table-row-group`,`table-row`,`flow-root`,`grid`,`inline-grid`,`contents`,`list-item`,`hidden`],sr:[`sr-only`,`not-sr-only`],float:[{float:[`right`,`left`,`none`,`start`,`end`]}],clear:[{clear:[`left`,`right`,`both`,`none`,`start`,`end`]}],isolation:[`isolate`,`isolation-auto`],"object-fit":[{object:[`contain`,`cover`,`fill`,`none`,`scale-down`]}],"object-position":[{object:x()}],overflow:[{overflow:ee()}],"overflow-x":[{"overflow-x":ee()}],"overflow-y":[{"overflow-y":ee()}],overscroll:[{overscroll:te()}],"overscroll-x":[{"overscroll-x":te()}],"overscroll-y":[{"overscroll-y":te()}],position:[`static`,`fixed`,`absolute`,`relative`,`sticky`],inset:[{inset:C()}],"inset-x":[{"inset-x":C()}],"inset-y":[{"inset-y":C()}],start:[{"inset-s":C(),start:C()}],end:[{"inset-e":C(),end:C()}],"inset-bs":[{"inset-bs":C()}],"inset-be":[{"inset-be":C()}],top:[{top:C()}],right:[{right:C()}],bottom:[{bottom:C()}],left:[{left:C()}],visibility:[`visible`,`invisible`,`collapse`],z:[{z:[rt,`auto`,N,M]}],basis:[{basis:[nt,`full`,`auto`,s,...S()]}],"flex-direction":[{flex:[`row`,`row-reverse`,`col`,`col-reverse`]}],"flex-wrap":[{flex:[`nowrap`,`wrap`,`wrap-reverse`]}],flex:[{flex:[j,nt,`auto`,`initial`,`none`,M]}],grow:[{grow:[``,j,N,M]}],shrink:[{shrink:[``,j,N,M]}],order:[{order:[rt,`first`,`last`,`none`,N,M]}],"grid-cols":[{"grid-cols":ne()}],"col-start-end":[{col:re()}],"col-start":[{"col-start":ie()}],"col-end":[{"col-end":ie()}],"grid-rows":[{"grid-rows":ne()}],"row-start-end":[{row:re()}],"row-start":[{"row-start":ie()}],"row-end":[{"row-end":ie()}],"grid-flow":[{"grid-flow":[`row`,`col`,`dense`,`row-dense`,`col-dense`]}],"auto-cols":[{"auto-cols":ae()}],"auto-rows":[{"auto-rows":ae()}],gap:[{gap:S()}],"gap-x":[{"gap-x":S()}],"gap-y":[{"gap-y":S()}],"justify-content":[{justify:[...oe(),`normal`]}],"justify-items":[{"justify-items":[...se(),`normal`]}],"justify-self":[{"justify-self":[`auto`,...se()]}],"align-content":[{content:[`normal`,...oe()]}],"align-items":[{items:[...se(),{baseline:[``,`last`]}]}],"align-self":[{self:[`auto`,...se(),{baseline:[``,`last`]}]}],"place-content":[{"place-content":oe()}],"place-items":[{"place-items":[...se(),`baseline`]}],"place-self":[{"place-self":[`auto`,...se()]}],p:[{p:S()}],px:[{px:S()}],py:[{py:S()}],ps:[{ps:S()}],pe:[{pe:S()}],pbs:[{pbs:S()}],pbe:[{pbe:S()}],pt:[{pt:S()}],pr:[{pr:S()}],pb:[{pb:S()}],pl:[{pl:S()}],m:[{m:ce()}],mx:[{mx:ce()}],my:[{my:ce()}],ms:[{ms:ce()}],me:[{me:ce()}],mbs:[{mbs:ce()}],mbe:[{mbe:ce()}],mt:[{mt:ce()}],mr:[{mr:ce()}],mb:[{mb:ce()}],ml:[{ml:ce()}],"space-x":[{"space-x":S()}],"space-x-reverse":[`space-x-reverse`],"space-y":[{"space-y":S()}],"space-y-reverse":[`space-y-reverse`],size:[{size:w()}],"inline-size":[{inline:[`auto`,...T()]}],"min-inline-size":[{"min-inline":[`auto`,...T()]}],"max-inline-size":[{"max-inline":[`none`,...T()]}],"block-size":[{block:[`auto`,...le()]}],"min-block-size":[{"min-block":[`auto`,...le()]}],"max-block-size":[{"max-block":[`none`,...le()]}],w:[{w:[s,`screen`,...w()]}],"min-w":[{"min-w":[s,`screen`,`none`,...w()]}],"max-w":[{"max-w":[s,`screen`,`none`,`prose`,{screen:[o]},...w()]}],h:[{h:[`screen`,`lh`,...w()]}],"min-h":[{"min-h":[`screen`,`lh`,`none`,...w()]}],"max-h":[{"max-h":[`screen`,`lh`,...w()]}],"font-size":[{text:[`base`,n,xt,mt]}],"font-smoothing":[`antialiased`,`subpixel-antialiased`],"font-style":[`italic`,`not-italic`],"font-weight":[{font:[r,wt,gt]}],"font-stretch":[{"font-stretch":[`ultra-condensed`,`extra-condensed`,`condensed`,`semi-condensed`,`normal`,`semi-expanded`,`expanded`,`extra-expanded`,`ultra-expanded`,it,M]}],"font-family":[{font:[St,_t,t]}],"font-features":[{"font-features":[M]}],"fvn-normal":[`normal-nums`],"fvn-ordinal":[`ordinal`],"fvn-slashed-zero":[`slashed-zero`],"fvn-figure":[`lining-nums`,`oldstyle-nums`],"fvn-spacing":[`proportional-nums`,`tabular-nums`],"fvn-fraction":[`diagonal-fractions`,`stacked-fractions`],tracking:[{tracking:[i,N,M]}],"line-clamp":[{"line-clamp":[j,`none`,N,ht]}],leading:[{leading:[a,...S()]}],"list-image":[{"list-image":[`none`,N,M]}],"list-style-position":[{list:[`inside`,`outside`]}],"list-style-type":[{list:[`disc`,`decimal`,`none`,N,M]}],"text-alignment":[{text:[`left`,`center`,`right`,`justify`,`start`,`end`]}],"placeholder-color":[{placeholder:E()}],"text-color":[{text:E()}],"text-decoration":[`underline`,`overline`,`line-through`,`no-underline`],"text-decoration-style":[{decoration:[...k(),`wavy`]}],"text-decoration-thickness":[{decoration:[j,`from-font`,`auto`,N,mt]}],"text-decoration-color":[{decoration:E()}],"underline-offset":[{"underline-offset":[j,`auto`,N,M]}],"text-transform":[`uppercase`,`lowercase`,`capitalize`,`normal-case`],"text-overflow":[`truncate`,`text-ellipsis`,`text-clip`],"text-wrap":[{text:[`wrap`,`nowrap`,`balance`,`pretty`]}],indent:[{indent:S()}],"tab-size":[{tab:[rt,N,M]}],"vertical-align":[{align:[`baseline`,`top`,`middle`,`bottom`,`text-top`,`text-bottom`,`sub`,`super`,N,M]}],whitespace:[{whitespace:[`normal`,`nowrap`,`pre`,`pre-line`,`pre-wrap`,`break-spaces`]}],break:[{break:[`normal`,`words`,`all`,`keep`]}],wrap:[{wrap:[`break-word`,`anywhere`,`normal`]}],hyphens:[{hyphens:[`none`,`manual`,`auto`]}],content:[{content:[`none`,N,M]}],"bg-attachment":[{bg:[`fixed`,`local`,`scroll`]}],"bg-clip":[{"bg-clip":[`border`,`padding`,`content`,`text`]}],"bg-origin":[{"bg-origin":[`border`,`padding`,`content`]}],"bg-position":[{bg:ue()}],"bg-repeat":[{bg:de()}],"bg-size":[{bg:fe()}],"bg-image":[{bg:[`none`,{linear:[{to:[`t`,`tr`,`r`,`br`,`b`,`bl`,`l`,`tl`]},rt,N,M],radial:[``,N,M],conic:[rt,N,M]},F,yt]}],"bg-color":[{bg:E()}],"gradient-from-pos":[{from:pe()}],"gradient-via-pos":[{via:pe()}],"gradient-to-pos":[{to:pe()}],"gradient-from":[{from:E()}],"gradient-via":[{via:E()}],"gradient-to":[{to:E()}],rounded:[{rounded:D()}],"rounded-s":[{"rounded-s":D()}],"rounded-e":[{"rounded-e":D()}],"rounded-t":[{"rounded-t":D()}],"rounded-r":[{"rounded-r":D()}],"rounded-b":[{"rounded-b":D()}],"rounded-l":[{"rounded-l":D()}],"rounded-ss":[{"rounded-ss":D()}],"rounded-se":[{"rounded-se":D()}],"rounded-ee":[{"rounded-ee":D()}],"rounded-es":[{"rounded-es":D()}],"rounded-tl":[{"rounded-tl":D()}],"rounded-tr":[{"rounded-tr":D()}],"rounded-br":[{"rounded-br":D()}],"rounded-bl":[{"rounded-bl":D()}],"border-w":[{border:O()}],"border-w-x":[{"border-x":O()}],"border-w-y":[{"border-y":O()}],"border-w-s":[{"border-s":O()}],"border-w-e":[{"border-e":O()}],"border-w-bs":[{"border-bs":O()}],"border-w-be":[{"border-be":O()}],"border-w-t":[{"border-t":O()}],"border-w-r":[{"border-r":O()}],"border-w-b":[{"border-b":O()}],"border-w-l":[{"border-l":O()}],"divide-x":[{"divide-x":O()}],"divide-x-reverse":[`divide-x-reverse`],"divide-y":[{"divide-y":O()}],"divide-y-reverse":[`divide-y-reverse`],"border-style":[{border:[...k(),`hidden`,`none`]}],"divide-style":[{divide:[...k(),`hidden`,`none`]}],"border-color":[{border:E()}],"border-color-x":[{"border-x":E()}],"border-color-y":[{"border-y":E()}],"border-color-s":[{"border-s":E()}],"border-color-e":[{"border-e":E()}],"border-color-bs":[{"border-bs":E()}],"border-color-be":[{"border-be":E()}],"border-color-t":[{"border-t":E()}],"border-color-r":[{"border-r":E()}],"border-color-b":[{"border-b":E()}],"border-color-l":[{"border-l":E()}],"divide-color":[{divide:E()}],"outline-style":[{outline:[...k(),`none`,`hidden`]}],"outline-offset":[{"outline-offset":[j,N,M]}],"outline-w":[{outline:[``,j,xt,mt]}],"outline-color":[{outline:E()}],shadow:[{shadow:[``,`none`,u,I,bt]}],"shadow-color":[{shadow:E()}],"inset-shadow":[{"inset-shadow":[`none`,d,I,bt]}],"inset-shadow-color":[{"inset-shadow":E()}],"ring-w":[{ring:O()}],"ring-w-inset":[`ring-inset`],"ring-color":[{ring:E()}],"ring-offset-w":[{"ring-offset":[j,mt]}],"ring-offset-color":[{"ring-offset":E()}],"inset-ring-w":[{"inset-ring":O()}],"inset-ring-color":[{"inset-ring":E()}],"text-shadow":[{"text-shadow":[`none`,f,I,bt]}],"text-shadow-color":[{"text-shadow":E()}],opacity:[{opacity:[j,N,M]}],"mix-blend":[{"mix-blend":[...me(),`plus-darker`,`plus-lighter`]}],"bg-blend":[{"bg-blend":me()}],"mask-clip":[{"mask-clip":[`border`,`padding`,`content`,`fill`,`stroke`,`view`]},`mask-no-clip`],"mask-composite":[{mask:[`add`,`subtract`,`intersect`,`exclude`]}],"mask-image-linear-pos":[{"mask-linear":[j]}],"mask-image-linear-from-pos":[{"mask-linear-from":A()}],"mask-image-linear-to-pos":[{"mask-linear-to":A()}],"mask-image-linear-from-color":[{"mask-linear-from":E()}],"mask-image-linear-to-color":[{"mask-linear-to":E()}],"mask-image-t-from-pos":[{"mask-t-from":A()}],"mask-image-t-to-pos":[{"mask-t-to":A()}],"mask-image-t-from-color":[{"mask-t-from":E()}],"mask-image-t-to-color":[{"mask-t-to":E()}],"mask-image-r-from-pos":[{"mask-r-from":A()}],"mask-image-r-to-pos":[{"mask-r-to":A()}],"mask-image-r-from-color":[{"mask-r-from":E()}],"mask-image-r-to-color":[{"mask-r-to":E()}],"mask-image-b-from-pos":[{"mask-b-from":A()}],"mask-image-b-to-pos":[{"mask-b-to":A()}],"mask-image-b-from-color":[{"mask-b-from":E()}],"mask-image-b-to-color":[{"mask-b-to":E()}],"mask-image-l-from-pos":[{"mask-l-from":A()}],"mask-image-l-to-pos":[{"mask-l-to":A()}],"mask-image-l-from-color":[{"mask-l-from":E()}],"mask-image-l-to-color":[{"mask-l-to":E()}],"mask-image-x-from-pos":[{"mask-x-from":A()}],"mask-image-x-to-pos":[{"mask-x-to":A()}],"mask-image-x-from-color":[{"mask-x-from":E()}],"mask-image-x-to-color":[{"mask-x-to":E()}],"mask-image-y-from-pos":[{"mask-y-from":A()}],"mask-image-y-to-pos":[{"mask-y-to":A()}],"mask-image-y-from-color":[{"mask-y-from":E()}],"mask-image-y-to-color":[{"mask-y-to":E()}],"mask-image-radial":[{"mask-radial":[N,M]}],"mask-image-radial-from-pos":[{"mask-radial-from":A()}],"mask-image-radial-to-pos":[{"mask-radial-to":A()}],"mask-image-radial-from-color":[{"mask-radial-from":E()}],"mask-image-radial-to-color":[{"mask-radial-to":E()}],"mask-image-radial-shape":[{"mask-radial":[`circle`,`ellipse`]}],"mask-image-radial-size":[{"mask-radial":[{closest:[`side`,`corner`],farthest:[`side`,`corner`]}]}],"mask-image-radial-pos":[{"mask-radial-at":b()}],"mask-image-conic-pos":[{"mask-conic":[j]}],"mask-image-conic-from-pos":[{"mask-conic-from":A()}],"mask-image-conic-to-pos":[{"mask-conic-to":A()}],"mask-image-conic-from-color":[{"mask-conic-from":E()}],"mask-image-conic-to-color":[{"mask-conic-to":E()}],"mask-mode":[{mask:[`alpha`,`luminance`,`match`]}],"mask-origin":[{"mask-origin":[`border`,`padding`,`content`,`fill`,`stroke`,`view`]}],"mask-position":[{mask:ue()}],"mask-repeat":[{mask:de()}],"mask-size":[{mask:fe()}],"mask-type":[{"mask-type":[`alpha`,`luminance`]}],"mask-image":[{mask:[`none`,N,M]}],filter:[{filter:[``,`none`,N,M]}],blur:[{blur:he()}],brightness:[{brightness:[j,N,M]}],contrast:[{contrast:[j,N,M]}],"drop-shadow":[{"drop-shadow":[``,`none`,p,I,bt]}],"drop-shadow-color":[{"drop-shadow":E()}],grayscale:[{grayscale:[``,j,N,M]}],"hue-rotate":[{"hue-rotate":[j,N,M]}],invert:[{invert:[``,j,N,M]}],saturate:[{saturate:[j,N,M]}],sepia:[{sepia:[``,j,N,M]}],"backdrop-filter":[{"backdrop-filter":[``,`none`,N,M]}],"backdrop-blur":[{"backdrop-blur":he()}],"backdrop-brightness":[{"backdrop-brightness":[j,N,M]}],"backdrop-contrast":[{"backdrop-contrast":[j,N,M]}],"backdrop-grayscale":[{"backdrop-grayscale":[``,j,N,M]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[j,N,M]}],"backdrop-invert":[{"backdrop-invert":[``,j,N,M]}],"backdrop-opacity":[{"backdrop-opacity":[j,N,M]}],"backdrop-saturate":[{"backdrop-saturate":[j,N,M]}],"backdrop-sepia":[{"backdrop-sepia":[``,j,N,M]}],"border-collapse":[{border:[`collapse`,`separate`]}],"border-spacing":[{"border-spacing":S()}],"border-spacing-x":[{"border-spacing-x":S()}],"border-spacing-y":[{"border-spacing-y":S()}],"table-layout":[{table:[`auto`,`fixed`]}],caption:[{caption:[`top`,`bottom`]}],transition:[{transition:[``,`all`,`colors`,`opacity`,`shadow`,`transform`,`none`,N,M]}],"transition-behavior":[{transition:[`normal`,`discrete`]}],duration:[{duration:[j,`initial`,N,M]}],ease:[{ease:[`linear`,`initial`,_,N,M]}],delay:[{delay:[j,N,M]}],animate:[{animate:[`none`,v,N,M]}],backface:[{backface:[`hidden`,`visible`]}],perspective:[{perspective:[h,N,M]}],"perspective-origin":[{"perspective-origin":x()}],rotate:[{rotate:ge()}],"rotate-x":[{"rotate-x":ge()}],"rotate-y":[{"rotate-y":ge()}],"rotate-z":[{"rotate-z":ge()}],scale:[{scale:_e()}],"scale-x":[{"scale-x":_e()}],"scale-y":[{"scale-y":_e()}],"scale-z":[{"scale-z":_e()}],"scale-3d":[`scale-3d`],skew:[{skew:ve()}],"skew-x":[{"skew-x":ve()}],"skew-y":[{"skew-y":ve()}],transform:[{transform:[N,M,``,`none`,`gpu`,`cpu`]}],"transform-origin":[{origin:x()}],"transform-style":[{transform:[`3d`,`flat`]}],translate:[{translate:ye()}],"translate-x":[{"translate-x":ye()}],"translate-y":[{"translate-y":ye()}],"translate-z":[{"translate-z":ye()}],"translate-none":[`translate-none`],zoom:[{zoom:[rt,N,M]}],accent:[{accent:E()}],appearance:[{appearance:[`none`,`auto`]}],"caret-color":[{caret:E()}],"color-scheme":[{scheme:[`normal`,`dark`,`light`,`light-dark`,`only-dark`,`only-light`]}],cursor:[{cursor:[`auto`,`default`,`pointer`,`wait`,`text`,`move`,`help`,`not-allowed`,`none`,`context-menu`,`progress`,`cell`,`crosshair`,`vertical-text`,`alias`,`copy`,`no-drop`,`grab`,`grabbing`,`all-scroll`,`col-resize`,`row-resize`,`n-resize`,`e-resize`,`s-resize`,`w-resize`,`ne-resize`,`nw-resize`,`se-resize`,`sw-resize`,`ew-resize`,`ns-resize`,`nesw-resize`,`nwse-resize`,`zoom-in`,`zoom-out`,N,M]}],"field-sizing":[{"field-sizing":[`fixed`,`content`]}],"pointer-events":[{"pointer-events":[`auto`,`none`]}],resize:[{resize:[`none`,``,`y`,`x`]}],"scroll-behavior":[{scroll:[`auto`,`smooth`]}],"scrollbar-thumb-color":[{"scrollbar-thumb":E()}],"scrollbar-track-color":[{"scrollbar-track":E()}],"scrollbar-gutter":[{"scrollbar-gutter":[`auto`,`stable`,`both`]}],"scrollbar-w":[{scrollbar:[`auto`,`thin`,`none`]}],"scroll-m":[{"scroll-m":S()}],"scroll-mx":[{"scroll-mx":S()}],"scroll-my":[{"scroll-my":S()}],"scroll-ms":[{"scroll-ms":S()}],"scroll-me":[{"scroll-me":S()}],"scroll-mbs":[{"scroll-mbs":S()}],"scroll-mbe":[{"scroll-mbe":S()}],"scroll-mt":[{"scroll-mt":S()}],"scroll-mr":[{"scroll-mr":S()}],"scroll-mb":[{"scroll-mb":S()}],"scroll-ml":[{"scroll-ml":S()}],"scroll-p":[{"scroll-p":S()}],"scroll-px":[{"scroll-px":S()}],"scroll-py":[{"scroll-py":S()}],"scroll-ps":[{"scroll-ps":S()}],"scroll-pe":[{"scroll-pe":S()}],"scroll-pbs":[{"scroll-pbs":S()}],"scroll-pbe":[{"scroll-pbe":S()}],"scroll-pt":[{"scroll-pt":S()}],"scroll-pr":[{"scroll-pr":S()}],"scroll-pb":[{"scroll-pb":S()}],"scroll-pl":[{"scroll-pl":S()}],"snap-align":[{snap:[`start`,`end`,`center`,`align-none`]}],"snap-stop":[{snap:[`normal`,`always`]}],"snap-type":[{snap:[`none`,`x`,`y`,`both`]}],"snap-strictness":[{snap:[`mandatory`,`proximity`]}],touch:[{touch:[`auto`,`none`,`manipulation`]}],"touch-x":[{"touch-pan":[`x`,`left`,`right`]}],"touch-y":[{"touch-pan":[`y`,`up`,`down`]}],"touch-pz":[`touch-pinch-zoom`],select:[{select:[`none`,`text`,`all`,`auto`]}],"will-change":[{"will-change":[`auto`,`scroll`,`contents`,`transform`,N,M]}],fill:[{fill:[`none`,...E()]}],"stroke-w":[{stroke:[j,xt,mt,ht]}],stroke:[{stroke:[`none`,...E()]}],"forced-color-adjust":[{"forced-color-adjust":[`auto`,`none`]}]},conflictingClassGroups:{"container-named":[`container-type`],overflow:[`overflow-x`,`overflow-y`],overscroll:[`overscroll-x`,`overscroll-y`],inset:[`inset-x`,`inset-y`,`inset-bs`,`inset-be`,`start`,`end`,`top`,`right`,`bottom`,`left`],"inset-x":[`right`,`left`],"inset-y":[`top`,`bottom`],flex:[`basis`,`grow`,`shrink`],gap:[`gap-x`,`gap-y`],p:[`px`,`py`,`ps`,`pe`,`pbs`,`pbe`,`pt`,`pr`,`pb`,`pl`],px:[`pr`,`pl`],py:[`pt`,`pb`],m:[`mx`,`my`,`ms`,`me`,`mbs`,`mbe`,`mt`,`mr`,`mb`,`ml`],mx:[`mr`,`ml`],my:[`mt`,`mb`],size:[`w`,`h`],"font-size":[`leading`],"fvn-normal":[`fvn-ordinal`,`fvn-slashed-zero`,`fvn-figure`,`fvn-spacing`,`fvn-fraction`],"fvn-ordinal":[`fvn-normal`],"fvn-slashed-zero":[`fvn-normal`],"fvn-figure":[`fvn-normal`],"fvn-spacing":[`fvn-normal`],"fvn-fraction":[`fvn-normal`],"line-clamp":[`display`,`overflow`],rounded:[`rounded-s`,`rounded-e`,`rounded-t`,`rounded-r`,`rounded-b`,`rounded-l`,`rounded-ss`,`rounded-se`,`rounded-ee`,`rounded-es`,`rounded-tl`,`rounded-tr`,`rounded-br`,`rounded-bl`],"rounded-s":[`rounded-ss`,`rounded-es`],"rounded-e":[`rounded-se`,`rounded-ee`],"rounded-t":[`rounded-tl`,`rounded-tr`],"rounded-r":[`rounded-tr`,`rounded-br`],"rounded-b":[`rounded-br`,`rounded-bl`],"rounded-l":[`rounded-tl`,`rounded-bl`],"border-spacing":[`border-spacing-x`,`border-spacing-y`],"border-w":[`border-w-x`,`border-w-y`,`border-w-s`,`border-w-e`,`border-w-bs`,`border-w-be`,`border-w-t`,`border-w-r`,`border-w-b`,`border-w-l`],"border-w-x":[`border-w-r`,`border-w-l`],"border-w-y":[`border-w-t`,`border-w-b`],"border-color":[`border-color-x`,`border-color-y`,`border-color-s`,`border-color-e`,`border-color-bs`,`border-color-be`,`border-color-t`,`border-color-r`,`border-color-b`,`border-color-l`],"border-color-x":[`border-color-r`,`border-color-l`],"border-color-y":[`border-color-t`,`border-color-b`],translate:[`translate-x`,`translate-y`,`translate-none`],"translate-none":[`translate`,`translate-x`,`translate-y`,`translate-z`],"scroll-m":[`scroll-mx`,`scroll-my`,`scroll-ms`,`scroll-me`,`scroll-mbs`,`scroll-mbe`,`scroll-mt`,`scroll-mr`,`scroll-mb`,`scroll-ml`],"scroll-mx":[`scroll-mr`,`scroll-ml`],"scroll-my":[`scroll-mt`,`scroll-mb`],"scroll-p":[`scroll-px`,`scroll-py`,`scroll-ps`,`scroll-pe`,`scroll-pbs`,`scroll-pbe`,`scroll-pt`,`scroll-pr`,`scroll-pb`,`scroll-pl`],"scroll-px":[`scroll-pr`,`scroll-pl`],"scroll-py":[`scroll-pt`,`scroll-pb`],touch:[`touch-x`,`touch-y`,`touch-pz`],"touch-x":[`touch`],"touch-y":[`touch`],"touch-pz":[`touch`]},conflictingClassGroupModifiers:{"font-size":[`leading`]},postfixLookupClassGroups:[`container-type`],orderSensitiveModifiers:[`*`,`**`,`after`,`backdrop`,`before`,`details-content`,`file`,`first-letter`,`first-line`,`marker`,`placeholder`,`selection`]}});function L(...e){return Ft(D(e))}var It=e((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),R=e(((e,t)=>{t.exports=It()}))(),Lt=me(`inline-flex min-h-[22px] items-center rounded-full border px-2 py-0.5 text-[11px] font-extrabold uppercase leading-tight`,{variants:{variant:{neutral:`border-border bg-secondary text-muted-foreground`,succeeded:`border-emerald-400/35 bg-emerald-500/15 text-emerald-300`,failed:`border-red-400/40 bg-red-500/15 text-red-300`,dead:`border-red-400/40 bg-red-500/15 text-red-300`,missing:`border-red-400/40 bg-red-500/15 text-red-300`,running:`border-amber-400/40 bg-amber-500/15 text-amber-300`,queued:`border-teal-400/40 bg-teal-500/15 text-teal-200`,canceled:`border-border bg-secondary text-muted-foreground`}},defaultVariants:{variant:`neutral`}});function z({className:e,variant:t,...n}){return(0,R.jsx)(`span`,{"data-slot":`badge`,className:L(Lt({variant:t,className:e})),...n})}var Rt=me(`inline-flex min-h-9 shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md border text-sm font-semibold transition-colors focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0`,{variants:{variant:{default:`border-primary bg-primary text-primary-foreground hover:bg-primary/90`,secondary:`border-border bg-secondary text-secondary-foreground hover:bg-secondary/80`,outline:`border-border bg-background hover:bg-accent hover:text-accent-foreground`,ghost:`border-transparent hover:bg-accent hover:text-accent-foreground`,destructive:`border-destructive bg-destructive text-white hover:bg-destructive/90`},size:{default:`h-9 px-4 py-2`,sm:`h-8 rounded-md px-3 text-xs`,icon:`size-9`}},defaultVariants:{variant:`secondary`,size:`default`}});function B({className:e,variant:t,size:n,type:r=`button`,...i}){return(0,R.jsx)(`button`,{"data-slot":`button`,type:r,className:L(Rt({variant:t,size:n,className:e})),...i})}function V({className:e,...t}){return(0,R.jsx)(`div`,{"data-slot":`card`,className:L(`rounded-lg border bg-card text-card-foreground shadow-[0_18px_44px_rgb(0_0_0/0.22)]`,e),...t})}function H({className:e,...t}){return(0,R.jsx)(`div`,{"data-slot":`card-header`,className:L(`flex items-center justify-between gap-3 border-b px-4 py-3`,e),...t})}function zt({className:e,...t}){return(0,R.jsx)(`h2`,{"data-slot":`card-title`,className:L(`text-[15px] font-bold`,e),...t})}function Bt({className:e,...t}){return(0,R.jsx)(`div`,{"data-slot":`card-content`,className:L(`p-4`,e),...t})}function U({className:e,type:t,...n}){return(0,R.jsx)(`input`,{"data-slot":`input`,type:t,className:L(`flex min-h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground shadow-xs transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50`,e),...n})}function W({className:e,...t}){return(0,R.jsx)(`label`,{"data-slot":`label`,className:L(`grid gap-1.5 text-xs font-bold text-muted-foreground`,e),...t})}function Vt({className:e,...t}){return(0,R.jsx)(`select`,{"data-slot":`select`,className:L(`flex min-h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground shadow-xs transition-colors focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50`,e),...t})}function Ht({className:e,...t}){return(0,R.jsx)(`table`,{"data-slot":`table`,className:L(`w-full border-collapse text-sm`,e),...t})}function Ut({className:e,...t}){return(0,R.jsx)(`thead`,{"data-slot":`table-header`,className:e,...t})}function Wt({className:e,...t}){return(0,R.jsx)(`tbody`,{"data-slot":`table-body`,className:e,...t})}function Gt({className:e,...t}){return(0,R.jsx)(`tr`,{"data-slot":`table-row`,className:L(`border-b transition-colors hover:bg-muted/45`,e),...t})}function G({className:e,...t}){return(0,R.jsx)(`th`,{"data-slot":`table-head`,className:L(`bg-secondary px-3 py-3 text-left align-middle text-xs font-extrabold text-muted-foreground`,e),...t})}function K({className:e,...t}){return(0,R.jsx)(`td`,{"data-slot":`table-cell`,className:L(`px-3 py-3 align-middle text-sm`,e),...t})}var Kt={pending:`Needs review`,selected:`Assigned to onboarder`,reachingout:`Reaching out`,awaitingcontribution:`Awaiting contribution`,onboarded:`Onboarded`,waitlist:`Waitlist`,rejected:`Rejected`};function qt(e){if(!e)return``;let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString(void 0,{year:`numeric`,month:`short`,day:`numeric`,hour:`2-digit`,minute:`2-digit`})}function Jt(e,t=new Date){if(!e)return null;let n=new Date(e);if(Number.isNaN(n.getTime()))return null;let r=t.getTime()-n.getTime();return r<0?0:Math.floor(r/864e5)}function Yt(e){return e==null?``:JSON.stringify(e,null,2)}function Xt(e){return e.onboarding_state||e.onboardingState||e.cOnboardingState||``}function Zt(e){let t=String(e||``).trim();if(!t)return`No status`;let n=t.toLowerCase();return Kt[n]?Kt[n]:t.replace(/[-_]+/g,` `).replace(/\s+/g,` `).trim().replace(/\b\w/g,e=>e.toUpperCase())}function Qt(e){let t=String(e||``).trim().toLowerCase();return!t||t===`pending`?`neutral`:t===`selected`?`queued`:t===`rejected`?`failed`:t===`onboarded`?`succeeded`:t===`waitlist`?`running`:`queued`}function $t(e){let t=String(e||``).trim();return!t||t.toLowerCase()===`none`?``:t}function en(e){let t=String(e||``).trim();return t?/^https?:\/\//i.test(t)?t:`https://${t.replace(/^\/+/,``)}`:``}function tn(e){try{return new URL(en(e))}catch{return null}}function nn(e,t){let n=e.toLowerCase();return n===t||n.endsWith(`.${t}`)}function rn(e){return e.split(`/`).filter(Boolean).map(e=>encodeURIComponent(e)).join(`/`)}function an(e){let t=String(e||``).trim();if(!t)return``;let n=tn(t);if(n&&nn(n.hostname,`linkedin.com`))return n.href;if(/^https?:\/\//i.test(t))return``;let r=t.replace(/^@/,``).replace(/^\/+|\/+$/g,``).replace(/^in\//i,``);return r?`https://www.linkedin.com/in/${rn(r)}`:``}function on(e){let t=String(e||``).trim().replace(/^@/,``);if(!t)return``;let n=tn(t);if(n&&nn(n.hostname,`github.com`))return n.href;if(/^https?:\/\//i.test(t))return``;let r=t.replace(/^\/+|\/+$/g,``);return r?`https://github.com/${rn(r)}`:``}var sn=[{category:`CRM`,label:`CRM`,description:`EspoCRM connection settings used by the API, worker, and Discord bot.`},{category:`Projects`,label:`Projects`,description:`ERPNext credentials and project workflow settings.`},{category:`Onboarding`,label:`Onboarding`,description:`Editable onboarding integrations such as DocuSeal, Outline, and onboarding email SMTP.`},{category:`AI`,label:`AI Providers`,description:`Provider credentials, base URLs, and model defaults.`},{category:`Agent`,label:`Agent Runtime`,description:`Planner, fallback, and tiered model routing for agent workflows.`},{category:`Observability`,label:`Observability`,description:`Telemetry and request tracing integrations.`},{category:`Intake`,label:`Intake`,description:`Resume and mailbox intake limits and parser defaults.`},{category:`Operations`,label:`Operations`,description:`Queue, sync, GitHub, and notification behavior.`},{category:`Legacy`,label:`Legacy`,description:`Older integrations retained for compatibility.`}],cn=new Map(sn.map((e,t)=>[e.category,{...e,index:t}]));function ln(e){return`configurationGroup-${e.replace(/[^a-zA-Z0-9_-]+/g,`-`)}`}function un(e){return e.key.startsWith(`ONBOARDING_EMAIL_`)||e.is_secret||e.value_type===`url`||e.key.endsWith(`_MODEL`)||e.key.endsWith(`_API_USER`)||e.key.endsWith(`_BASE_URL`)}var dn={people:`/dashboard/people`,gigs:`/dashboard/gigs`,projects:`/dashboard/projects`,onboarding:`/dashboard/onboarding`,jobs:`/dashboard/jobs`,agent:`/dashboard/agent`,audit:`/dashboard/audit`,configuration:`/dashboard/configuration`},fn={people:`people:read`,gigs:`gigs:read`,projects:`projects:read`,onboarding:`onboarding:read`,jobs:`jobs:read`,agent:`audit:read`,audit:`audit:read`,configuration:`configuration:read`},pn={discord:{label:`Discord`,options:[[`linked`,`Linked`],[`missing`,`Missing`]]},email_508:{label:`508 email`,options:[[`present`,`Present`],[`missing`,`Missing`]]},resume:{label:`Resume`,options:[[`present`,`Present`],[`missing`,`Missing`]]},skills:{label:`Skills`,options:[[`present`,`Parsed`],[`missing`,`Not parsed`]]},sync_status:{label:`Sync status`,options:[[`active`,`Active`],[`conflict`,`Conflict`],[`missing_in_crm`,`Missing in CRM`]]}},mn=[[`pending`,`Needs review`],[`selected`,`Assigned to onboarder`],[`reachingout`,`Reaching out`],[`awaitingcontribution`,`Awaiting contribution`],[`onboarded`,`Onboarded`],[`waitlist`,`Waitlist`],[`rejected`,`Rejected`]],hn=mn.slice(0,4),gn=new Set([`onboarded`,`waitlist`,`rejected`]);function _n(e){return String(e||``).trim().toLowerCase().replace(/[-_\s]+/g,``)}var vn=class extends Error{status;statusText;payload;url;method;constructor(e,t,n,r,i,a){super(e),this.name=`ApiRequestError`,this.status=t,this.statusText=n,this.payload=r,this.url=i,this.method=a}};function yn(e,t){let n=e.detail;if(typeof n==`string`&&n.trim())return n;let r=e.error;return typeof r==`string`?r===`person_not_found`?`No CRM person, ERPNext user, or ERPNext supplier matched "${typeof e.person==`string`&&e.person.trim()?e.person:`that person`}". Try an email address or an exact name from CRM/ERPNext.`:r===`candidate_not_found`?`The selected person record is no longer available. Search again and choose one of the current matches.`:r===`invalid_crm_profile`?`Paste a valid CRM Contact profile URL or Contact id.`:r===`crm_profile_not_found`?`That CRM Contact profile was not found.`:r===`crm_profile_mismatch`?`CRM returned a different Contact than the profile requested. Check the profile URL and try again.`:r===`crm_profile_lookup_failed`?`CRM profile lookup failed. Try again after CRM is reachable.`:r===`ambiguous_person`?`Multiple people matched. Choose the matching person record.`:r||t:t}function bn(e,t){return typeof e==`string`&&e.trim()?e:e instanceof Error&&e.message.trim()?e.message:t}function xn(){return window.location.pathname.split(`/`).filter(Boolean)[1]||``}function Sn(){let e=xn();return Object.hasOwn(dn,e)?e:`people`}function Cn(e=`gigs`){let[,t,n]=window.location.pathname.split(`/`).filter(Boolean);if(t!==e||!n)return``;try{return decodeURIComponent(n)}catch{return``}}async function q(e,t={}){let n=String(t.method||`GET`).toUpperCase(),r=new Headers(t.headers);r.set(`Accept`,`application/json`);let i;try{i=await fetch(e,{credentials:`same-origin`,...t,headers:r})}catch(t){throw new vn(bn(t,`Network request failed`),0,`Network request failed`,null,e,n)}if(i.status===401){let t=`${window.location.pathname}${window.location.search}`||`/dashboard`;throw window.location.assign(`/auth/login?next=${encodeURIComponent(t)}`),new vn(`Session expired`,i.status,i.statusText,null,e,n)}if(!i.ok){let t=i.statusText,r=null;try{r=await i.json(),r&&typeof r==`object`&&(t=yn(r,String(t||`Request failed`)))}catch{t=i.statusText}throw new vn(typeof t==`string`?t:JSON.stringify(t),i.status,i.statusText,r,e,n)}return i.json()}function wn(e,t,n){if(e===`gigs`){let e=t;if(n===`title`)return e.title||``;if(n===`status`)return e.status||``;if(n===`applications`)return Number(e.application_count||0);if(n===`activity`)return zn(e)}if(e===`projects`){let e=t;if(n===`display_name`)return e.display_name||``;if(n===`customer`)return e.customer||``;if(n===`status`)return e.source_status||``;if(n===`roster_count`)return Number(e.roster_count||0);if(n===`modified`)return e.source_modified_at||e.last_synced_at||``}if(e===`onboarding`){let e=t,r=e.profile_status||{};if(n===`name`)return e.name||e.email_508||e.email||``;if(n===`onboarding_state`){let t=Xt(e);return t.toLowerCase()===`pending`?`zzz-${t}`:t}if(n===`onboarder`)return e.onboarder||``;if(n===`updated`)return e.onboarding_updated_at||``;if(n===`profile_gaps`)return[!r.discord_linked,!r.latest_resume,Number(r.skills_count||0)<=0].filter(Boolean).length}if(e===`people`){let e=t,r=e.profile_status||{};if(n===`name`)return e.name||e.email_508||e.email||``;if(n===`status`)return[r.crm_active,r.is_member,r.discord_linked,r.email_508,r.latest_resume].filter(Boolean).length;if(n===`discord`)return e.discord_username||e.discord_user_id||``;if(n===`resume`)return e.latest_resume_name||e.latest_resume_id||``}if(e===`audit`){let e=t;if(n===`actor`)return e.actor_display_name||e.actor_subject||e.actor_provider||``}return t[n]??``}function Tn(e,t,n){let r=n.direction===`asc`?1:-1;return[...t].sort((t,i)=>{let a=wn(e,t,n.key),o=wn(e,i,n.key);return typeof a==`number`&&typeof o==`number`?(a-o)*r:String(a).localeCompare(String(o),void 0,{numeric:!0})*r})}function En({label:e,scope:t,sort:n,sortKey:r,onSort:i}){let a=n.key===r,o=n.direction===`asc`?`↑`:`↓`;return(0,R.jsx)(`button`,{type:`button`,"data-sort-scope":t,"data-sort-key":r,className:`text-left font-[inherit] text-inherit hover:text-foreground`,onClick:()=>i(t,r),children:a?`${e} ${o}`:e})}function Dn({className:e,label:t,scope:n,sort:r,sortKey:i,onSort:a}){return(0,R.jsx)(G,{className:e,"aria-sort":r.key===i?r.direction===`asc`?`ascending`:`descending`:`none`,children:(0,R.jsx)(En,{label:t,scope:n,sort:r,sortKey:i,onSort:a})})}function On({label:e,value:t,id:n}){return(0,R.jsxs)(V,{className:`p-4`,children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:e}),(0,R.jsx)(`strong`,{id:n,className:`block text-2xl`,children:t})]})}function kn({children:e,hidden:t}){return t?null:(0,R.jsx)(`div`,{className:`px-4 py-7 text-center text-sm text-muted-foreground`,children:e})}function An({value:e,query:t}){let n=t.trim().toLowerCase();if(!n)return(0,R.jsx)(R.Fragment,{children:e});let r=e.toLowerCase(),i=[],a=0,o=r.indexOf(n);for(;o>=0;){o>a&&i.push(e.slice(a,o));let t=o+n.length;i.push((0,R.jsx)(`mark`,{className:`rounded-sm bg-amber-200 px-0.5 text-inherit dark:bg-amber-500/35`,children:e.slice(o,t)},`${o}-${t}`)),a=t,o=r.indexOf(n,a)}return avoid 0);function bt(e){return s.includes(e)}function N(e){return s.includes(`${e}:dry_run`)}function xt(e){return bt(e)||N(e)}function St(e){return bt(fn[e])}function Ct(){return Object.keys(dn).find(e=>St(e))||`people`}function P(e,t){o({message:e,tone:t})}function F(e,t){P(bn(e,t),`error`)}function I(e,t){Ee(n=>({...n,[e]:t}))}function wt(e,t=!1){let n=e;St(n)||(P(`${n[0].toUpperCase()}${n.slice(1)} requires SSO validation`,`error`),n=Ct()),n!==`gigs`&&le(``),n!==`projects`&&ue(``),n===`gigs`&&t&&le(``),n===`projects`&&t&&ue(``),i(n),t?window.history.pushState({view:n},``,dn[n]):(!Object.hasOwn(dn,xn())||n!==e)&&window.history.replaceState({view:n},``,dn[n])}yt.current=wt;function Tt(e){return!u||!e?``:`${u}/#Contact/view/${encodeURIComponent(e)}`}function Et(e){return!u||!e?``:`${u}/api/v1/Attachment/file/${encodeURIComponent(e)}`}function Dt(e,t){Me(n=>{let r=n[e];return{...n,[e]:{key:t,direction:r.key===t&&r.direction===`asc`?`desc`:`asc`}}})}function Ot(e){le(e),w(h.find(t=>t.id===e)||null),i(`gigs`),window.history.pushState({view:`gigs`,gigId:e},``,`/dashboard/gigs/${encodeURIComponent(e)}`)}function kt(){le(``),w(null),window.history.replaceState({view:`gigs`},``,dn.gigs)}function At(e){ue(e),i(`projects`),window.history.pushState({view:`projects`,projectId:e},``,`/dashboard/projects/${encodeURIComponent(e)}`)}function jt(){ue(``),window.history.replaceState({view:`projects`},``,dn.projects)}async function Mt(){let e=await q(`/dashboard/api/me`);n(e);let t=Array.isArray(e.permissions)?e.permissions:[];return c(t),d((e.crm_base_url||``).replace(/\/+$/,``)),t}function Nt(){let e=new URLSearchParams({minutes:Ne,limit:`100`});return Fe&&e.set(`status`,Fe),Le.trim()&&e.set(`type`,Le.trim()),`/dashboard/api/jobs?${e.toString()}`}function Pt(){let e=new URLSearchParams({limit:String(Ge)});return ze&&e.set(`status`,ze),Ve.trim()&&e.set(`query`,Ve.trim()),Ue&&e.set(`include_historical`,`true`),`/dashboard/api/gigs?${e.toString()}`}function Ft(){let e=new URLSearchParams({limit:`100`,status:Ye});return qe.trim()&&e.set(`query`,qe.trim()),`/dashboard/api/projects?${e.toString()}`}async function It(){I(`jobs`,!0),P(`Loading background tasks`);try{let e=await q(Nt());p(e),P(`Loaded ${e.length} background task${e.length===1?``:`s`}`,`ok`)}catch(e){F(e,`Unable to load background tasks`)}finally{I(`jobs`,!1)}}async function Lt(){I(`gigs`,!0);try{let e=await q(Pt());y(e),P(`Loaded ${e.length} gig${e.length===1?``:`s`}`,`ok`),Jt()}catch(e){F(e,`Unable to load gigs`)}finally{I(`gigs`,!1)}}async function z(){I(`projects`,!0);try{let e=await q(Ft());S(e.projects||[]),ne(e.summary||{}),P(`Loaded ${(e.projects||[]).length} project${(e.projects||[]).length===1?``:`s`}`,`ok`)}catch(e){F(e,`Unable to load projects`)}finally{I(`projects`,!1)}}async function Rt(){I(`syncProjects`,!0),P(`Queueing project sync`);try{let e=await q(`/dashboard/api/sync/projects`,{method:`POST`});e.dry_run?P(`Dry run only: would queue ${e.would_enqueue?.job_type||`project sync`}`,`warning`):P(`Queued project sync ${e.job_id}`,`ok`)}catch(e){F(e,`Unable to queue project sync`)}finally{I(`syncProjects`,!1)}}async function V(e){let t=e.trim();if(t.length<2)return[];try{return(await q(`/dashboard/api/erpnext/customers?${new URLSearchParams({query:t}).toString()}`)).customers||[]}catch(e){return P(e instanceof Error?e.message:`Unable to search customers`,`error`),[]}}async function H(e){let t=e.trim();if(t.length<2)return[];try{return(await q(`/dashboard/api/erpnext/contacts?${new URLSearchParams({query:t}).toString()}`)).contacts||[]}catch(e){return P(e instanceof Error?e.message:`Unable to search contacts`,`error`),[]}}async function zt(e){let t=e.trim();if(t.length<2)return[];try{return(await q(`/dashboard/api/erpnext/account-managers?${new URLSearchParams({query:t}).toString()}`)).users||[]}catch(e){return P(e instanceof Error?e.message:`Unable to search account managers`,`error`),[]}}async function Bt(){try{let e=(await q(`/dashboard/api/erpnext/cost-centers`)).cost_centers||[];return e.length?e:[{name:`Projects - 5`,cost_center_name:`Projects`}]}catch(e){return P(e instanceof Error?e.message:`Unable to load cost centers`,`error`),[{name:`Projects - 5`,cost_center_name:`Projects`}]}}async function U(e){I(`createProject`,!0);try{let t=await q(`/dashboard/api/projects/create`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)});return t.project.id?(S(e=>e.some(e=>e.id===t.project.id)?e.map(e=>e.id===t.project.id?t.project:e):[t.project,...e]),P(t.setup_warnings?.length?t.setup_warning_message||`Created ERP project setup; account manager setup needs follow-up`:`Created ERP project setup`,t.setup_warnings?.length?`warning`:`ok`),At(t.project.id)):(P([t.cache_refresh_message||`Created ERP project in ERPNext; local sync is pending`,t.setup_warnings?.length?t.setup_warning_message||`Account manager setup needs follow-up`:``].filter(Boolean).join(` `),t.setup_warnings?.length?`warning`:`ok`),z()),!0}catch(e){return P(e instanceof Error?e.message:`Unable to create project`,`error`),!1}finally{I(`createProject`,!1)}}async function W(e,t){I(`project:${e}:status`,!0);try{let n=await q(`/dashboard/api/projects/${encodeURIComponent(e)}/status`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({status:t})});S(t=>t.map(t=>t.id===e?n.project:t)),P(`Updated project status`,`ok`)}catch(e){F(e,`Unable to update project`)}finally{I(`project:${e}:status`,!1)}}async function Vt(e,t){if(e.length===0)return!1;I(`projectsBulkUpdate`,!0);try{let n=await q(`/dashboard/api/projects/bulk`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({project_ids:e,...t})}),r=n.projects||[];S(e=>e.map(e=>r.find(t=>t.id===e.id)||e));let i=n.failures||[];return P(i.length?`Updated ${r.length}; ${i.length} failed`:`Updated ${r.length} project${r.length===1?``:`s`}`,i.length?`error`:`ok`),i.length===0}catch(e){return F(e,`Unable to bulk update projects`),!1}finally{I(`projectsBulkUpdate`,!1)}}async function Ht(e,t,n,r){let i=t.trim(),a=n.trim();if(!i||!a)return!1;I(`project:${e}:user`,!0);try{let t=await q(`/dashboard/api/projects/${encodeURIComponent(e)}/users`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({user:i,candidate_id:a,...r||{}})});return S(n=>n.map(n=>n.id===e?t.project:n)),P(t.activity_cost_error?`Added project user; rate failed`:t.activity_cost?`Added project user and rate`:`Added project user`,t.activity_cost_error?`error`:`ok`),!0}catch(e){return F(e,`Unable to add project user`),!1}finally{I(`project:${e}:user`,!1)}}async function Ut(e,t){let n=t.trim();if(!n)return!1;I(`project:${e}:user`,!0);try{let t=await q(`/dashboard/api/projects/${encodeURIComponent(e)}/users/remove`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({user:n})});return S(n=>n.map(n=>n.id===e?t.project:n)),P(`Removed project user`,`ok`),!0}catch(e){return P(e instanceof Error?e.message:`Unable to remove project user`,`error`),!1}finally{I(`project:${e}:user`,!1)}}async function Wt(e,t,n){let r=t.trim();if(!r)return!1;I(`project:${e}:historical`,!0);try{let t=await q(`/dashboard/api/projects/${encodeURIComponent(e)}/historical-members`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({person:r,candidate_id:n})});return S(n=>n.map(n=>n.id===e?t.project:n)),Ae(null),P(`Added historical project member`,`ok`),!0}catch(t){if(t instanceof vn&&t.status===409){let n=t.payload?.candidates||[];if(n.length>0)return Ae({projectId:e,person:r,candidates:n}),P(`Choose the matching person record`,`error`),!1}return F(t,`Unable to add historical member`),!1}finally{I(`project:${e}:historical`,!1)}}async function Gt(e,t){let n=t.trim();if(!n)return!1;I(`project:${e}:historical`,!0);try{let t=await q(`/dashboard/api/projects/${encodeURIComponent(e)}/historical-members/remove`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({source_user_id:n})});return S(n=>n.map(n=>n.id===e?t.project:n)),P(`Removed historical project member`,`ok`),!0}catch(e){return P(e instanceof Error?e.message:`Unable to remove historical member`,`error`),!1}finally{I(`project:${e}:historical`,!1)}}async function G(e,t,n){I(`project:${e}:wiki`,!0);try{await q(`/dashboard/api/projects/${encodeURIComponent(e)}/wiki-match`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({status:t,row_key:n})}),P(t===`no_row`?`Marked as no wiki row`:`Confirmed wiki match`,`ok`),await K()}catch(e){F(e,`Unable to save wiki match`)}finally{I(`project:${e}:wiki`,!1)}}async function K(){I(`wikiMatches`,!0);try{oe(await q(`/dashboard/api/projects/wiki-matches`)),P(`Loaded wiki match preview`,`ok`)}catch(e){F(e,`Unable to load wiki matches`)}finally{I(`wikiMatches`,!1)}}async function Kt(e){I(`gig:${e}:detail`,!0);try{w(await q(`/dashboard/api/gigs/${encodeURIComponent(e)}`))}catch(e){w(null),F(e,`Unable to load gig`)}finally{I(`gig:${e}:detail`,!1)}}async function qt(){await Lt(),T&&await Kt(T)}async function Jt(){if(bt(`gigs:read`)){I(`notifications`,!0);try{let e=await q(`/dashboard/api/notifications?limit=20`);Qe(e.stale_days||7),fe(e.notifications||[])}catch(e){F(e,`Unable to load notifications`)}finally{I(`notifications`,!1)}}}async function Yt(e,t){I(`gig:${e}:status`,!0);try{let n=(await q(`/dashboard/api/gigs/${encodeURIComponent(e)}/status`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({status:t})})).discord_title_sync?.status;P(n===`error`?`Updated gig status; Discord title sync failed`:`Updated gig status`,n===`error`?`error`:`ok`),await Lt(),T===e&&await Kt(e)}catch(e){F(e,`Unable to update gig`)}finally{I(`gig:${e}:status`,!1)}}async function Xt(e,t,n){I(`application:${t}:status`,!0);try{await q(`/dashboard/api/gigs/${encodeURIComponent(e)}/applications/${encodeURIComponent(t)}/status`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({status:n})}),P(`Updated candidate status`,`ok`),await Lt(),T===e&&await Kt(e)}catch(e){F(e,`Unable to update candidate`)}finally{I(`application:${t}:status`,!1)}}async function Qt(e,t){let n=t.trim();if(!n)return P(`Paste a CRM Contact profile first`,`warning`),!1;I(`gig:${e}:addCandidate`,!0);try{return await q(`/dashboard/api/gigs/${encodeURIComponent(e)}/applications`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({crm_profile:n})}),P(`Added candidate`,`ok`),await Lt(),T===e&&await Kt(e),!0}catch(e){return F(e,`Unable to add candidate`),!1}finally{I(`gig:${e}:addCandidate`,!1)}}function $t(){let e=new URLSearchParams({limit:`25`});$e.trim()&&e.set(`query`,$e.trim()),tt&&e.set(`is_member`,tt);for(let[t,n]of Object.entries(j))n&&e.set(t,n);return`/dashboard/api/people?${e.toString()}`}async function en(){I(`people`,!0);try{k(await q($t()))}catch(e){F(e,`Unable to load people`)}finally{I(`people`,!1)}}function tn(){let e=new URLSearchParams({limit:`25`});ct.trim()&&e.set(`query`,ct.trim()),ut&&e.set(`onboarding_state`,ut),ft.trim()&&e.set(`onboarder`,ft.trim());for(let[t,n]of Object.entries(M))n&&e.set(t,n);return`/dashboard/api/onboarding?${e.toString()}`}async function nn(){I(`onboarding`,!0);try{A(await q(tn()))}catch(e){F(e,`Unable to load onboarding`)}finally{I(`onboarding`,!1)}}async function rn(e,t){if(!e)return P(`Missing CRM contact`,`error`),null;let n=`onboarding-email-draft:${e}`;I(n,!0);try{let n=await q(`/dashboard/api/onboarding/${encodeURIComponent(e)}/email/draft`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)});return P(`Drafted onboarding email`,`ok`),n}catch(e){return F(e,`Unable to draft onboarding email`),null}finally{I(n,!1)}}async function an(e,t,n){if(!e)return P(`Missing CRM contact`,`error`),null;let r=`onboarding-email-send:${e}`;I(r,!0);try{let r=await q(`/dashboard/api/onboarding/${encodeURIComponent(e)}/email/send`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({...t,markdown_body:n})});return A(t=>t.map(t=>t.crm_contact_id===e?{...t,onboarding_email_sent_at:r.onboarding_email_sent_at||t.onboarding_email_sent_at,onboarding_email_sent_by:r.onboarding_email_sent_by||t.onboarding_email_sent_by,onboarding_email_recipient:r.onboarding_email_recipient||r.recipient_email||t.onboarding_email_recipient}:t)),P(`Sent onboarding email`,`ok`),r}catch(e){return F(e,`Unable to send onboarding email`),null}finally{I(r,!1)}}async function on(){I(`audit`,!0);try{ge(await q(`/dashboard/api/audit-events?limit=25`))}catch(e){F(e,`Unable to load audit events`)}finally{I(`audit`,!1)}}async function sn(){I(`agent`,!0);try{ve(await q(`/dashboard/api/agent?limit=100`))}catch(e){F(e,`Unable to load agent report`)}finally{I(`agent`,!1)}}async function cn(){I(`configuration`,!0);try{be((await q(`/dashboard/api/configuration`)).items)}catch(e){F(e,`Unable to load configuration`)}finally{I(`configuration`,!1)}}async function ln(e,t){I(`configuration:${e}`,!0);try{be((await q(`/dashboard/api/configuration/${encodeURIComponent(e)}`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({value:t})})).items),P(`Saved ${e}`,`ok`)}catch(t){F(t,`Unable to save ${e}`)}finally{I(`configuration:${e}`,!1)}}async function un(e){I(`configuration:${e}`,!0);try{be((await q(`/dashboard/api/configuration/${encodeURIComponent(e)}`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({clear:!0})})).items),P(`Cleared ${e}`,`ok`)}catch(t){F(t,`Unable to clear ${e}`)}finally{I(`configuration:${e}`,!1)}}async function mn(e){I(`detail:${e}`,!0),P(`Loading ${e}`);try{we(await q(`/dashboard/api/jobs/${encodeURIComponent(e)}`)),P(`Loaded ${e}`,`ok`)}catch(e){F(e,`Unable to load task detail`)}finally{I(`detail:${e}`,!1)}}async function hn(e){I(`rerun:${e}`,!0),P(`Rerunning ${e}`);try{let t=await q(`/dashboard/api/jobs/${encodeURIComponent(e)}/rerun`,{method:`POST`});t.dry_run?P(`Dry run only: would rerun ${t.would_enqueue?.job_type||e}`,`warning`):(P(`Queued rerun ${t.job_id}`,`ok`),await It())}catch(e){F(e,`Unable to rerun task`)}finally{I(`rerun:${e}`,!1)}}async function yn(){I(`syncPeople`,!0),P(`Queueing people sync`);try{let e=await q(`/dashboard/api/sync/people`,{method:`POST`});e.dry_run?P(`Dry run only: would queue ${e.would_enqueue?.job_type||`people sync`}`,`warning`):P(`Queued people sync ${e.job_id}`,`ok`)}catch(e){F(e,`Unable to queue people sync`)}finally{I(`syncPeople`,!1)}}async function wn(e,t){let n=String(e||``).trim(),r=t.trim();if(!n){P(`Missing CRM contact id`,`error`);return}if(!r){P(`Enter a 508 username`,`error`);return}I(`onboarder:${n}`,!0),P(`Assigning ${r}`);try{let e=await q(`/dashboard/api/onboarding/${encodeURIComponent(n)}/onboarder`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({onboarder:r})});A(t=>t.map(t=>t.crm_contact_id===e.contact_id?{...t,onboarder:e.onboarder,onboarding_state:e.state_updated&&e.onboarding_state?e.onboarding_state:t.onboarding_state,onboarding_status_label:e.onboarding_status_label||(e.state_updated?void 0:t.onboarding_status_label)}:t)),P(`Assigned ${e.onboarder}`,`ok`)}catch(e){F(e,`Unable to assign onboarder`)}finally{I(`onboarder:${n}`,!1)}}async function En(e,t){let n=String(e||``).trim(),r=t.trim();if(!n){P(`Missing CRM contact id`,`error`);return}if(!r){P(`Choose an onboarding status`,`error`);return}I(`onboarding-status:${n}`,!0),P(`Updating onboarding status`);try{let e=await q(`/dashboard/api/onboarding/${encodeURIComponent(n)}/status`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({status:r})}),t=_n(e.onboarding_state),i=e.onboarding_status_label||Zt(t);A(n=>n.map(n=>n.crm_contact_id===e.contact_id?{...n,onboarding_state:t,onboarding_status_label:i}:n).filter(n=>n.crm_contact_id!==e.contact_id||!gn.has(t))),P(`Status set to ${i}`,`ok`)}catch(e){F(e,`Unable to update onboarding status`)}finally{I(`onboarding-status:${n}`,!1)}}async function Dn(e){let t=e.email.trim().toLowerCase(),n=e.first_name.trim();if(!t?.endsWith(`@508.dev`))return P(`Enter the engineer's @508.dev email`,`error`),null;if(!n)return P(`Enter the engineer name`,`error`),null;I(`engineerSetup`,!0);try{let r=await q(`/dashboard/api/onboarding/engineers`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({...e,email:t,first_name:n})});return P(`Set up ${r.employee_name||r.user||t}`,`ok`),r}catch(e){if(e instanceof vn&&e.status===409){let t=e.payload&&typeof e.payload==`object`?e.payload:null,n=(Array.isArray(t?.matches)?t.matches:[]).map(e=>e?.label||e?.email).filter(Boolean).slice(0,2).join(`, `);P(n?`Similar account exists: ${n}`:`Similar account exists; confirm before creating`,`error`)}else P(e instanceof Error?e.message:`Unable to set up engineer`,`error`);return null}finally{I(`engineerSetup`,!1)}}async function On(){I(`logout`,!0);try{let e=await q(`/auth/logout`,{method:`POST`});window.location.assign(e.end_session_url||`/dashboard`)}catch(e){F(e,`Unable to log out`),I(`logout`,!1)}}(0,l.useEffect)(()=>{Mt().then(e=>{let t=Sn(),n=e.includes(fn[t])?t:Object.keys(dn).find(t=>e.includes(fn[t]))||`people`;le(n===`gigs`?Cn():``),ue(n===`projects`?Cn(`projects`):``),i(n),(!Object.hasOwn(dn,xn())||n!==t)&&window.history.replaceState({view:n},``,dn[n])}).catch(e=>{F(e,`Dashboard failed to load`)})},[]),(0,l.useEffect)(()=>{let e=()=>{le(Cn()),ue(Cn(`projects`)),yt.current(Sn(),!1)};return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)},[]),(0,l.useEffect)(()=>{if(!a.message)return;let e=window.setTimeout(()=>o({message:``}),4500);return()=>window.clearTimeout(e)},[a.message]),(0,l.useEffect)(()=>{},[]),(0,l.useEffect)(()=>{s.length!==0&&(bt(`gigs:read`)&&Jt(),r===`people`&&en(),r===`gigs`&&Lt(),r===`projects`&&z(),r===`onboarding`&&nn(),r===`jobs`&&It(),r===`agent`&&sn(),r===`audit`&&on(),r===`configuration`&&cn())},[r]),(0,l.useEffect)(()=>{s.length!==0&&(bt(`gigs:read`)&&Jt(),r===`people`&&en(),r===`gigs`&&Lt(),r===`projects`&&z(),r===`onboarding`&&nn(),r===`jobs`&&It(),r===`agent`&&sn(),r===`audit`&&on(),r===`configuration`&&cn())},[s]),(0,l.useEffect)(()=>{r===`jobs`&&s.length>0&&It()},[Ne,Fe]),(0,l.useEffect)(()=>{r===`gigs`&&s.length>0&&Lt()},[ze,Ue,Ge]),(0,l.useEffect)(()=>{r===`projects`&&s.length>0&&z()},[Ye]),(0,l.useEffect)(()=>{r===`gigs`&&T&&s.length>0&&Kt(T)},[r,T,s]),(0,l.useEffect)(()=>{r===`people`&&s.length>0&&en()},[tt]),(0,l.useEffect)(()=>{r===`people`&&s.length>0&&en()},[j]),(0,l.useEffect)(()=>{r===`onboarding`&&s.length>0&&nn()},[ut]),(0,l.useEffect)(()=>{r===`onboarding`&&s.length>0&&nn()},[M]);let kn=(0,l.useMemo)(()=>Tn(`jobs`,f,je.jobs),[f,je.jobs]),An=(0,l.useMemo)(()=>Tn(`people`,O,je.people),[O,je.people]),jn=(0,l.useMemo)(()=>Tn(`onboarding`,me,je.onboarding),[me,je.onboarding]),Fn=(0,l.useMemo)(()=>Tn(`gigs`,h,je.gigs),[h,je.gigs]),In=(0,l.useMemo)(()=>Tn(`projects`,te,je.projects),[te,je.projects]),Ln=(0,l.useMemo)(()=>se?.id===T?se:Fn.find(e=>e.id===T)||null,[se,T,Fn]),Rn=(0,l.useMemo)(()=>In.find(e=>e.id===E)||null,[E,In]),zn=(0,l.useMemo)(()=>Tn(`audit`,he,je.audit),[he,je.audit]),Bn=(0,l.useMemo)(()=>f.reduce((e,t)=>(e[t.status]=(e[t.status]||0)+1,e),{}),[f]),Vn=Object.keys(pn).filter(e=>!j[e]),Hn=Object.keys(pn).filter(e=>e!==`sync_status`&&e!==`email_508`&&!M[e]);function Un(e){if(e.type===`stale_recruiting_gig`){let t=e.engagement_id||(e.id.startsWith(`stale-recruiting:`)?e.id.slice(17):``);t?Ot(t):(Be(`recruiting`),wt(`gigs`,!0))}D(!1)}(0,l.useEffect)(()=>{!Vn.includes(it)&&Vn[0]&&at(Vn[0])},[Vn,it]),(0,l.useEffect)(()=>{let e=pn[it]?.options;e?.[0]&&!e.some(([e])=>e===ot)&&st(e[0][0])},[it,ot]),(0,l.useEffect)(()=>{!Hn.includes(ht)&&Hn[0]&>(Hn[0])},[Hn,ht]),(0,l.useEffect)(()=>{let e=pn[ht]?.options;e?.[0]&&!e.some(([e])=>e===_t)&&vt(e[0][0])},[ht,_t]);let Wn=[t?.email,t?.crm_contact_id?`CRM ${t.crm_contact_id}`:``,t?.actor_provider].filter(Boolean).join(` | `);return(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`header`,{className:`sticky top-0 z-20 border-b bg-background/90 backdrop-blur`,children:(0,R.jsxs)(`div`,{className:`mx-auto flex max-w-7xl flex-col gap-4 px-5 py-4 md:flex-row md:items-center md:justify-between`,children:[(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`h1`,{className:`text-xl font-bold`,children:`508 Operations Dashboard`}),(0,R.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Operations view for authenticated 508 operators.`})]}),(0,R.jsxs)(`div`,{className:`flex min-w-0 items-center gap-3`,children:[bt(`gigs:read`)?(0,R.jsx)(`div`,{className:`relative`,children:(0,R.jsxs)(B,{id:`notifications`,type:`button`,variant:`outline`,size:`icon`,"aria-label":`Notifications`,"aria-expanded":pe,onClick:()=>D(e=>!e),children:[(0,R.jsx)(g,{}),de.length>0?(0,R.jsx)(`span`,{className:`absolute -right-1 -top-1 grid min-h-5 min-w-5 place-items-center rounded-full bg-red-500 px-1 text-[11px] font-bold text-white`,children:de.length}):null]})}):null,(0,R.jsxs)(`div`,{className:`grid min-w-0 gap-0.5 text-right text-sm text-muted-foreground`,children:[(0,R.jsx)(`strong`,{id:`userName`,className:`truncate text-foreground`,children:t?.display_name||t?.email||t?.subject||`Loading user`}),(0,R.jsx)(`span`,{id:`userMeta`,className:`truncate`,children:Wn||`Checking session`})]}),(0,R.jsxs)(B,{id:`logout`,type:`button`,variant:`outline`,onClick:On,disabled:Te.logout,children:[(0,R.jsx)(ee,{}),`Log out`]})]})]})}),(0,R.jsx)(Mn,{open:pe,notifications:de,loading:Te.notifications,onClose:()=>D(!1),onRefresh:Jt,onOpenNotification:Un}),(0,R.jsx)(Pn,{toast:a}),null,(0,R.jsx)(Nn,{choice:ke,loading:!!(ke&&Te[`project:${ke.projectId}:historical`]),crmContactUrl:Tt,onClose:()=>Ae(null),onChoose:e=>{ke&&Wt(ke.projectId,ke.person,e)}}),(0,R.jsxs)(`main`,{className:`mx-auto grid max-w-7xl grid-cols-1 gap-5 px-5 py-5 md:grid-cols-[190px_minmax(0,1fr)]`,children:[(0,R.jsx)(`nav`,{className:`grid content-start gap-1 md:sticky md:top-24`,"aria-label":`Dashboard sections`,children:[[`people`,`People`,ce],[`gigs`,`Gigs`,_],[`projects`,`Projects`,x],[`onboarding`,`Onboarding`,v],[`jobs`,`Background tasks`,m],[`agent`,`Agent`,ae],[`audit`,`Audit`,b],[`configuration`,`Configuration`,ie]].filter(([e])=>St(e)).map(([e,t,n])=>(0,R.jsxs)(`a`,{className:L(`flex min-h-10 items-center gap-2 rounded-md border border-transparent px-3 text-sm font-extrabold text-muted-foreground hover:border-border hover:bg-secondary hover:text-foreground`,r===e&&`border-primary bg-accent text-accent-foreground`),"data-view-link":e,"data-permission":fn[e],href:dn[e],"aria-current":r===e?`page`:void 0,onClick:t=>{t.preventDefault(),wt(e,!0)},children:[(0,R.jsx)(n,{className:`size-4`}),t]},e))}),(0,R.jsxs)(`div`,{className:`grid min-w-0 gap-5`,children:[r===`people`?(0,R.jsx)($n,{crmBaseUrl:u,people:An,sort:je.people,canSync:xt(`people:sync`),loading:Te,peopleQuery:$e,peopleMember:tt,peopleFilters:j,peopleFilterKind:it,peopleFilterValue:ot,peopleFilterKeys:Vn,onSearch:en,onSync:yn,onSort:e=>Dt(`people`,e),setPeopleQuery:et,setPeopleMember:nt,setPeopleFilterKind:at,setPeopleFilterValue:st,addFilter:()=>{rt(e=>({...e,[it]:ot}))},removeFilter:e=>{rt(t=>{let n={...t};return delete n[e],n})},crmContactUrl:Tt,crmAttachmentUrl:Et}):null,r===`gigs`?(0,R.jsx)(Jn,{gigs:Fn,selectedGig:Ln,selectedGigId:T,sort:je.gigs,loading:Te,status:ze,query:Ve,includeHistorical:Ue,limit:Ge,staleDays:Ze,canWrite:bt(`gigs:write`),canIncludeHistorical:bt(`people:read`),crmContactUrl:Tt,crmAttachmentUrl:Et,setStatus:Be,setQuery:He,setIncludeHistorical:We,setLimit:Ke,onRefresh:qt,onSort:e=>Dt(`gigs`,e),onOpenGig:Ot,onCloseGig:kt,onUpdateStatus:Yt,onAddApplication:Qt,onUpdateApplicationStatus:Xt}):null,r===`projects`?(0,R.jsx)(Gn,{projects:In,selectedProject:Rn,selectedProjectId:E,summary:C,wikiMatches:re,sort:je.projects,loading:Te,query:qe,status:Ye,canSync:xt(`projects:sync`),canWrite:bt(`projects:write`),crmContactUrl:Tt,setQuery:Je,setStatus:Xe,onSearch:z,onSync:Rt,onSearchCustomers:V,onSearchContacts:H,onSearchAccountManagers:zt,onLoadCostCenters:Bt,onCreateProject:U,onUpdateStatus:W,onBulkUpdate:Vt,onAddUser:Ht,onRemoveUser:Ut,onAddHistoricalMember:Wt,onRemoveHistoricalMember:Gt,onUpdateWikiMatch:G,onWikiMatches:K,onOpenProject:At,onCloseProject:jt,onSort:e=>Dt(`projects`,e)}):null,r===`onboarding`?(0,R.jsx)(er,{people:jn,sort:je.onboarding,loading:Te,onboardingQuery:ct,onboardingState:ut,onboarderFilter:ft,onboardingFilters:M,onboardingFilterKind:ht,onboardingFilterValue:_t,onboardingFilterKeys:Hn,onSearch:nn,onSort:e=>Dt(`onboarding`,e),onAssign:wn,onStatusChange:En,onDraftEmail:rn,onSendEmail:an,onSetupEngineer:Dn,setOnboardingQuery:lt,setOnboardingState:dt,setOnboarderFilter:pt,setOnboardingFilterKind:gt,setOnboardingFilterValue:vt,addFilter:()=>{mt(e=>({...e,[ht]:_t}))},removeFilter:e=>{mt(t=>{let n={...t};return delete n[e],n})},crmContactUrl:Tt,crmAttachmentUrl:Et,canWrite:bt(`onboarding:write`),canConfigure:bt(`configuration:write`),onOpenConfiguration:()=>{Se({category:`Onboarding`,nonce:Date.now()}),wt(`configuration`,!0)}}):null,r===`jobs`?(0,R.jsx)(cr,{jobs:kn,jobDetail:Ce,sort:je.jobs,loading:Te,minutes:Ne,status:Fe,jobType:Le,jobCounts:Bn,canWrite:xt(`jobs:write`),setMinutes:Pe,setStatus:Ie,setJobType:Re,onSearch:It,onSort:e=>Dt(`jobs`,e),onDetail:mn,onRerun:hn}):null,r===`audit`?(0,R.jsx)(lr,{events:zn,sort:je.audit,loading:Te,onRefresh:on,onSort:e=>Dt(`audit`,e)}):null,r===`agent`?(0,R.jsx)(ur,{report:_e,loading:Te,onRefresh:sn}):null,r===`configuration`?(0,R.jsx)(dr,{items:ye,loading:Te,canWrite:bt(`configuration:write`),focusCategory:xe?.category,focusNonce:xe?.nonce,onRefresh:cn,onSave:ln,onClear:un}):null]})]})]})}function Mn({open:e,notifications:t,loading:n,onClose:r,onRefresh:i,onOpenNotification:a}){return e?(0,R.jsxs)(`div`,{className:`fixed inset-0 z-40`,"aria-labelledby":`notificationsTitle`,"aria-modal":`true`,role:`dialog`,children:[(0,R.jsx)(`button`,{type:`button`,className:`absolute inset-0 cursor-default bg-black/45`,"aria-label":`Close notifications`,onClick:r}),(0,R.jsxs)(`aside`,{className:`absolute right-0 top-0 grid h-full w-full max-w-md grid-rows-[auto_minmax(0,1fr)] border-l bg-background shadow-2xl`,children:[(0,R.jsxs)(`div`,{className:`flex items-center justify-between gap-3 border-b p-4`,children:[(0,R.jsxs)(`div`,{className:`grid gap-0.5`,children:[(0,R.jsx)(`strong`,{id:`notificationsTitle`,className:`text-base`,children:`Notifications`}),(0,R.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:t.length===0?`No active notifications`:`${t.length} active`})]}),(0,R.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,R.jsxs)(B,{type:`button`,variant:`outline`,size:`sm`,onClick:i,disabled:n,children:[(0,R.jsx)(C,{}),`Refresh`]}),(0,R.jsx)(B,{type:`button`,variant:`ghost`,size:`icon`,"aria-label":`Close`,onClick:r,children:(0,R.jsx)(w,{})})]})]}),(0,R.jsx)(`div`,{className:`min-h-0 overflow-auto p-4`,children:t.length===0?(0,R.jsx)(`div`,{className:`rounded-md border border-dashed p-6 text-sm text-muted-foreground`,children:`No active notifications.`}):(0,R.jsx)(`div`,{className:`grid gap-3`,children:t.map(e=>(0,R.jsxs)(`button`,{type:`button`,className:`grid gap-2 rounded-md border p-3 text-left hover:bg-secondary`,onClick:()=>a(e),children:[(0,R.jsx)(`span`,{className:`text-sm font-bold`,children:e.title}),(0,R.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:e.message})]},e.id))})})]})]}):null}function Nn({choice:e,loading:t,crmContactUrl:n,onClose:r,onChoose:i}){return e?(0,R.jsxs)(`div`,{className:`fixed inset-0 z-50 grid place-items-center p-4`,"aria-labelledby":`historicalPersonChoiceTitle`,"aria-modal":`true`,role:`dialog`,children:[(0,R.jsx)(`button`,{type:`button`,className:`absolute inset-0 cursor-default bg-black/45`,"aria-label":`Close person selection`,onClick:r}),(0,R.jsxs)(`div`,{className:`relative grid w-full max-w-2xl gap-4 rounded-md border bg-background p-5 shadow-2xl`,children:[(0,R.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`strong`,{id:`historicalPersonChoiceTitle`,className:`block text-base`,children:`Choose person record`}),(0,R.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:e.person})]}),(0,R.jsx)(B,{type:`button`,variant:`ghost`,size:`icon`,"aria-label":`Close person selection`,onClick:r,children:(0,R.jsx)(w,{})})]}),(0,R.jsx)(`div`,{className:`grid gap-2`,children:e.candidates.map(e=>(0,R.jsxs)(`div`,{className:`grid gap-3 rounded-md border p-3 md:grid-cols-[minmax(0,1fr)_auto] md:items-center`,children:[(0,R.jsxs)(`div`,{className:`min-w-0`,children:[(0,R.jsx)(`strong`,{className:`block truncate`,children:e.label||e.full_name||e.email||`Person`}),(0,R.jsxs)(`div`,{className:`flex flex-wrap gap-x-3 gap-y-1 text-sm text-muted-foreground`,children:[e.email?(0,R.jsx)(`span`,{children:e.email}):null,e.sources?.length?(0,R.jsx)(`span`,{children:e.sources.join(`, `)}):null,e.erpnext_user_id?(0,R.jsxs)(`span`,{children:[`ERP `,e.erpnext_user_id]}):null,e.supplier_erpnext_id?(0,R.jsxs)(`span`,{children:[`Supplier `,e.supplier_erpnext_id]}):null,e.crm_contact_id&&n(e.crm_contact_id)?(0,R.jsx)(`a`,{className:`font-semibold text-primary underline-offset-4 hover:underline`,href:n(e.crm_contact_id),target:`_blank`,rel:`noreferrer`,children:`CRM`}):null]})]}),(0,R.jsx)(B,{type:`button`,disabled:t,onClick:()=>i(e.candidate_id),children:`Select`})]},e.candidate_id))})]})]}):null}function Pn({toast:e}){return e.message?(0,R.jsx)(`div`,{id:`toast`,role:`status`,className:L(`fixed bottom-5 right-5 z-50 max-w-sm rounded-md border bg-background px-4 py-3 text-sm font-semibold shadow-lg`,e.tone===`ok`&&`border-emerald-500/40 text-emerald-300`,e.tone===`warning`&&`border-amber-500/40 text-amber-200`,e.tone===`error`&&`border-red-500/40 text-red-300`),children:e.message}):null}function Fn({filters:e,onRemove:t,suffix:n=`filter`}){return(0,R.jsx)(`fieldset`,{className:`m-0 flex min-h-7 flex-wrap gap-2 border-0 p-0`,"aria-label":`Active filters`,children:Object.entries(e).map(([e,r])=>{let i=pn[e],a=i.options.find(([e])=>e===r),o=`${i.label}: ${a?a[1]:r}`;return(0,R.jsxs)(B,{type:`button`,variant:`outline`,size:`sm`,className:`rounded-full`,"aria-label":`Remove ${o} ${n}`,onClick:()=>t(e),children:[o,` x`]},e)})})}var In=[`recruiting`,`filled`,`unknown`,`lost`,`outdated`],Ln=[`suggested`,`interested`,`reviewing`,`contacted`,`accepted`,`unavailable`,`rejected`,`withdrawn`];function Rn(e){return String(e||``).replace(/[-_]+/g,` `).replace(/\s+/g,` `).trim().replace(/\b\w/g,e=>e.toUpperCase())}function zn(e){let t=[e.last_activity_at,e.last_status_changed_at,e.posted_at,e.created_at].map(e=>e?new Date(e).getTime():NaN).filter(e=>!Number.isNaN(e));return t.length>0?new Date(Math.max(...t)).toISOString():``}function Bn(e,t){if(e.status!==`recruiting`)return null;let n=Jt(zn(e));return n===null||ne.projects.map(e=>e.id),[e.projects]),m=(0,l.useMemo)(()=>new Set(p),[p]),g=n.filter(e=>m.has(e)),_=e.projects.length>0&&g.length===e.projects.length;(0,l.useEffect)(()=>{r(e=>e.filter(e=>m.has(e)))},[m]);function v(e,t){r(n=>t?Array.from(new Set([...n,e])):n.filter(t=>t!==e))}async function b(){let t={};i&&(t.status=i),o&&(t.project_type=o),await e.onBulkUpdate(g,t)&&(r([]),a(``),s(``),u(!1))}let x=(0,R.jsxs)(V,{className:`grid gap-3 p-4 md:grid-cols-[minmax(0,1fr)_180px_auto_auto_auto] md:items-end`,children:[(0,R.jsxs)(W,{children:[`Search projects`,(0,R.jsx)(U,{id:`projectQuery`,value:e.query,autoComplete:`off`,placeholder:`Project, customer, ERP id`,onChange:t=>e.setQuery(t.target.value),onKeyDown:t=>t.key===`Enter`&&e.onSearch()})]}),(0,R.jsxs)(W,{children:[`Status`,(0,R.jsxs)(Vt,{id:`projectStatus`,value:e.status,onChange:t=>e.setStatus(t.target.value),children:[(0,R.jsx)(`option`,{value:`Open`,children:`Open`}),(0,R.jsx)(`option`,{value:``,children:`Any status`})]})]}),(0,R.jsxs)(B,{id:`refreshProjects`,type:`button`,onClick:e.onSearch,disabled:e.loading.projects,children:[(0,R.jsx)(C,{}),`Refresh`]}),e.canSync?(0,R.jsxs)(B,{id:`syncProjects`,type:`button`,variant:`outline`,onClick:e.onSync,disabled:e.loading.syncProjects,children:[(0,R.jsx)(C,{}),`Sync ERP`]}):null,(0,R.jsxs)(B,{id:`wikiProjectMatches`,type:`button`,variant:`outline`,onClick:e.onWikiMatches,disabled:e.loading.wikiMatches,children:[(0,R.jsx)(ne,{}),`Wiki match`]})]});return e.selectedProjectId&&!e.selectedProject&&e.loading.projects?(0,R.jsxs)(R.Fragment,{children:[x,(0,R.jsxs)(V,{children:[(0,R.jsx)(H,{children:(0,R.jsx)(zt,{children:`Project detail`})}),(0,R.jsx)(Bt,{className:`text-sm text-muted-foreground`,children:`Loading project.`})]})]}):e.selectedProjectId&&!e.selectedProject?(0,R.jsxs)(R.Fragment,{children:[x,(0,R.jsxs)(V,{children:[(0,R.jsx)(H,{children:(0,R.jsx)(zt,{children:`Project detail`})}),(0,R.jsxs)(Bt,{className:`grid gap-3`,children:[(0,R.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`This project is not in the current result set. Clear filters or refresh the project list.`}),(0,R.jsxs)(B,{type:`button`,variant:`outline`,onClick:e.onCloseProject,children:[(0,R.jsx)(h,{}),`Back to projects`]})]})]})]}):e.selectedProject?(0,R.jsxs)(R.Fragment,{children:[x,(0,R.jsx)(qn,{project:e.selectedProject,loading:e.loading,canWrite:e.canWrite,crmContactUrl:e.crmContactUrl,onBack:e.onCloseProject,onUpdateStatus:e.onUpdateStatus,onAddUser:e.onAddUser,onRemoveUser:e.onRemoveUser,onAddHistoricalMember:e.onAddHistoricalMember,onRemoveHistoricalMember:e.onRemoveHistoricalMember})]}):(0,R.jsxs)(R.Fragment,{children:[x,(0,R.jsxs)(`section`,{className:`grid gap-3 md:grid-cols-2`,"aria-label":`Project summary`,children:[(0,R.jsx)(On,{id:`projectMetricOpen`,label:`Open`,value:e.summary.open_project_count||0}),(0,R.jsx)(On,{id:`projectMetricTotal`,label:`Projects`,value:e.summary.project_count||0})]}),e.canWrite?(0,R.jsxs)(V,{className:`flex flex-wrap items-center justify-between gap-3 p-4`,children:[(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Selected`}),(0,R.jsxs)(`strong`,{className:`block`,children:[g.length,` project(s)`]})]}),(0,R.jsxs)(`div`,{className:`flex flex-wrap gap-2`,children:[(0,R.jsxs)(B,{type:`button`,onClick:()=>f(!0),children:[(0,R.jsx)(S,{}),`New project`]}),(0,R.jsx)(B,{type:`button`,variant:`outline`,disabled:g.length===0,onClick:()=>u(!0),children:`Bulk edit`})]})]}):null,d?(0,R.jsx)(Kn,{loading:e.loading.createProject,onClose:()=>f(!1),onSearchCustomers:e.onSearchCustomers,onSearchContacts:e.onSearchContacts,onSearchAccountManagers:e.onSearchAccountManagers,onLoadCostCenters:e.onLoadCostCenters,onCreateProject:e.onCreateProject}):null,c?(0,R.jsxs)(`div`,{className:`fixed inset-0 z-50 grid place-items-center p-4`,"aria-labelledby":`bulkProjectEditTitle`,"aria-modal":`true`,role:`dialog`,children:[(0,R.jsx)(`button`,{type:`button`,className:`absolute inset-0 cursor-default bg-black/45`,"aria-label":`Close bulk project edit`,onClick:()=>u(!1)}),(0,R.jsxs)(`div`,{className:`relative grid w-full max-w-lg gap-4 rounded-md border bg-background p-5 shadow-2xl`,children:[(0,R.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`strong`,{id:`bulkProjectEditTitle`,className:`block text-base`,children:`Bulk edit projects`}),(0,R.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[g.length,` selected`]})]}),(0,R.jsx)(B,{type:`button`,variant:`ghost`,size:`icon`,"aria-label":`Close bulk project edit`,onClick:()=>u(!1),children:(0,R.jsx)(w,{})})]}),(0,R.jsxs)(`div`,{className:`grid gap-3`,children:[(0,R.jsx)(`strong`,{className:`text-sm`,children:`Changes`}),(0,R.jsxs)(W,{children:[`Status`,(0,R.jsxs)(Vt,{value:i,onChange:e=>a(e.target.value),children:[(0,R.jsx)(`option`,{value:``,children:`No change`}),(0,R.jsx)(`option`,{value:`Open`,children:`Open`}),(0,R.jsx)(`option`,{value:`Completed`,children:`Completed`}),(0,R.jsx)(`option`,{value:`Cancelled`,children:`Cancelled`})]})]}),(0,R.jsxs)(W,{children:[`ERP Type`,(0,R.jsxs)(Vt,{value:o,onChange:e=>s(e.target.value),children:[(0,R.jsx)(`option`,{value:``,children:`No change`}),(0,R.jsx)(`option`,{value:`Internal`,children:`Internal`}),(0,R.jsx)(`option`,{value:`External`,children:`External`})]})]})]}),(0,R.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-2`,children:[(0,R.jsx)(B,{type:`button`,variant:`outline`,onClick:()=>u(!1),children:`Cancel`}),(0,R.jsx)(B,{type:`button`,disabled:e.loading.projectsBulkUpdate||g.length===0||!i&&!o,onClick:()=>void b(),children:`Apply changes`})]})]})]}):null,(0,R.jsxs)(V,{children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`ERP projects`}),(0,R.jsx)(`span`,{id:`projectsStatus`,className:`text-sm text-muted-foreground`,children:e.loading.projects?`Loading`:`${e.projects.length} shown | synced ${qt(e.summary.last_synced_at)}`})]}),(0,R.jsx)(kn,{hidden:e.projects.length!==0,children:`No projects match this view. Sync ERP projects if the cache is empty.`}),(0,R.jsx)(`div`,{className:`overflow-x-auto`,children:(0,R.jsxs)(Ht,{id:`projectsTable`,className:L(`min-w-[1100px]`,e.projects.length===0&&`hidden`),"aria-label":`ERP projects`,children:[(0,R.jsx)(Ut,{children:(0,R.jsxs)(Gt,{children:[e.canWrite?(0,R.jsx)(G,{className:`w-[48px]`,children:(0,R.jsx)(`input`,{type:`checkbox`,"aria-label":`Select all visible projects`,checked:_,onChange:e=>{r(e.target.checked?p:[])}})}):null,(0,R.jsx)(Dn,{className:`w-[24%]`,label:`Project`,scope:`projects`,sort:e.sort,sortKey:`display_name`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[16%]`,label:`Customer`,scope:`projects`,sort:e.sort,sortKey:`customer`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[10%]`,label:`Status`,scope:`projects`,sort:e.sort,sortKey:`status`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(G,{className:`w-[16%]`,children:`Timeline`}),(0,R.jsx)(Dn,{className:`w-[10%]`,label:`Roster`,scope:`projects`,sort:e.sort,sortKey:`roster_count`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[14%]`,label:`Modified`,scope:`projects`,sort:e.sort,sortKey:`modified`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(G,{children:`ERP`})]})}),(0,R.jsx)(Wt,{id:`projectsBody`,children:e.projects.map(t=>{let n=t.roster_members||[];return(0,R.jsxs)(Gt,{children:[e.canWrite?(0,R.jsx)(K,{children:(0,R.jsx)(`input`,{type:`checkbox`,"aria-label":`Select ${t.display_name}`,checked:g.includes(t.id),onChange:e=>v(t.id,e.target.checked)})}):null,(0,R.jsxs)(K,{children:[(0,R.jsx)(`button`,{type:`button`,className:`text-left font-bold text-primary underline-offset-4 hover:underline`,onClick:()=>e.onOpenProject(t.id),children:t.display_name}),(0,R.jsxs)(`div`,{className:`mt-1 flex flex-wrap items-center gap-1.5`,children:[t.project_type?(0,R.jsx)(z,{variant:`neutral`,children:t.project_type}):null,t.linked_engagement_count?(0,R.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[t.linked_engagement_count,` linked gig`]}):null]})]}),(0,R.jsx)(K,{children:t.customer_erpnext_url?(0,R.jsxs)(`a`,{className:`inline-flex items-center gap-1 font-semibold text-primary underline-offset-4 hover:underline`,href:t.customer_erpnext_url,target:`_blank`,rel:`noreferrer`,children:[t.customer,(0,R.jsx)(y,{className:`size-3.5`})]}):t.customer||`None`}),(0,R.jsx)(K,{children:(0,R.jsx)(z,{variant:Vn(t.source_status),children:t.source_status||`Unknown`})}),(0,R.jsx)(K,{children:[t.actual_start_date,t.actual_end_date].filter(Boolean).map(e=>Hn(e)).join(` to `)||`Not set`}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`grid gap-1`,children:[(0,R.jsx)(`strong`,{children:n.length}),(0,R.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[n.map(Un).slice(0,4).join(`, `)||`No ERP roster`,n.length>4?` +${n.length-4}`:``]})]})}),(0,R.jsx)(K,{children:qt(t.source_modified_at)}),(0,R.jsx)(K,{className:`text-xs`,children:t.erpnext_project_url?(0,R.jsxs)(`a`,{className:`inline-flex items-center gap-1 font-mono font-semibold text-primary underline-offset-4 hover:underline`,href:t.erpnext_project_url,target:`_blank`,rel:`noreferrer`,children:[t.erpnext_project_id,(0,R.jsx)(y,{className:`size-3.5`})]}):(0,R.jsx)(`span`,{className:`font-mono`,children:`Unlinked`})})]},t.id)})})]})})]}),e.wikiMatches?(0,R.jsxs)(V,{children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`Wiki match preview`}),(0,R.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[e.wikiMatches.document?.title||`Client & Project Info`,` |`,` `,qt(e.wikiMatches.document?.updatedAt)]})]}),(0,R.jsx)(`div`,{className:`overflow-x-auto`,children:(0,R.jsxs)(Ht,{id:`wikiMatchesTable`,className:`min-w-[920px]`,"aria-label":`Wiki matches`,children:[(0,R.jsx)(Ut,{children:(0,R.jsxs)(Gt,{children:[(0,R.jsx)(G,{children:`ERP project`}),(0,R.jsx)(G,{children:`Best wiki row`}),(0,R.jsx)(G,{children:`Confidence`}),(0,R.jsx)(G,{children:`Section`}),(0,R.jsx)(G,{children:`Decision`})]})}),(0,R.jsx)(Wt,{children:t.map((t,n)=>{let r=t.project,i=t.best_match?.row||{},a=t.manual_match?.match_status||``,o=r?.id||i.row_key||[i.section,i.Client].filter(Boolean).join(`:`)||`wiki-match-${n}`;return(0,R.jsxs)(Gt,{children:[(0,R.jsx)(K,{children:r?.display_name||`Unknown`}),(0,R.jsxs)(K,{children:[(0,R.jsx)(`strong`,{children:i.Client||`No match`}),(0,R.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:[i.DRI,i.Members].filter(Boolean).join(` | `)})]}),(0,R.jsx)(K,{children:(0,R.jsx)(z,{variant:t.best_match?.confidence===`high`?`succeeded`:t.best_match?.confidence===`medium`?`running`:`neutral`,children:t.best_match?`${t.best_match.confidence} ${t.best_match.score}`:`none`})}),(0,R.jsx)(K,{children:i.section||``}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[a?(0,R.jsx)(z,{variant:a===`confirmed`?`succeeded`:`neutral`,children:a===`no_row`?`No wiki row`:`Confirmed`}):null,e.canWrite&&r?.id?(0,R.jsxs)(R.Fragment,{children:[i.row_key?(0,R.jsx)(B,{type:`button`,variant:`outline`,size:`sm`,disabled:e.loading[`project:${r.id}:wiki`],onClick:()=>void e.onUpdateWikiMatch(r.id,`confirmed`,i.row_key),children:`Confirm`}):null,(0,R.jsx)(B,{type:`button`,variant:`outline`,size:`sm`,disabled:e.loading[`project:${r.id}:wiki`],onClick:()=>void e.onUpdateWikiMatch(r.id,`no_row`),children:`No row`})]}):null]})})]},o)})})]})})]}):null]})}function Kn(e){let[t,n]=(0,l.useState)(``),[r,i]=(0,l.useState)(`new`),[a,o]=(0,l.useState)(``),[s,c]=(0,l.useState)(``),[u,d]=(0,l.useState)(``),[f,p]=(0,l.useState)([]),[m,h]=(0,l.useState)(``),[g,_]=(0,l.useState)(``),[v,y]=(0,l.useState)([]),[b,x]=(0,l.useState)(`USD`),[ee,te]=(0,l.useState)(``),[S,C]=(0,l.useState)(``),[ne,re]=(0,l.useState)(``),[ie,ae]=(0,l.useState)(``),[oe,se]=(0,l.useState)(``),[ce,T]=(0,l.useState)(``),[le,E]=(0,l.useState)(`United States`),[ue,de]=(0,l.useState)(``),[fe,pe]=(0,l.useState)(`new`),[D,O]=(0,l.useState)(``),[k,me]=(0,l.useState)(``),[A,he]=(0,l.useState)([]),[ge,_e]=(0,l.useState)(``),[ve,ye]=(0,l.useState)(``),[be,xe]=(0,l.useState)(``),[Se,Ce]=(0,l.useState)(``),[we,Te]=(0,l.useState)(``),[Ee,De]=(0,l.useState)(!1),[Oe,ke]=(0,l.useState)([{name:`Projects - 5`,cost_center_name:`Projects`}]),[Ae,je]=(0,l.useState)(`Projects - 5`),[Me,Ne]=(0,l.useState)(``),[Pe,Fe]=(0,l.useState)(!1),Ie=(0,l.useRef)(e.onSearchCustomers),Le=(0,l.useRef)(e.onSearchContacts),Re=(0,l.useRef)(e.onSearchAccountManagers),ze=(0,l.useRef)(e.onLoadCostCenters),Be=(0,l.useRef)(0),Ve=(0,l.useRef)(0),He=(0,l.useRef)(0),Ue=(0,l.useRef)(0),We=t.trim()?`Engineering for ${t.trim()}`.slice(0,140):``,Ge=[ne,ie,oe,ce,ue].some(e=>e.trim()),Ke=[ge,ve,be,Se,we].some(e=>e.trim()),qe=t.trim()&&(r===`new`?a.trim():u.trim())&&!e.loading;(0,l.useEffect)(()=>{Ie.current=e.onSearchCustomers},[e.onSearchCustomers]),(0,l.useEffect)(()=>{Le.current=e.onSearchContacts},[e.onSearchContacts]),(0,l.useEffect)(()=>{Re.current=e.onSearchAccountManagers},[e.onSearchAccountManagers]),(0,l.useEffect)(()=>{ze.current=e.onLoadCostCenters},[e.onLoadCostCenters]),(0,l.useEffect)(()=>{let e=!0,t=Be.current+1;return Be.current=t,ze.current().then(n=>{!e||Be.current!==t||(ke(n),je(e=>n.some(t=>t.name===e)?e:`Projects - 5`))}),()=>{e=!1}},[]),(0,l.useEffect)(()=>{if(r!==`existing`){Ve.current+=1,p([]);return}let e=!0,t=Ve.current+1;Ve.current=t;let n=window.setTimeout(()=>{Ie.current(s).then(n=>{!e||Ve.current!==t||p(n)})},250);return()=>{e=!1,window.clearTimeout(n)}},[r,s]),(0,l.useEffect)(()=>{if(r!==`new`){He.current+=1,y([]);return}let e=!0,t=He.current+1;He.current=t;let n=window.setTimeout(()=>{Re.current(m).then(n=>{!e||He.current!==t||y(n)})},250);return()=>{e=!1,window.clearTimeout(n)}},[r,m]),(0,l.useEffect)(()=>{if(r!==`new`||fe!==`existing`){Ue.current+=1,he([]);return}let e=!0,t=Ue.current+1;Ue.current=t;let n=window.setTimeout(()=>{Le.current(D).then(n=>{!e||Ue.current!==t||he(n)})},250);return()=>{e=!1,window.clearTimeout(n)}},[r,fe,D]);async function Je(){qe&&await e.onCreateProject({project_name:t.trim(),customer_mode:r,customer_name:r===`new`?a.trim():void 0,customer:r===`existing`?u.trim():void 0,account_manager:r===`new`&&g.trim()||void 0,default_billing_currency:r===`new`?b.trim()||`USD`:void 0,default_cost_center:Ae.trim()||`Projects - 5`,activity_type:Pe&&Me.trim()||void 0,customer_details:r===`new`&&ee.trim()||void 0,customer_website:r===`new`&&S.trim()||void 0,address_line1:r===`new`&&ne.trim()||void 0,address_line2:r===`new`&&ie.trim()||void 0,address_city:r===`new`&&oe.trim()||void 0,address_state:r===`new`&&ce.trim()||void 0,address_country:r===`new`&&ne.trim()?le.trim()||`United States`:void 0,address_postal_code:r===`new`&&ue.trim()||void 0,contact:r===`new`&&fe===`existing`&&k.trim()||void 0,contact_first_name:r===`new`&&fe===`new`&&ge.trim()||void 0,contact_last_name:r===`new`&&fe===`new`&&ve.trim()||void 0,contact_email:r===`new`&&fe===`new`&&be.trim()||void 0,contact_phone:r===`new`&&fe===`new`&&Se.trim()||void 0,contact_mobile:r===`new`&&fe===`new`&&we.trim()||void 0})&&e.onClose()}return(0,R.jsxs)(`div`,{className:`fixed inset-0 z-50 grid place-items-center p-4`,"aria-labelledby":`createProjectTitle`,"aria-modal":`true`,role:`dialog`,children:[(0,R.jsx)(`button`,{type:`button`,className:`absolute inset-0 cursor-default bg-black/45`,"aria-label":`Close project creation`,onClick:e.onClose}),(0,R.jsxs)(`div`,{className:`relative grid max-h-[90vh] w-full max-w-2xl gap-4 overflow-y-auto rounded-md border bg-background p-5 shadow-2xl`,children:[(0,R.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`strong`,{id:`createProjectTitle`,className:`block text-base`,children:`New ERP project`}),(0,R.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:`Creates a project and links a new or existing customer.`})]}),(0,R.jsx)(B,{type:`button`,variant:`ghost`,size:`icon`,"aria-label":`Close project creation`,onClick:e.onClose,children:(0,R.jsx)(w,{})})]}),(0,R.jsxs)(`div`,{className:`grid gap-3`,children:[(0,R.jsxs)(W,{children:[`Project name *`,(0,R.jsx)(U,{value:t,autoComplete:`off`,maxLength:140,placeholder:`Acme Portal`,onChange:e=>n(e.target.value)})]}),(0,R.jsxs)(`div`,{className:`grid gap-2`,children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Customer`}),(0,R.jsx)(`div`,{className:`grid grid-cols-2 gap-2`,children:[`new`,`existing`].map(e=>(0,R.jsx)(B,{type:`button`,variant:r===e?`default`:`outline`,onClick:()=>i(e),children:e===`new`?`New customer`:`Existing customer`},e))})]}),r===`new`?(0,R.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-2`,children:[(0,R.jsxs)(W,{className:`md:col-span-2`,children:[`Customer name *`,(0,R.jsx)(U,{value:a,autoComplete:`off`,maxLength:140,placeholder:`Acme`,onChange:e=>o(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Account manager`,(0,R.jsx)(U,{value:m,autoComplete:`off`,placeholder:`Search @508.dev user`,onChange:e=>{h(e.target.value),_(``)}})]}),m.trim().length>=2?(0,R.jsx)(`div`,{className:`grid max-h-40 gap-2 overflow-y-auto rounded-md border p-2 md:col-span-2`,children:v.length?v.map(e=>{let t=e.email||e.name||``;return(0,R.jsxs)(`label`,{className:`flex cursor-pointer items-start gap-2 rounded-sm px-2 py-1.5 hover:bg-secondary`,children:[(0,R.jsx)(`input`,{type:`radio`,name:`erpAccountManager`,value:t,checked:g===t,onChange:()=>{_(t),h(t)}}),(0,R.jsxs)(`span`,{className:`grid gap-0.5 text-sm`,children:[(0,R.jsx)(`strong`,{children:e.full_name||t}),(0,R.jsx)(`span`,{className:`text-muted-foreground`,children:t})]})]},t)}):(0,R.jsx)(`span`,{className:`px-2 py-3 text-sm text-muted-foreground`,children:`No enabled @508.dev users found.`})}):null]}):(0,R.jsxs)(`div`,{className:`grid gap-3`,children:[(0,R.jsxs)(W,{children:[`Find customer *`,(0,R.jsx)(U,{value:s,autoComplete:`off`,placeholder:`Search customer`,onChange:e=>c(e.target.value)})]}),(0,R.jsx)(`div`,{className:`grid max-h-48 gap-2 overflow-y-auto rounded-md border p-2`,children:f.length?f.map(e=>{let t=e.name||e.customer_name||``;return(0,R.jsxs)(`label`,{className:`flex cursor-pointer items-start gap-2 rounded-sm px-2 py-1.5 hover:bg-secondary`,children:[(0,R.jsx)(`input`,{type:`radio`,name:`erpCustomer`,value:t,checked:u===t,onChange:()=>d(t)}),(0,R.jsxs)(`span`,{className:`grid gap-0.5 text-sm`,children:[(0,R.jsx)(`strong`,{children:e.customer_name||t}),(0,R.jsx)(`span`,{className:`text-muted-foreground`,children:[t,e.default_currency].filter(Boolean).join(` | `)})]})]},t)}):(0,R.jsx)(`span`,{className:`px-2 py-3 text-sm text-muted-foreground`,children:`Search at least two characters.`})})]}),r===`new`?(0,R.jsxs)(R.Fragment,{children:[(0,R.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-2`,children:[(0,R.jsxs)(W,{className:`md:col-span-2`,children:[`Customer details`,(0,R.jsx)(`textarea`,{value:ee,className:`min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground shadow-xs transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]`,maxLength:2e3,placeholder:`More information`,onChange:e=>te(e.target.value)})]}),(0,R.jsxs)(W,{className:`md:col-span-2`,children:[`Website`,(0,R.jsx)(U,{value:S,autoComplete:`url`,placeholder:`https://example.com`,onChange:e=>C(e.target.value)})]})]}),(0,R.jsxs)(`div`,{className:`grid gap-3`,children:[(0,R.jsxs)(`div`,{className:`flex items-center justify-between gap-3`,children:[(0,R.jsx)(`strong`,{className:`text-sm text-foreground`,children:`Contact`}),(0,R.jsx)(`div`,{className:`grid grid-cols-2 gap-2`,children:[`new`,`existing`].map(e=>(0,R.jsx)(B,{type:`button`,size:`sm`,variant:fe===e?`default`:`outline`,onClick:()=>pe(e),children:e===`new`?`New`:`Existing`},e))})]}),fe===`new`?(0,R.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-2`,children:[(0,R.jsxs)(W,{children:[`First name `,Ke?`*`:``,(0,R.jsx)(U,{value:ge,autoComplete:`given-name`,onChange:e=>_e(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Last name`,(0,R.jsx)(U,{value:ve,autoComplete:`family-name`,onChange:e=>ye(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Email`,(0,R.jsx)(U,{value:be,type:`email`,autoComplete:`email`,onChange:e=>xe(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Phone`,(0,R.jsx)(U,{value:Se,type:`tel`,autoComplete:`tel`,onChange:e=>Ce(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Mobile`,(0,R.jsx)(U,{value:we,type:`tel`,autoComplete:`tel`,onChange:e=>Te(e.target.value)})]})]}):(0,R.jsxs)(`div`,{className:`grid gap-3`,children:[(0,R.jsxs)(W,{children:[`Find contact`,(0,R.jsx)(U,{value:D,autoComplete:`off`,placeholder:`Search name or email`,onChange:e=>O(e.target.value)})]}),(0,R.jsx)(`div`,{className:`grid max-h-48 gap-2 overflow-y-auto rounded-md border p-2`,children:A.length?A.map(e=>{let t=e.name||``,n=e.full_name||t,r=[{key:`company`,value:e.company_name},{key:`email`,value:e.email_id},{key:`phone`,value:e.phone},{key:`mobile`,value:e.mobile_no}].filter(e=>!!e.value);return(0,R.jsxs)(`label`,{className:`flex cursor-pointer items-start gap-2 rounded-sm px-2 py-1.5 hover:bg-secondary`,children:[(0,R.jsx)(`input`,{type:`radio`,name:`erpContact`,value:t,checked:k===t,onChange:()=>me(t)}),(0,R.jsxs)(`span`,{className:`grid gap-0.5 text-sm`,children:[(0,R.jsx)(`strong`,{children:(0,R.jsx)(An,{value:n,query:D})}),r.length?(0,R.jsx)(`span`,{className:`text-muted-foreground`,children:r.map((e,t)=>(0,R.jsxs)(`span`,{children:[t>0?` | `:``,(0,R.jsx)(An,{value:e.value,query:D})]},e.key))}):null]})]},t)}):(0,R.jsx)(`span`,{className:`px-2 py-3 text-sm text-muted-foreground`,children:`Search at least two characters.`})})]})]}),(0,R.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-2`,children:[(0,R.jsx)(`strong`,{className:`text-sm text-foreground md:col-span-2`,children:`Address`}),(0,R.jsxs)(W,{className:`md:col-span-2`,children:[`Address line 1 `,Ge?`*`:``,(0,R.jsx)(U,{value:ne,autoComplete:`address-line1`,onChange:e=>re(e.target.value)})]}),(0,R.jsxs)(W,{className:`md:col-span-2`,children:[`Address line 2`,(0,R.jsx)(U,{value:ie,autoComplete:`address-line2`,onChange:e=>ae(e.target.value)})]}),(0,R.jsxs)(W,{children:[`City`,(0,R.jsx)(U,{value:oe,autoComplete:`address-level2`,onChange:e=>se(e.target.value)})]}),(0,R.jsxs)(W,{children:[`State`,(0,R.jsx)(U,{value:ce,autoComplete:`address-level1`,onChange:e=>T(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Postal code`,(0,R.jsx)(U,{value:ue,autoComplete:`postal-code`,onChange:e=>de(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Country`,(0,R.jsx)(U,{value:le,autoComplete:`country-name`,onChange:e=>E(e.target.value)})]})]})]}):null,(0,R.jsxs)(`div`,{className:`grid gap-3 rounded-md border p-3`,children:[(0,R.jsx)(B,{type:`button`,variant:`outline`,onClick:()=>De(e=>!e),children:Ee?`Hide advanced`:`Show advanced`}),Ee?(0,R.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-2`,children:[r===`new`?(0,R.jsxs)(W,{children:[`Billing currency`,(0,R.jsx)(U,{value:b,autoComplete:`off`,maxLength:3,onChange:e=>x(e.target.value.toUpperCase())})]}):null,(0,R.jsxs)(W,{children:[`Cost center`,(0,R.jsx)(Vt,{value:Ae,onChange:e=>je(e.target.value),children:Oe.map(e=>{let t=e.name||``;return(0,R.jsx)(`option`,{value:t,children:[t,e.company].filter(Boolean).join(` | `)},t)})})]}),(0,R.jsxs)(W,{children:[`Activity type`,(0,R.jsx)(U,{value:Pe?Me:We,autoComplete:`off`,maxLength:140,placeholder:We||`Engineering for project`,onChange:e=>{Fe(!0),Ne(e.target.value)}})]})]}):null]})]}),(0,R.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-2`,children:[(0,R.jsx)(B,{type:`button`,variant:`outline`,onClick:e.onClose,children:`Cancel`}),(0,R.jsx)(B,{type:`button`,disabled:!qe,onClick:()=>void Je(),children:`Create project`})]})]})]})}function qn(e){let t=e.project,n=t.roster_members||[],[r,i]=(0,l.useState)(``),[a,o]=(0,l.useState)([]),[s,c]=(0,l.useState)(``),[u,d]=(0,l.useState)(``),[f,p]=(0,l.useState)(``),[m,g]=(0,l.useState)(``),_=[t.actual_start_date||t.expected_start_date,t.actual_end_date||t.expected_end_date].filter(Boolean).map(e=>Hn(e)).join(` to `)||`Not set`,v=typeof t.percent_complete==`number`?`${Math.round(t.percent_complete)}%`:`Not set`,b=a.find(e=>e.candidate_id===s),x=r.trim().includes(`@`)?r.trim().length>=5:r.trim().length>=3,ee=!!(u.trim()||f.trim()||m.trim()),te=Wn(f),S=Wn(m),C=!!((f.trim()||m.trim())&&!u.trim()),ne=!!(u.trim()&&(!f.trim()||!m.trim())),re=!!(f.trim()&&te===void 0)||!!(m.trim()&&S===void 0),ie=C||ne||re||te!==void 0&&te<0||S!==void 0&&S<0,ae=ee&&!ie?{activity_type:u.trim(),billing_rate:te,costing_rate:S}:void 0;(0,l.useEffect)(()=>{if(!e.canWrite)return;let t=r.trim();if(s&&b&&t===(b.email||b.label||``))return;if(s&&c(``),!(t.includes(`@`)?t.length>=5:t.length>=3)){o([]);return}let n=new AbortController,i=window.setTimeout(()=>{q(`/dashboard/api/project-member-candidates?query=${encodeURIComponent(t)}`,{signal:n.signal}).then(e=>o(e)).catch(e=>{e instanceof DOMException&&e.name===`AbortError`||o([])})},500);return()=>{n.abort(),window.clearTimeout(i)}},[r,e.canWrite,b,s]);function se(e){c(e.candidate_id),i(e.email||e.label||e.full_name||r)}return(0,R.jsxs)(R.Fragment,{children:[(0,R.jsxs)(V,{children:[(0,R.jsx)(H,{children:(0,R.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-[auto_minmax(0,1fr)_auto] md:items-start`,children:[(0,R.jsxs)(B,{type:`button`,variant:`outline`,onClick:e.onBack,children:[(0,R.jsx)(h,{}),`Projects`]}),(0,R.jsxs)(`div`,{className:`min-w-0`,children:[(0,R.jsx)(zt,{children:t.display_name}),(0,R.jsxs)(`div`,{className:`mt-2 flex flex-wrap items-center gap-2 text-sm text-muted-foreground`,children:[(0,R.jsx)(z,{variant:Vn(t.source_status),children:t.source_status||`Unknown`}),t.erpnext_project_id?(0,R.jsx)(`span`,{className:`font-mono`,children:t.erpnext_project_id}):null,t.last_synced_at?(0,R.jsxs)(`span`,{children:[`Synced `,qt(t.last_synced_at)]}):null]})]}),(0,R.jsxs)(`div`,{className:`flex flex-wrap justify-start gap-2 md:justify-end`,children:[e.canWrite?(0,R.jsxs)(Vt,{className:`w-[160px]`,"aria-label":`Status for ${t.display_name}`,value:t.source_status||``,disabled:e.loading[`project:${t.id}:status`],onChange:n=>e.onUpdateStatus(t.id,n.target.value),children:[(0,R.jsx)(`option`,{value:`Open`,children:`Open`}),(0,R.jsx)(`option`,{value:`Completed`,children:`Completed`}),(0,R.jsx)(`option`,{value:`Cancelled`,children:`Cancelled`})]}):null,t.erpnext_project_url?(0,R.jsxs)(`a`,{className:`inline-flex min-h-9 items-center justify-center gap-2 rounded-md border bg-secondary px-3 text-sm font-semibold`,href:t.erpnext_project_url,target:`_blank`,rel:`noreferrer`,children:[(0,R.jsx)(y,{className:`size-4`}),`ERP project`]}):null,t.customer_erpnext_url?(0,R.jsxs)(`a`,{className:`inline-flex min-h-9 items-center justify-center gap-2 rounded-md border bg-secondary px-3 text-sm font-semibold`,href:t.customer_erpnext_url,target:`_blank`,rel:`noreferrer`,children:[(0,R.jsx)(y,{className:`size-4`}),`ERP customer`]}):null]})]})}),(0,R.jsxs)(Bt,{className:`grid gap-4 md:grid-cols-2 lg:grid-cols-4`,children:[(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Customer`}),(0,R.jsx)(`strong`,{className:`block`,children:t.customer||`None`})]}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Timeline`}),(0,R.jsx)(`strong`,{className:`block`,children:_})]}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Progress`}),(0,R.jsx)(`strong`,{className:`block`,children:v})]}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Linked Gigs`}),(0,R.jsx)(`strong`,{className:`block`,children:t.linked_engagement_count||0})]}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`ERP Type`}),(0,R.jsx)(`div`,{className:`mt-1`,children:t.project_type?(0,R.jsx)(z,{variant:`neutral`,children:t.project_type}):(0,R.jsx)(`strong`,{className:`block`,children:`Not set`})})]}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`ERP Modified`}),(0,R.jsx)(`strong`,{className:`block`,children:qt(t.source_modified_at)})]}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Cache ID`}),(0,R.jsx)(`strong`,{className:`block break-all font-mono text-xs`,children:t.id})]})]})]}),(0,R.jsxs)(V,{children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`Project roster`}),(0,R.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:n.length?`${n.length} synced ERP user${n.length===1?``:`s`}`:`No ERP roster`})]}),e.canWrite?(0,R.jsxs)(Bt,{className:`grid gap-3 border-b md:grid-cols-[minmax(260px,1fr)_minmax(180px,.7fr)_minmax(130px,.45fr)_minmax(130px,.45fr)_auto_auto] md:items-end`,children:[(0,R.jsxs)(`div`,{className:`relative`,children:[(0,R.jsxs)(W,{children:[`Person search`,(0,R.jsx)(U,{value:r,autoComplete:`off`,placeholder:`Search @508.dev person`,onChange:e=>i(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),a.length===1&&se(a[0]))}})]}),x&&!s?(0,R.jsx)(`div`,{className:`absolute z-20 mt-1 max-h-64 w-full overflow-auto rounded-md border bg-background shadow-lg`,children:a.length?a.map(e=>(0,R.jsxs)(`button`,{type:`button`,className:`grid w-full gap-0.5 px-3 py-2 text-left hover:bg-secondary focus:bg-secondary focus:outline-none`,onClick:()=>se(e),children:[(0,R.jsx)(`span`,{className:`truncate text-sm font-bold`,children:e.label||e.full_name||e.email||`Person`}),(0,R.jsx)(`span`,{className:`truncate text-xs text-muted-foreground`,children:[e.email,e.sources?.join(`, `)].filter(Boolean).join(` | `)})]},e.candidate_id)):(0,R.jsx)(`div`,{className:`px-3 py-2 text-sm text-muted-foreground`,children:`No verified @508.dev results`})}):null]}),(0,R.jsxs)(W,{children:[`Activity Type`,(0,R.jsx)(U,{value:u,autoComplete:`off`,placeholder:`Optional rate step`,onChange:e=>d(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Billing rate`,(0,R.jsx)(U,{value:f,inputMode:`decimal`,autoComplete:`off`,placeholder:`USD/hr`,onChange:e=>p(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Costing rate`,(0,R.jsx)(U,{value:m,inputMode:`decimal`,autoComplete:`off`,placeholder:`USD/hr`,onChange:e=>g(e.target.value)})]}),(0,R.jsxs)(B,{type:`button`,variant:`outline`,disabled:e.loading[`project:${t.id}:user`]||!s||!b?.email||ie,onClick:()=>void e.onAddUser(t.id,b?.email||r,s,ae).then(e=>{e&&(i(``),o([]),c(``),d(``),p(``),g(``))}),children:[(0,R.jsx)(ce,{}),`Add ERP user`]}),(0,R.jsxs)(B,{type:`button`,variant:`outline`,disabled:e.loading[`project:${t.id}:historical`]||!r.trim(),onClick:()=>void e.onAddHistoricalMember(t.id,r).then(e=>{e&&i(``)}),children:[(0,R.jsx)(ce,{}),`Add historical`]})]}):null,(0,R.jsx)(`div`,{className:`overflow-x-auto`,children:(0,R.jsxs)(Ht,{className:`min-w-[860px]`,"aria-label":`Project roster`,children:[(0,R.jsx)(Ut,{children:(0,R.jsxs)(Gt,{children:[(0,R.jsx)(G,{children:`Name`}),(0,R.jsx)(G,{children:`Email`}),(0,R.jsx)(G,{children:`ERP user`}),(0,R.jsx)(G,{children:`Links`}),(0,R.jsx)(G,{children:`Source`}),(0,R.jsx)(G,{children:`Last seen`}),e.canWrite?(0,R.jsx)(G,{children:`Actions`}):null]})}),(0,R.jsx)(Wt,{children:n.length?n.map(n=>{let r=Un(n),i=n.source_user_id||n.email||``,a=n.roster_kind===`historical`||n.source===`manual`;return(0,R.jsxs)(Gt,{children:[(0,R.jsx)(K,{children:(0,R.jsx)(`strong`,{children:n.full_name||n.email||n.source_user_id})}),(0,R.jsx)(K,{children:n.email||`None`}),(0,R.jsx)(K,{className:`font-mono text-xs`,children:n.erpnext_user_url?(0,R.jsxs)(`a`,{className:`inline-flex items-center gap-1 font-semibold text-primary underline-offset-4 hover:underline`,href:n.erpnext_user_url,target:`_blank`,rel:`noreferrer`,children:[n.source_user_id||`ERP user`,(0,R.jsx)(y,{className:`size-3.5`})]}):n.source_user_id||`Unknown`}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`flex flex-wrap gap-2`,children:[n.supplier_erpnext_url?(0,R.jsxs)(`a`,{className:`inline-flex items-center gap-1 font-semibold text-primary underline-offset-4 hover:underline`,href:n.supplier_erpnext_url,target:`_blank`,rel:`noreferrer`,children:[`Supplier`,(0,R.jsx)(y,{className:`size-3.5`})]}):null,n.crm_contact_id&&e.crmContactUrl(n.crm_contact_id)?(0,R.jsxs)(`a`,{className:`inline-flex items-center gap-1 font-semibold text-primary underline-offset-4 hover:underline`,href:e.crmContactUrl(n.crm_contact_id),target:`_blank`,rel:`noreferrer`,children:[`CRM`,(0,R.jsx)(y,{className:`size-3.5`})]}):null,!n.supplier_erpnext_url&&!(n.crm_contact_id&&e.crmContactUrl(n.crm_contact_id))?(0,R.jsx)(`span`,{className:`text-muted-foreground`,children:`None`}):null]})}),(0,R.jsx)(K,{children:n.roster_kind||n.source||`ERP`}),(0,R.jsx)(K,{children:qt(n.last_seen_at)}),e.canWrite?(0,R.jsx)(K,{children:(0,R.jsxs)(B,{type:`button`,variant:`outline`,size:`sm`,disabled:!i||e.loading[`project:${t.id}:${a?`historical`:`user`}`],onClick:()=>{window.confirm(`Remove ${r} from this project roster?`)&&(a?e.onRemoveHistoricalMember(t.id,i):e.onRemoveUser(t.id,i))},children:[(0,R.jsx)(oe,{}),`Remove`]})}):null]},`${n.source||``}:${n.source_user_id||n.email}`)}):(0,R.jsx)(Gt,{children:(0,R.jsx)(K,{colSpan:e.canWrite?7:6,className:`text-sm text-muted-foreground`,children:`No roster rows have been synced for this project.`})})})]})})]})]})}function Jn(e){let t=e.gigs.reduce((t,n)=>(t.total+=1,t.applications+=Number(n.application_count||0),t.interested+=Number(n.interested_count||0),Bn(n,e.staleDays)!==null&&(t.stale+=1),t),{total:0,applications:0,interested:0,stale:0}),n=(0,R.jsxs)(V,{className:`grid gap-3 p-4 md:grid-cols-[minmax(140px,.75fr)_minmax(220px,1.25fr)_auto_auto_auto] md:items-end`,children:[(0,R.jsxs)(W,{children:[`Status`,(0,R.jsxs)(Vt,{id:`gigStatus`,value:e.status,onChange:t=>e.setStatus(t.target.value),children:[(0,R.jsx)(`option`,{value:``,children:`Any status`}),In.map(e=>(0,R.jsx)(`option`,{value:e,children:Rn(e)},e))]})]}),(0,R.jsxs)(W,{children:[`Search gigs`,(0,R.jsx)(U,{id:`gigQuery`,value:e.query,autoComplete:`off`,placeholder:`Title, gig text, #tag, @poster`,onChange:t=>e.setQuery(t.target.value),onKeyDown:t=>t.key===`Enter`&&e.onRefresh()})]}),e.canIncludeHistorical?(0,R.jsxs)(`label`,{className:`flex min-h-9 items-center gap-2 text-xs font-bold text-muted-foreground`,children:[(0,R.jsx)(`input`,{type:`checkbox`,checked:e.includeHistorical,onChange:t=>e.setIncludeHistorical(t.target.checked)}),`Include historical`]}):null,(0,R.jsxs)(B,{id:`searchGigs`,type:`button`,onClick:e.onRefresh,disabled:e.loading.gigs,children:[(0,R.jsx)(ne,{}),`Search`]}),(0,R.jsxs)(B,{id:`refreshGigs`,type:`button`,variant:`outline`,onClick:e.onRefresh,disabled:e.loading.gigs,children:[(0,R.jsx)(C,{}),`Refresh`]}),e.gigs.length>=e.limit?(0,R.jsx)(B,{type:`button`,variant:`outline`,onClick:()=>e.setLimit(Math.min(e.limit+100,500)),disabled:e.loading.gigs||e.limit>=500,children:`Load more`}):null]}),r=e.selectedGigId?e.loading[`gig:${e.selectedGigId}:detail`]:!1;return e.selectedGigId&&!e.selectedGig&&(e.loading.gigs||r)?(0,R.jsxs)(R.Fragment,{children:[n,(0,R.jsxs)(V,{children:[(0,R.jsx)(H,{children:(0,R.jsx)(zt,{children:`Gig detail`})}),(0,R.jsx)(Bt,{className:`text-sm text-muted-foreground`,children:`Loading gig.`})]})]}):e.selectedGigId&&!e.selectedGig?(0,R.jsxs)(R.Fragment,{children:[n,(0,R.jsxs)(V,{children:[(0,R.jsx)(H,{children:(0,R.jsx)(zt,{children:`Gig detail`})}),(0,R.jsxs)(Bt,{className:`grid gap-3`,children:[(0,R.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`This gig is not in the current result set. Clear filters or refresh the gig list.`}),(0,R.jsxs)(B,{type:`button`,variant:`outline`,onClick:e.onCloseGig,children:[(0,R.jsx)(h,{}),`Back to gigs`]})]})]})]}):e.selectedGig?(0,R.jsxs)(R.Fragment,{children:[n,(0,R.jsx)(Zn,{gig:e.selectedGig,loading:e.loading,canWrite:e.canWrite,crmContactUrl:e.crmContactUrl,crmAttachmentUrl:e.crmAttachmentUrl,staleDays:e.staleDays,onBack:e.onCloseGig,onUpdateStatus:e.onUpdateStatus,onAddApplication:e.onAddApplication,onUpdateApplicationStatus:e.onUpdateApplicationStatus})]}):(0,R.jsxs)(R.Fragment,{children:[n,(0,R.jsxs)(`section`,{className:`grid gap-3 md:grid-cols-4`,"aria-label":`Gig summary`,children:[(0,R.jsx)(On,{id:`gigMetricTotal`,label:`Gigs`,value:t.total}),(0,R.jsx)(On,{id:`gigMetricCandidates`,label:`Candidates`,value:t.applications}),(0,R.jsx)(On,{id:`gigMetricInterested`,label:`Interested`,value:t.interested}),(0,R.jsx)(On,{id:`gigMetricStale`,label:`Stale recruiting`,value:t.stale})]}),(0,R.jsxs)(V,{children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`Discord gigs`}),(0,R.jsxs)(`div`,{className:`flex flex-wrap items-center justify-end gap-2`,children:[(0,R.jsxs)(B,{type:`button`,variant:`ghost`,size:`sm`,onClick:()=>e.onSort(`activity`),"aria-label":`Sort gigs by activity`,children:[`Activity`,` `,e.sort.key===`activity`?e.sort.direction===`asc`?`↑`:`↓`:``]}),(0,R.jsxs)(B,{type:`button`,variant:`ghost`,size:`sm`,onClick:()=>e.onSort(`title`),"aria-label":`Sort gigs by title`,children:[`Title `,e.sort.key===`title`?e.sort.direction===`asc`?`↑`:`↓`:``]}),(0,R.jsx)(`span`,{id:`gigsStatus`,className:`text-sm text-muted-foreground`,children:e.loading.gigs?`Loading`:`${e.gigs.length} shown`})]})]}),(0,R.jsx)(kn,{hidden:e.gigs.length!==0,children:`No gigs match this view.`}),(0,R.jsx)(`div`,{id:`gigsBody`,className:L(`grid gap-3 p-4`,e.gigs.length===0&&`hidden`),children:e.gigs.map(t=>(0,R.jsx)(Yn,{gig:t,loading:e.loading,canWrite:e.canWrite,staleDays:e.staleDays,onOpenGig:e.onOpenGig,onUpdateStatus:e.onUpdateStatus},t.id))})]})]})}function Yn({gig:e,loading:t,canWrite:n,onOpenGig:r,onUpdateStatus:i,staleDays:a}){let o=Array.isArray(e.applications)?e.applications:[],s=e.status===`recruiting`,c=e.discord_guild_id&&e.discord_thread_id?`https://discord.com/channels/${encodeURIComponent(e.discord_guild_id)}/${encodeURIComponent(e.discord_thread_id)}`:``,l=Bn(e,a);return(0,R.jsxs)(`article`,{className:L(`grid gap-4 rounded-md border bg-background p-4 lg:grid-cols-[minmax(0,1fr)_220px_180px] lg:items-start`,!s&&`border-l-4 border-l-muted-foreground/60 bg-secondary/45`),children:[(0,R.jsxs)(`div`,{className:`min-w-0`,children:[(0,R.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,R.jsx)(`a`,{className:`text-base font-extrabold text-primary`,href:`/dashboard/gigs/${encodeURIComponent(e.id)}`,onClick:t=>{t.preventDefault(),r(e.id)},children:e.title||`Untitled gig`}),(0,R.jsx)(z,{variant:e.status===`filled`?`succeeded`:e.status===`lost`?`failed`:s?`queued`:`neutral`,children:e.status_label||Rn(e.status)}),s?null:(0,R.jsx)(z,{variant:`neutral`,children:`Not recruiting`}),l===null?null:(0,R.jsxs)(z,{variant:`running`,children:[l,`d stale`]})]}),(0,R.jsxs)(`div`,{className:`mt-2 flex flex-wrap gap-1.5`,children:[e.posting_type?(0,R.jsx)(z,{variant:`neutral`,children:Rn(e.posting_type)}):null,e.discord_channel_name?(0,R.jsxs)(z,{variant:`neutral`,children:[`#`,e.discord_channel_name]}):null,(e.required_skills||[]).slice(0,5).map(e=>(0,R.jsx)(z,{variant:`queued`,children:e},e)),(e.preferred_skills||[]).slice(0,3).map(e=>(0,R.jsx)(z,{variant:`neutral`,children:e},e))]}),(0,R.jsxs)(`div`,{className:`mt-3 flex flex-wrap gap-x-4 gap-y-1 text-sm text-muted-foreground`,children:[(0,R.jsxs)(`span`,{children:[`Activity `,qt(zn(e))||`unknown`]}),(0,R.jsxs)(`span`,{children:[`Posted `,qt(e.posted_at)||`unknown`]}),c?(0,R.jsx)(`a`,{className:`font-extrabold text-primary`,href:c,target:`_blank`,rel:`noreferrer`,children:`Open Discord thread`}):null]})]}),(0,R.jsxs)(`div`,{className:`grid grid-cols-2 gap-2 text-sm lg:grid-cols-1`,children:[(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`block text-xs font-bold text-muted-foreground`,children:`People`}),(0,R.jsx)(`strong`,{children:e.application_count||o.length}),(0,R.jsxs)(`span`,{className:`ml-2 text-muted-foreground`,children:[Number(e.interested_count||0),` interested`]})]}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`block text-xs font-bold text-muted-foreground`,children:`Top candidates`}),(0,R.jsx)(`span`,{className:`text-muted-foreground`,children:o.slice(0,3).map(e=>Xn(e)).join(`, `)||`None yet`})]})]}),(0,R.jsxs)(`div`,{className:`grid gap-2`,children:[n?(0,R.jsx)(Vt,{"aria-label":`Status for ${e.title||`gig`}`,value:e.status,disabled:t[`gig:${e.id}:status`],onChange:t=>i(e.id,t.target.value),children:In.map(e=>(0,R.jsx)(`option`,{value:e,children:Rn(e)},e))}):null,(0,R.jsx)(B,{type:`button`,onClick:()=>r(e.id),children:`Manage people`})]})]})}function Xn(e){return e.name||e.email_508||e.discord_username||(typeof e.evaluation?.discord_username==`string`?e.evaluation.discord_username:``)||`Candidate`}function Zn({gig:e,loading:t,canWrite:n,crmContactUrl:r,crmAttachmentUrl:i,staleDays:a,onBack:o,onUpdateStatus:s,onAddApplication:c,onUpdateApplicationStatus:u}){let[d,f]=(0,l.useState)(``),p=Array.isArray(e.applications)?e.applications:[],m=e.status===`recruiting`,g=e.discord_guild_id&&e.discord_thread_id?`https://discord.com/channels/${encodeURIComponent(e.discord_guild_id)}/${encodeURIComponent(e.discord_thread_id)}`:``,_=Bn(e,a);return(0,R.jsxs)(`div`,{className:`grid gap-5`,children:[(0,R.jsxs)(V,{className:L(!m&&`border-l-4 border-l-muted-foreground/60 bg-secondary/35`),children:[(0,R.jsxs)(H,{className:`items-start`,children:[(0,R.jsxs)(`div`,{className:`grid gap-2`,children:[(0,R.jsxs)(B,{type:`button`,variant:`ghost`,size:`sm`,className:`w-fit`,onClick:o,children:[(0,R.jsx)(h,{}),`Back to gigs`]}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(zt,{className:`text-xl`,children:e.title||`Untitled gig`}),(0,R.jsxs)(`div`,{className:`mt-2 flex flex-wrap gap-1.5`,children:[(0,R.jsx)(z,{variant:e.status===`filled`?`succeeded`:e.status===`lost`?`failed`:m?`queued`:`neutral`,children:e.status_label||Rn(e.status)}),m?null:(0,R.jsx)(z,{variant:`neutral`,children:`Not recruiting`}),_===null?null:(0,R.jsxs)(z,{variant:`running`,children:[_,`d stale`]}),e.posting_type?(0,R.jsx)(z,{variant:`neutral`,children:Rn(e.posting_type)}):null,e.discord_channel_name?(0,R.jsxs)(z,{variant:`neutral`,children:[`#`,e.discord_channel_name]}):null,(e.required_skills||[]).map(e=>(0,R.jsx)(z,{variant:`queued`,children:e},e)),(e.preferred_skills||[]).map(e=>(0,R.jsx)(z,{variant:`neutral`,children:e},e))]})]})]}),(0,R.jsxs)(`div`,{className:`grid min-w-[190px] gap-2`,children:[n?(0,R.jsxs)(W,{children:[`Gig status`,(0,R.jsx)(Vt,{"aria-label":`Status for ${e.title||`gig`}`,value:e.status,disabled:t[`gig:${e.id}:status`],onChange:t=>s(e.id,t.target.value),children:In.map(e=>(0,R.jsx)(`option`,{value:e,children:Rn(e)},e))})]}):null,g?(0,R.jsxs)(`a`,{className:`inline-flex min-h-9 items-center justify-center gap-2 rounded-md border bg-secondary px-3 text-sm font-semibold`,href:g,target:`_blank`,rel:`noreferrer`,children:[(0,R.jsx)(y,{className:`size-4`}),`Discord thread`]}):null]})]}),(0,R.jsxs)(Bt,{className:`grid gap-4 lg:grid-cols-[1fr_1fr_1fr]`,children:[(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Activity`}),(0,R.jsx)(`strong`,{className:`block`,children:qt(zn(e))||`unknown`}),(0,R.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[`Posted `,qt(e.posted_at)||`unknown`]})]}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`People`}),(0,R.jsx)(`strong`,{className:`block`,children:e.application_count||p.length}),(0,R.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[Number(e.interested_count||0),` interested`]})]}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:`Discord`}),(0,R.jsx)(`strong`,{className:`block`,children:e.discord_channel_name||`No channel`}),(0,R.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:e.discord_thread_id?`Thread ${e.discord_thread_id}`:`No thread`})]})]})]}),(0,R.jsxs)(V,{children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`People`}),(0,R.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[p.length,` candidate`,p.length===1?``:`s`]})]}),n?(0,R.jsxs)(`form`,{className:`grid gap-2 border-t p-4 md:grid-cols-[minmax(220px,1fr)_auto]`,onSubmit:t=>{t.preventDefault(),c(e.id,d).then(e=>{e&&f(``)})},children:[(0,R.jsxs)(W,{className:`min-w-0`,children:[`CRM profile`,(0,R.jsx)(U,{value:d,onChange:e=>f(e.target.value),placeholder:`https://crm.508.dev/#Contact/view/...`,"aria-label":`CRM profile for candidate`})]}),(0,R.jsxs)(B,{type:`submit`,className:`self-end`,disabled:t[`gig:${e.id}:addCandidate`]||!d.trim(),children:[(0,R.jsx)(se,{}),`Add candidate`]})]}):null,(0,R.jsx)(kn,{hidden:p.length!==0,children:`No suggested or interested people yet.`}),(0,R.jsx)(`div`,{className:L(`grid gap-3 p-4`,p.length===0&&`hidden`),children:p.map(a=>(0,R.jsx)(Qn,{gigId:e.id,application:a,loading:t,canWrite:n,crmContactUrl:r,crmAttachmentUrl:i,onUpdateApplicationStatus:u},a.id))})]})]})}function Qn({gigId:e,application:t,loading:n,canWrite:r,crmContactUrl:i,crmAttachmentUrl:a,onUpdateApplicationStatus:o}){let s=Xn(t),c=i(t.crm_contact_id),l=a(t.latest_resume_id),u=typeof t.fit_score==`number`?`${Math.round(t.fit_score)}/100`:typeof t.match_score==`number`?t.match_score.toFixed(1):``,d=typeof t.evaluation?.llm_summary==`string`?t.evaluation.llm_summary:``;return(0,R.jsxs)(`div`,{className:`grid gap-2 rounded-md border bg-background p-2`,children:[(0,R.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[c?(0,R.jsx)(`a`,{className:`font-extrabold text-primary`,href:c,target:`_blank`,rel:`noopener noreferrer`,children:s}):(0,R.jsx)(`strong`,{children:s}),(0,R.jsx)(z,{variant:t.status===`interested`?`succeeded`:`neutral`,children:Rn(t.status)}),(0,R.jsx)(z,{variant:`neutral`,children:Rn(t.source||`manual_add`)}),u?(0,R.jsxs)(`span`,{className:`text-xs font-bold text-muted-foreground`,children:[`Fit `,u]}):null,c?(0,R.jsx)(`a`,{className:`text-xs font-extrabold text-primary`,href:c,target:`_blank`,rel:`noopener noreferrer`,"aria-label":`Open ${s} CRM profile`,children:`CRM profile`}):null,l?(0,R.jsx)(`a`,{className:`text-xs font-extrabold text-primary`,href:l,target:`_blank`,rel:`noopener noreferrer`,children:`Resume`}):null]}),d?(0,R.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:d}):null,r?(0,R.jsx)(Vt,{"aria-label":`Candidate status for ${s}`,value:t.status||`suggested`,disabled:n[`application:${t.id}:status`],onChange:n=>o(e,t.id,n.target.value),children:Ln.map(e=>(0,R.jsx)(`option`,{value:e,children:Rn(e)},e))}):null]})}function $n(e){let t=pn[e.peopleFilterKind]?.options||[];return(0,R.jsxs)(V,{children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`People lookup`}),(0,R.jsxs)(`div`,{className:`flex flex-wrap items-center justify-end gap-2`,children:[e.canSync?(0,R.jsxs)(B,{id:`syncPeople`,"data-permission":`people:sync`,type:`button`,onClick:e.onSync,disabled:e.loading.syncPeople,children:[(0,R.jsx)(C,{}),`Sync people`]}):null,e.crmBaseUrl?(0,R.jsx)(`a`,{id:`crmHomeLink`,className:`text-sm font-extrabold text-primary`,href:e.crmBaseUrl,target:`_blank`,rel:`noreferrer`,children:`Open CRM`}):null,(0,R.jsx)(`span`,{id:`peopleStatus`,className:`text-sm text-muted-foreground`,children:e.loading.people?`Loading`:`${e.people.length} shown`})]})]}),(0,R.jsxs)(`div`,{className:`grid gap-3 border-b p-4 md:grid-cols-[minmax(0,1fr)_auto]`,children:[(0,R.jsxs)(W,{children:[`Search CRM people cache`,(0,R.jsx)(U,{id:`peopleQuery`,value:e.peopleQuery,autoComplete:`off`,placeholder:`Name, email, CRM id, Discord, resume`,onChange:t=>e.setPeopleQuery(t.target.value),onKeyDown:t=>{t.key===`Enter`&&e.onSearch()}})]}),(0,R.jsxs)(B,{id:`searchPeople`,type:`button`,onClick:e.onSearch,disabled:e.loading.people,children:[(0,R.jsx)(ne,{}),`Search`]})]}),(0,R.jsxs)(`div`,{className:`grid gap-3 border-b bg-background p-4 md:grid-cols-[minmax(120px,.7fr)_minmax(150px,1fr)_minmax(150px,1fr)_auto]`,children:[(0,R.jsxs)(W,{children:[`Member`,(0,R.jsxs)(Vt,{id:`peopleMember`,value:e.peopleMember,onChange:t=>e.setPeopleMember(t.target.value),children:[(0,R.jsx)(`option`,{value:``,children:`Any`}),(0,R.jsx)(`option`,{value:`true`,children:`Member`}),(0,R.jsx)(`option`,{value:`false`,children:`Not member`})]})]}),(0,R.jsxs)(W,{children:[`Add filter`,(0,R.jsx)(Vt,{id:`peopleFilterKind`,value:e.peopleFilterKind,disabled:e.peopleFilterKeys.length===0,onChange:t=>e.setPeopleFilterKind(t.target.value),children:e.peopleFilterKeys.map(e=>(0,R.jsx)(`option`,{value:e,children:pn[e].label},e))})]}),(0,R.jsxs)(W,{children:[`Value`,(0,R.jsx)(Vt,{id:`peopleFilterValue`,value:e.peopleFilterValue,onChange:t=>e.setPeopleFilterValue(t.target.value),children:t.map(([e,t])=>(0,R.jsx)(`option`,{value:e,children:t},e))})]}),(0,R.jsx)(B,{id:`addPeopleFilter`,type:`button`,onClick:e.addFilter,disabled:e.peopleFilterKeys.length===0,children:`Add filter`}),(0,R.jsx)(`div`,{id:`activePeopleFilters`,className:`md:col-span-4`,children:(0,R.jsx)(Fn,{filters:e.peopleFilters,onRemove:e.removeFilter})})]}),(0,R.jsx)(kn,{hidden:e.people.length!==0,children:`No people match this lookup.`}),(0,R.jsx)(`div`,{className:`overflow-x-auto`,children:(0,R.jsxs)(Ht,{id:`peopleTable`,className:L(`min-w-[900px]`,e.people.length===0&&`hidden`),"aria-label":`People lookup results`,children:[(0,R.jsx)(Ut,{children:(0,R.jsxs)(Gt,{children:[(0,R.jsx)(Dn,{className:`w-[27%]`,label:`Name`,scope:`people`,sort:e.sort,sortKey:`name`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[28%]`,label:`Status`,scope:`people`,sort:e.sort,sortKey:`status`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[20%]`,label:`Discord`,scope:`people`,sort:e.sort,sortKey:`discord`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[25%]`,label:`Resume / skills`,scope:`people`,sort:e.sort,sortKey:`resume`,onSort:(t,n)=>e.onSort(n)})]})}),(0,R.jsx)(Wt,{id:`peopleBody`,children:e.people.map(t=>{let n=t.name||t.email_508||t.email||`CRM contact`,r=e.crmContactUrl(t.crm_contact_id),i=t.profile_status||{},a=Number(i.skills_count||0),o=e.crmAttachmentUrl(t.latest_resume_id);return(0,R.jsxs)(Gt,{children:[(0,R.jsxs)(K,{children:[r?(0,R.jsx)(`a`,{className:`font-extrabold text-primary`,href:r,target:`_blank`,rel:`noreferrer`,"aria-label":`Open ${n} in CRM`,children:n}):(0,R.jsx)(`strong`,{children:n}),(0,R.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:[t.email_508||t.email,t.contact_type].filter(Boolean).join(` | `)})]}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`flex flex-wrap gap-1.5`,children:[i.crm_active?null:(0,R.jsx)(z,{variant:`missing`,children:t.sync_status||`CRM sync issue`}),(0,R.jsx)(z,{variant:i.is_member?`succeeded`:`missing`,children:i.is_member?`Member`:`Missing Member`}),(0,R.jsx)(z,{variant:i.discord_linked?`succeeded`:`missing`,children:i.discord_linked?`Discord`:`Missing Discord`}),(0,R.jsx)(z,{variant:i.email_508?`succeeded`:`missing`,children:i.email_508?`508 email`:`Missing 508 email`}),i.latest_resume?null:(0,R.jsx)(z,{variant:`missing`,children:`Missing Resume`})]})}),(0,R.jsx)(K,{children:[t.discord_username,t.discord_user_id].filter(Boolean).join(` | `)||`Not linked`}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5`,children:[o?(0,R.jsx)(`a`,{className:`inline-flex min-h-7 items-center rounded-md border bg-secondary px-2 text-xs font-extrabold`,href:o,target:`_blank`,rel:`noreferrer`,"aria-label":`Open ${n} resume`,children:`Resume`}):(0,R.jsx)(`span`,{children:t.latest_resume_name||t.latest_resume_id||`No resume`}),(0,R.jsx)(z,{variant:a>0?`succeeded`:`missing`,children:a>0?`Skills parsed`:`Skills not parsed`})]})})]},t.crm_contact_id||n)})})]})})]})}function er(e){let t=pn[e.onboardingFilterKind]?.options||[];return(0,R.jsxs)(R.Fragment,{children:[e.canWrite?(0,R.jsx)(or,{loading:e.loading.engineerSetup,onSetup:e.onSetupEngineer}):null,(0,R.jsxs)(V,{children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`Onboarding queue`}),(0,R.jsx)(`span`,{id:`onboardingStatus`,className:`text-sm text-muted-foreground`,children:e.loading.onboarding?`Loading`:`${e.people.length} shown`})]}),(0,R.jsxs)(`div`,{className:`grid gap-3 border-b p-4 md:grid-cols-[minmax(0,1fr)_auto]`,children:[(0,R.jsxs)(W,{children:[`Search prospects`,(0,R.jsx)(U,{id:`onboardingQuery`,value:e.onboardingQuery,autoComplete:`off`,placeholder:`Name, email, Discord, onboarder`,onChange:t=>e.setOnboardingQuery(t.target.value),onKeyDown:t=>t.key===`Enter`&&e.onSearch()})]}),(0,R.jsxs)(B,{id:`searchOnboarding`,type:`button`,onClick:e.onSearch,disabled:e.loading.onboarding,children:[(0,R.jsx)(ne,{}),`Search`]})]}),(0,R.jsxs)(`div`,{className:`grid gap-3 border-b bg-background p-4 md:grid-cols-[minmax(140px,.8fr)_minmax(150px,1fr)_minmax(150px,1fr)_minmax(120px,.7fr)_auto]`,children:[(0,R.jsxs)(W,{children:[`Status`,(0,R.jsxs)(Vt,{id:`onboardingState`,value:e.onboardingState,onChange:t=>e.setOnboardingState(t.target.value),children:[(0,R.jsx)(`option`,{value:``,children:`Any state`}),hn.map(([e,t])=>(0,R.jsx)(`option`,{value:e,children:t},e))]})]}),(0,R.jsxs)(W,{children:[`Onboarder`,(0,R.jsx)(U,{id:`onboarderFilter`,value:e.onboarderFilter,autoComplete:`off`,placeholder:`Any onboarder`,onChange:t=>e.setOnboarderFilter(t.target.value),onKeyDown:t=>t.key===`Enter`&&e.onSearch()})]}),(0,R.jsxs)(W,{children:[`Add filter`,(0,R.jsx)(Vt,{id:`onboardingFilterKind`,value:e.onboardingFilterKind,disabled:e.onboardingFilterKeys.length===0,onChange:t=>e.setOnboardingFilterKind(t.target.value),children:e.onboardingFilterKeys.map(e=>(0,R.jsx)(`option`,{value:e,children:pn[e].label},e))})]}),(0,R.jsxs)(W,{children:[`Value`,(0,R.jsx)(Vt,{id:`onboardingFilterValue`,value:e.onboardingFilterValue,onChange:t=>e.setOnboardingFilterValue(t.target.value),children:t.map(([e,t])=>(0,R.jsx)(`option`,{value:e,children:t},e))})]}),(0,R.jsx)(B,{id:`addOnboardingFilter`,type:`button`,onClick:e.addFilter,disabled:e.onboardingFilterKeys.length===0,children:`Add filter`}),(0,R.jsx)(`div`,{id:`activeOnboardingFilters`,className:`md:col-span-5`,children:(0,R.jsx)(Fn,{filters:e.onboardingFilters,onRemove:e.removeFilter,suffix:`onboarding filter`})})]}),(0,R.jsx)(kn,{hidden:e.people.length!==0,children:`No prospects match this queue view.`}),(0,R.jsx)(`div`,{className:`overflow-x-auto`,children:(0,R.jsxs)(Ht,{id:`onboardingTable`,className:L(`min-w-[1340px]`,e.people.length===0&&`hidden`),"aria-label":`Onboarding queue`,children:[(0,R.jsx)(Ut,{children:(0,R.jsxs)(Gt,{children:[(0,R.jsx)(Dn,{className:`w-[18%]`,label:`Name`,scope:`onboarding`,sort:e.sort,sortKey:`name`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[12%]`,label:`Status`,scope:`onboarding`,sort:e.sort,sortKey:`onboarding_state`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[20%]`,label:`Onboarder`,scope:`onboarding`,sort:e.sort,sortKey:`onboarder`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[12%]`,label:`Updated`,scope:`onboarding`,sort:e.sort,sortKey:`updated`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(G,{className:`w-[15%]`,children:`Email`}),(0,R.jsx)(G,{className:`w-[12%]`,children:`Links`}),(0,R.jsx)(Dn,{className:`w-[11%]`,label:`Needs`,scope:`onboarding`,sort:e.sort,sortKey:`profile_gaps`,onSort:(t,n)=>e.onSort(n)})]})}),(0,R.jsx)(Wt,{id:`onboardingBody`,children:e.people.map(t=>(0,R.jsx)(sr,{person:t,loading:e.loading,canWrite:e.canWrite,onAssign:e.onAssign,onStatusChange:e.onStatusChange,onDraftEmail:e.onDraftEmail,onSendEmail:e.onSendEmail,crmContactUrl:e.crmContactUrl,crmAttachmentUrl:e.crmAttachmentUrl,canConfigure:e.canConfigure,onOpenConfiguration:e.onOpenConfiguration},t.crm_contact_id||t.name))})]})})]})]})}var tr=[`Female`,`Genderqueer`,`Male`,`Non-Conforming`,`Other`,`Prefer not to say`,`Transgender`],nr=[`Company Email`,`Personal Email`,`User ID`];function rr(e){let t=(e||``).trim().split(/\s+/).filter(Boolean);return t.length===0?{first:``,middle:``,last:``}:t.length===1?{first:t[0],middle:``,last:``}:t.length===2?{first:t[0],middle:``,last:t[1]}:{first:t[0],middle:t.slice(1,-1).join(` `),last:t[t.length-1]}}function ir(e){let t=(e.email||``).trim();return!t||t.toLowerCase().endsWith(`@508.dev`)?``:t}function ar(e){let t=(e.email_508||``).trim();if(t)return t;let n=(e.email||``).trim();return n.toLowerCase().endsWith(`@508.dev`)?n:``}function or({loading:e,onSetup:t}){let[n,r]=(0,l.useState)(``),[i,a]=(0,l.useState)([]),[o,s]=(0,l.useState)(!1),[c,u]=(0,l.useState)(``),[d,f]=(0,l.useState)(``),[p,m]=(0,l.useState)(``),[h,g]=(0,l.useState)(``),[_,v]=(0,l.useState)(``),[y,b]=(0,l.useState)(``),[x,ee]=(0,l.useState)(``),[te,S]=(0,l.useState)(``),[C,re]=(0,l.useState)(``),[ie,ae]=(0,l.useState)(``),[oe,ce]=(0,l.useState)(``);function w(e){let t=rr(e.name);m(t.first),g(t.middle),v(t.last),f(ar(e)),ae(ir(e)),b(e.address_country||``),r(e.name||e.email_508||e.email||``),a([]),u(``)}async function T(){let e=n.trim();if(e){s(!0),u(``);try{a(await q(`/dashboard/api/people?${new URLSearchParams({limit:`8`,query:e}).toString()}`))}catch(e){u(bn(e,`Unable to search people`)),a([])}finally{s(!1)}}}async function le(){let e={email:d,first_name:p,middle_name:h,last_name:_,country:y,personal_email:ie};x.trim()&&(e.gender=x),te.trim()&&(e.date_of_birth=te),C.trim()&&(e.date_of_joining=C),oe.trim()&&(e.prefered_email=oe),await t(e)&&(r(``),a([]),f(``),m(``),g(``),v(``),b(``),ee(``),S(``),re(``),ae(``),ce(``))}return(0,R.jsxs)(V,{children:[(0,R.jsx)(H,{children:(0,R.jsx)(zt,{children:`Engineer setup`})}),(0,R.jsx)(Bt,{children:(0,R.jsxs)(`form`,{className:`grid gap-3`,onSubmit:e=>{e.preventDefault(),le()},children:[(0,R.jsxs)(`div`,{className:`grid gap-3 border-b pb-3 md:grid-cols-[minmax(0,1fr)_auto]`,children:[(0,R.jsxs)(W,{children:[`CRM person`,(0,R.jsx)(U,{value:n,autoComplete:`off`,placeholder:`Search name or email`,onChange:e=>r(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),T())}})]}),(0,R.jsxs)(B,{type:`button`,onClick:T,disabled:o||!n.trim(),children:[(0,R.jsx)(ne,{}),`Search`]}),c?(0,R.jsx)(`span`,{className:`text-sm font-semibold text-destructive`,children:c}):null,i.length>0?(0,R.jsx)(`div`,{className:`grid gap-2 md:col-span-2`,children:i.map(e=>{let t=e.name||e.email_508||e.email||e.crm_contact_id,n=[e.email_508||e.email,e.contact_type].filter(Boolean).join(` | `);return(0,R.jsxs)(`button`,{type:`button`,className:`grid rounded-md border bg-background px-3 py-2 text-left text-sm hover:border-primary`,onClick:()=>w(e),children:[(0,R.jsx)(`strong`,{children:t}),n?(0,R.jsx)(`span`,{className:`text-muted-foreground`,children:n}):null]},e.crm_contact_id||t)})}):null]}),(0,R.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(130px,.6fr)]`,children:[(0,R.jsxs)(W,{children:[`Company email`,(0,R.jsx)(U,{value:d,autoComplete:`off`,placeholder:`engineer@508.dev`,onChange:e=>f(e.target.value)})]}),(0,R.jsxs)(W,{children:[`First name`,(0,R.jsx)(U,{value:p,autoComplete:`off`,placeholder:`First`,onChange:e=>m(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Middle name`,(0,R.jsx)(U,{value:h,autoComplete:`off`,placeholder:`Optional`,onChange:e=>g(e.target.value)})]})]}),(0,R.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-[minmax(0,1fr)_minmax(130px,.6fr)]`,children:[(0,R.jsxs)(W,{children:[`Last name`,(0,R.jsx)(U,{value:_,autoComplete:`off`,placeholder:`Last`,onChange:e=>v(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Country`,(0,R.jsx)(U,{value:y,autoComplete:`off`,placeholder:`Taiwan`,onChange:e=>b(e.target.value)})]})]}),(0,R.jsxs)(`details`,{className:`rounded-md border bg-background p-3`,children:[(0,R.jsx)(`summary`,{className:`cursor-pointer text-sm font-extrabold`,children:`Advanced options`}),(0,R.jsxs)(`div`,{className:`mt-3 grid gap-3 md:grid-cols-2`,children:[(0,R.jsxs)(W,{children:[`Gender`,(0,R.jsxs)(Vt,{value:x,onChange:e=>ee(e.target.value),children:[(0,R.jsx)(`option`,{value:``,children:`Default`}),tr.map(e=>(0,R.jsx)(`option`,{value:e,children:e},e))]})]}),(0,R.jsxs)(W,{children:[`Date of birth`,(0,R.jsx)(U,{value:te,type:`date`,autoComplete:`off`,onChange:e=>S(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Date of joining`,(0,R.jsx)(U,{value:C,type:`date`,autoComplete:`off`,onChange:e=>re(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Personal email`,(0,R.jsx)(U,{value:ie,type:`email`,autoComplete:`off`,placeholder:`Optional`,onChange:e=>ae(e.target.value)})]}),(0,R.jsxs)(W,{children:[`Preferred contact email`,(0,R.jsxs)(Vt,{value:oe,onChange:e=>ce(e.target.value),children:[(0,R.jsx)(`option`,{value:``,children:`Default`}),nr.map(e=>(0,R.jsx)(`option`,{value:e,children:e},e))]})]})]})]}),(0,R.jsx)(`div`,{className:`flex flex-wrap items-center justify-between gap-3`,children:(0,R.jsxs)(B,{id:`setupEngineer`,type:`submit`,disabled:e||!d.trim()||!p.trim(),children:[(0,R.jsx)(se,{}),`Set up engineer`]})})]})})]})}function sr({person:e,loading:t,canWrite:n,onAssign:r,onStatusChange:i,onDraftEmail:a,onSendEmail:o,crmContactUrl:s,crmAttachmentUrl:c,canConfigure:u,onOpenConfiguration:d}){let f=e.name||e.email_508||e.email||`CRM contact`,[p,m]=(0,l.useState)($t(e.onboarder)),[h,g]=(0,l.useState)(!1),[_,v]=(0,l.useState)(null),[y,b]=(0,l.useState)(null),[x,ee]=(0,l.useState)({has_contributed:_n(Xt(e))===`onboarded`,discord_joined:e.discord_user_id?`yes`:`unknown`,agreement_signed:`unknown`});(0,l.useEffect)(()=>m($t(e.onboarder)),[e.onboarder]);let S=_n(Xt(e)),ne=e.profile_status||{},ae=[[`Discord`,ne.discord_linked],[`Resume`,ne.latest_resume],[`Skills`,Number(ne.skills_count||0)>0]].filter(([,e])=>!e),oe=s(e.crm_contact_id),se=c(e.latest_resume_id),ce=_?.onboarding_email_sent_at||e.onboarding_email_sent_at,w=_?.onboarding_email_sent_by||e.onboarding_email_sent_by,T=_?.onboarding_email_recipient||e.onboarding_email_recipient,le=!_||y!==null&&y.has_contributed===x.has_contributed&&y.discord_joined===x.discord_joined&&y.agreement_signed===x.agreement_signed,E=_&&!_.onboarding_email_sent_at?le?_.can_send?``:_.recipient_email?_.reply_to_email?`Send disabled: onboarding email SMTP is not configured.`:`Send disabled: your Reply-To email is missing.`:`Send disabled: candidate email is missing.`:`Send disabled: regenerate after changing draft options.`:``,ue=E.includes(`SMTP`),de=!!t[`onboarding-email-draft:${e.crm_contact_id}`],fe=!!t[`onboarding-email-send:${e.crm_contact_id}`],pe=_?.markdown_body||``;async function D(t=x){let n=await a(e.crm_contact_id,t);n&&(v(n),b({...t}),g(!0))}async function O(){if(!_||!y||!le)return;let t=await o(e.crm_contact_id,y,_.markdown_body);t&&v(t)}return(0,R.jsxs)(R.Fragment,{children:[(0,R.jsxs)(Gt,{children:[(0,R.jsxs)(K,{children:[oe?(0,R.jsx)(`a`,{className:`font-extrabold text-primary`,href:oe,target:`_blank`,rel:`noreferrer`,"aria-label":`Open ${f} in CRM`,children:f}):(0,R.jsx)(`strong`,{children:f}),(0,R.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:e.email_508||e.email||``})]}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`grid max-w-56 gap-2`,children:[(0,R.jsx)(z,{variant:Qt(Xt(e)),children:e.onboarding_status_label||Zt(Xt(e))}),n?(0,R.jsxs)(Vt,{"aria-label":`Onboarding status for ${f}`,value:S,disabled:t[`onboarding-status:${e.crm_contact_id}`],onChange:t=>i(e.crm_contact_id,t.target.value),children:[S?null:(0,R.jsx)(`option`,{value:``,disabled:!0,children:`No status`}),mn.map(([e,t])=>(0,R.jsx)(`option`,{value:e,children:t},e))]}):null]})}),(0,R.jsx)(K,{children:(0,R.jsxs)(`form`,{className:`grid max-w-64 grid-cols-[minmax(100px,1fr)_auto] items-center gap-2`,onSubmit:t=>{t.preventDefault(),r(e.crm_contact_id,p)},children:[(0,R.jsx)(U,{"aria-label":`Onboarder for ${f}`,value:p,placeholder:`508 username`,onChange:e=>m(e.target.value)}),(0,R.jsx)(B,{type:`submit`,size:`sm`,"aria-label":`Save onboarder for ${f}`,disabled:t[`onboarder:${e.crm_contact_id}`],children:`Save`})]})}),(0,R.jsx)(K,{children:qt(e.onboarding_updated_at)}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`grid gap-2`,children:[ce?(0,R.jsxs)(z,{variant:`succeeded`,children:[`Sent `,qt(ce)]}):(0,R.jsx)(z,{variant:`neutral`,children:`Not sent`}),T?(0,R.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:T}):null,w?(0,R.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`By `,w]}):null,n?(0,R.jsxs)(B,{type:`button`,size:`sm`,variant:h?`outline`:`secondary`,onClick:()=>{if(h){g(!1);return}g(!0),_||D()},disabled:de,children:[(0,R.jsx)(te,{}),_?`Edit draft`:`Draft email`]}):null]})}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`flex flex-wrap gap-1.5`,children:[se?(0,R.jsx)(`a`,{className:`inline-flex min-h-7 items-center rounded-md border bg-secondary px-2 text-xs font-extrabold`,href:se,target:`_blank`,rel:`noreferrer`,"aria-label":`Open ${f} resume`,children:`Resume`}):null,an(e.linkedin)?(0,R.jsx)(`a`,{className:`inline-flex min-h-7 items-center rounded-md border bg-secondary px-2 text-xs font-extrabold`,href:an(e.linkedin),target:`_blank`,rel:`noreferrer`,"aria-label":`Open ${f} LinkedIn`,children:`LinkedIn`}):null,on(e.github_username)?(0,R.jsx)(`a`,{className:`inline-flex min-h-7 items-center rounded-md border bg-secondary px-2 text-xs font-extrabold`,href:on(e.github_username),target:`_blank`,rel:`noreferrer`,"aria-label":`Open ${f} GitHub`,children:e.github_username||`GitHub`}):null,!se&&!an(e.linkedin)&&!on(e.github_username)?`None`:null]})}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`flex flex-wrap gap-1.5`,children:[ae.map(([e])=>(0,R.jsxs)(z,{variant:`missing`,children:[`Missing `,e]},String(e))),ae.length===0?`None`:null]})})]}),h?(0,R.jsx)(Gt,{children:(0,R.jsx)(K,{colSpan:7,className:`bg-secondary/30`,children:(0,R.jsxs)(`div`,{className:`grid gap-3 rounded-md border bg-background p-4`,children:[(0,R.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-[auto_minmax(150px,220px)_minmax(150px,220px)_auto] md:items-end`,children:[(0,R.jsxs)(`label`,{className:`flex min-h-9 items-center gap-2 text-sm font-semibold`,children:[(0,R.jsx)(`input`,{type:`checkbox`,checked:x.has_contributed,onChange:e=>{ee({...x,has_contributed:e.target.checked})}}),`Contribution done`]}),(0,R.jsxs)(W,{children:[`Discord`,(0,R.jsxs)(Vt,{value:x.discord_joined,onChange:e=>ee({...x,discord_joined:e.target.value}),children:[(0,R.jsx)(`option`,{value:`unknown`,children:`Unknown`}),(0,R.jsx)(`option`,{value:`yes`,children:`Joined`}),(0,R.jsx)(`option`,{value:`no`,children:`Not joined`})]})]}),(0,R.jsxs)(W,{children:[`Agreement`,(0,R.jsxs)(Vt,{value:x.agreement_signed,onChange:e=>ee({...x,agreement_signed:e.target.value}),children:[(0,R.jsx)(`option`,{value:`unknown`,children:`Unknown`}),(0,R.jsx)(`option`,{value:`yes`,children:`Signed`}),(0,R.jsx)(`option`,{value:`no`,children:`Not signed`})]})]}),(0,R.jsxs)(B,{type:`button`,variant:`outline`,onClick:()=>D(),disabled:de,children:[(0,R.jsx)(C,{}),`Regenerate`]})]}),_?(0,R.jsxs)(R.Fragment,{children:[(0,R.jsxs)(`div`,{className:`grid gap-2 text-sm md:grid-cols-3`,children:[(0,R.jsxs)(`span`,{children:[(0,R.jsx)(`strong`,{children:`To:`}),` `,_.recipient_email||`Missing`]}),(0,R.jsxs)(`span`,{children:[(0,R.jsx)(`strong`,{children:`Reply-To:`}),` `,_.reply_to_email||`Missing`]}),(0,R.jsxs)(`span`,{children:[(0,R.jsx)(`strong`,{children:`From:`}),` `,_.sender_display_name||`onboarding`]})]}),(0,R.jsxs)(W,{children:[`Subject`,(0,R.jsx)(U,{value:_.subject,readOnly:!0})]}),(0,R.jsxs)(W,{children:[`Draft`,(0,R.jsx)(`textarea`,{value:pe,className:`min-h-64 w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-sm text-foreground shadow-xs transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]`,onChange:e=>v({..._,markdown_body:e.target.value})})]}),(0,R.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,R.jsxs)(B,{type:`button`,variant:`default`,onClick:O,disabled:fe||!_.can_send||!le||!pe.trim(),title:E||void 0,children:[(0,R.jsx)(re,{}),fe?`Sending`:`Send`]}),E?(0,R.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[E,ue&&u?(0,R.jsxs)(B,{type:`button`,size:`sm`,variant:`ghost`,className:`ml-2`,onClick:d,children:[(0,R.jsx)(ie,{}),`Configure`]}):null]}):null,_.marker_status===`error`?(0,R.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[`Marker not saved: `,_.marker_error||`unknown`]}):null]})]}):(0,R.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:de?`Drafting email`:`No draft loaded`})]})})}):null]})}function cr(e){return(0,R.jsxs)(R.Fragment,{children:[(0,R.jsxs)(V,{className:`grid gap-3 p-4 md:grid-cols-4 md:items-end`,children:[(0,R.jsxs)(W,{children:[`Window`,(0,R.jsxs)(Vt,{id:`minutes`,value:e.minutes,onChange:t=>e.setMinutes(t.target.value),children:[(0,R.jsx)(`option`,{value:`15`,children:`15 minutes`}),(0,R.jsx)(`option`,{value:`60`,children:`1 hour`}),(0,R.jsx)(`option`,{value:`360`,children:`6 hours`}),(0,R.jsx)(`option`,{value:`1440`,children:`24 hours`})]})]}),(0,R.jsxs)(W,{children:[`Status`,(0,R.jsxs)(Vt,{id:`status`,value:e.status,onChange:t=>e.setStatus(t.target.value),children:[(0,R.jsx)(`option`,{value:``,children:`Any status`}),(0,R.jsx)(`option`,{value:`queued`,children:`Queued`}),(0,R.jsx)(`option`,{value:`running`,children:`Running`}),(0,R.jsx)(`option`,{value:`succeeded`,children:`Succeeded`}),(0,R.jsx)(`option`,{value:`failed`,children:`Failed`}),(0,R.jsx)(`option`,{value:`dead`,children:`Dead`}),(0,R.jsx)(`option`,{value:`canceled`,children:`Canceled`})]})]}),(0,R.jsxs)(W,{children:[`Type`,(0,R.jsx)(U,{id:`jobType`,value:e.jobType,autoComplete:`off`,placeholder:`Any type`,onChange:t=>e.setJobType(t.target.value),onKeyDown:t=>t.key===`Enter`&&e.onSearch()})]}),(0,R.jsxs)(B,{id:`refreshJobs`,type:`button`,onClick:e.onSearch,disabled:e.loading.jobs,children:[(0,R.jsx)(C,{}),`Refresh background tasks`]})]}),(0,R.jsxs)(`section`,{className:`grid gap-3 md:grid-cols-4`,"aria-label":`Background task summary`,children:[(0,R.jsx)(On,{id:`metricTotal`,label:`Total`,value:e.jobs.length}),(0,R.jsx)(On,{id:`metricQueued`,label:`Queued`,value:e.jobCounts.queued||0}),(0,R.jsx)(On,{id:`metricRunning`,label:`Running`,value:e.jobCounts.running||0}),(0,R.jsx)(On,{id:`metricFailed`,label:`Failed`,value:(e.jobCounts.failed||0)+(e.jobCounts.dead||0)})]}),(0,R.jsxs)(V,{children:[(0,R.jsx)(H,{children:(0,R.jsx)(zt,{children:`Recent background tasks`})}),(0,R.jsx)(kn,{hidden:e.jobs.length!==0,children:`No background tasks match these filters.`}),(0,R.jsx)(`div`,{className:`overflow-x-auto`,children:(0,R.jsxs)(Ht,{id:`jobsTable`,className:L(`min-w-[980px]`,e.jobs.length===0&&`hidden`),"aria-label":`Recent background tasks`,children:[(0,R.jsx)(Ut,{children:(0,R.jsxs)(Gt,{children:[(0,R.jsx)(Dn,{className:`w-[22%]`,label:`Task id`,scope:`jobs`,sort:e.sort,sortKey:`job_id`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[24%]`,label:`Task type`,scope:`jobs`,sort:e.sort,sortKey:`type`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[12%]`,label:`Status`,scope:`jobs`,sort:e.sort,sortKey:`status`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[12%]`,label:`Attempts`,scope:`jobs`,sort:e.sort,sortKey:`attempts`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[18%]`,label:`Updated`,scope:`jobs`,sort:e.sort,sortKey:`updated_at`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(G,{children:`Actions`})]})}),(0,R.jsx)(Wt,{id:`jobsBody`,children:e.jobs.map(t=>(0,R.jsxs)(Gt,{children:[(0,R.jsx)(K,{className:`font-mono`,children:t.job_id}),(0,R.jsx)(K,{children:t.type}),(0,R.jsx)(K,{children:(0,R.jsx)(z,{variant:t.status||`neutral`,children:t.status})}),(0,R.jsxs)(K,{children:[t.attempts,`/`,t.max_attempts]}),(0,R.jsx)(K,{children:qt(t.updated_at)}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-2`,children:[(0,R.jsx)(B,{type:`button`,size:`sm`,variant:`outline`,"aria-label":`View details for ${t.type} task ${t.job_id}`,onClick:()=>e.onDetail(t.job_id),disabled:e.loading[`detail:${t.job_id}`],children:`Details`}),e.canWrite?(0,R.jsx)(B,{type:`button`,size:`sm`,"aria-label":`Rerun ${t.type} task ${t.job_id}`,onClick:()=>e.onRerun(t.job_id),disabled:e.loading[`rerun:${t.job_id}`],children:`Rerun`}):null]})})]},t.job_id))})]})})]}),e.jobDetail?(0,R.jsxs)(V,{id:`jobDetailPanel`,children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`Task detail`}),(0,R.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:e.jobDetail.job_id})]}),(0,R.jsxs)(Bt,{className:`grid gap-4`,children:[(0,R.jsx)(`div`,{className:`grid gap-3 md:grid-cols-2`,children:[[`Task type`,e.jobDetail.type],[`Status`,e.jobDetail.status],[`Attempts`,`${e.jobDetail.attempts}/${e.jobDetail.max_attempts}`],[`Updated`,qt(e.jobDetail.updated_at)],[`Created`,qt(e.jobDetail.created_at)],[`Run after`,qt(e.jobDetail.run_after)],[`Locked by`,e.jobDetail.locked_by||`None`],[`Last error`,e.jobDetail.last_error||`None`]].map(([e,t])=>(0,R.jsxs)(`div`,{className:`grid gap-1 rounded-md border bg-background p-3`,children:[(0,R.jsx)(`span`,{className:`text-[11px] font-extrabold uppercase text-muted-foreground`,children:e}),(0,R.jsx)(`strong`,{className:`break-words text-sm`,children:t})]},e))}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`h2`,{className:`mb-2 text-[15px] font-bold`,children:`Payload`}),(0,R.jsx)(`pre`,{className:`max-h-64 overflow-auto whitespace-pre-wrap break-words rounded-md border bg-background p-3 font-mono text-xs`,children:Yt(e.jobDetail.payload)||`No payload`})]}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`h2`,{className:`mb-2 text-[15px] font-bold`,children:`Result`}),(0,R.jsx)(`pre`,{className:`max-h-64 overflow-auto whitespace-pre-wrap break-words rounded-md border bg-background p-3 font-mono text-xs`,children:Yt(e.jobDetail.result)||`No result`})]})]})]}):null]})}function lr(e){return(0,R.jsxs)(V,{children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`Recent audit`}),(0,R.jsxs)(B,{id:`refreshAudit`,type:`button`,variant:`outline`,onClick:e.onRefresh,disabled:e.loading.audit,children:[(0,R.jsx)(C,{}),`Refresh`]})]}),(0,R.jsx)(kn,{hidden:e.events.length!==0,children:`No audit events found.`}),(0,R.jsx)(`div`,{className:`overflow-x-auto`,children:(0,R.jsxs)(Ht,{id:`auditTable`,className:L(`min-w-[760px]`,e.events.length===0&&`hidden`),"aria-label":`Recent audit events`,children:[(0,R.jsx)(Ut,{children:(0,R.jsxs)(Gt,{children:[(0,R.jsx)(Dn,{className:`w-[24%]`,label:`Time`,scope:`audit`,sort:e.sort,sortKey:`occurred_at`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[28%]`,label:`Actor`,scope:`audit`,sort:e.sort,sortKey:`actor`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[28%]`,label:`Action`,scope:`audit`,sort:e.sort,sortKey:`action`,onSort:(t,n)=>e.onSort(n)}),(0,R.jsx)(Dn,{className:`w-[20%]`,label:`Result`,scope:`audit`,sort:e.sort,sortKey:`result`,onSort:(t,n)=>e.onSort(n)})]})}),(0,R.jsx)(Wt,{id:`auditBody`,children:e.events.map(e=>(0,R.jsxs)(Gt,{children:[(0,R.jsx)(K,{children:qt(e.occurred_at)}),(0,R.jsx)(K,{children:e.actor_display_name||e.actor_subject||e.actor_provider}),(0,R.jsx)(K,{children:e.action}),(0,R.jsx)(K,{children:(0,R.jsx)(z,{variant:e.result===`success`?`succeeded`:`failed`,children:e.result})})]},e.id||`${e.occurred_at||``}-${e.actor_subject||``}-${e.action||``}`))})]})})]})}function ur({report:e,loading:t,onRefresh:n}){let r=e?.summary||{},i=[[`Status`,e?.status_counts||{}],[`Intent`,e?.intent_counts||{}],[`Planner`,e?.planner_counts||{}]].flatMap(([e,t])=>Object.entries(t).map(([t,n])=>({label:e,value:t,count:n}))).sort((e,t)=>t.count-e.count||e.label.localeCompare(t.label)),a=Array.isArray(e?.recent_unsupported)?e.recent_unsupported:[];return(0,R.jsxs)(R.Fragment,{children:[(0,R.jsxs)(V,{children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`Agent requests`}),(0,R.jsxs)(B,{id:`refreshAgent`,type:`button`,variant:`outline`,onClick:n,disabled:t.agent,children:[(0,R.jsx)(C,{}),`Refresh`]})]}),(0,R.jsxs)(Bt,{className:`grid gap-3 md:grid-cols-5`,children:[(0,R.jsx)(On,{id:`agentMetricTotal`,label:`Total`,value:r.total||0}),(0,R.jsx)(On,{id:`agentMetricHandled`,label:`Handled`,value:r.handled||0}),(0,R.jsx)(On,{id:`agentMetricConfirmations`,label:`Confirmations`,value:r.requires_confirmation||0}),(0,R.jsx)(On,{id:`agentMetricClarifications`,label:`Clarifications`,value:r.needs_clarification||0}),(0,R.jsx)(On,{id:`agentMetricUnsupported`,label:`Not understood`,value:r.unsupported||0})]})]}),(0,R.jsxs)(V,{children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`Request mix`}),(0,R.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:`Recent agent.request audit events.`})]}),(0,R.jsx)(kn,{hidden:i.length!==0,children:`No agent request data found.`}),(0,R.jsx)(`div`,{className:`overflow-x-auto`,children:(0,R.jsxs)(Ht,{id:`agentBreakdownTable`,className:L(`min-w-[860px]`,i.length===0&&`hidden`),"aria-label":`Agent request breakdown`,children:[(0,R.jsx)(Ut,{children:(0,R.jsxs)(Gt,{children:[(0,R.jsx)(G,{children:`Dimension`}),(0,R.jsx)(G,{children:`Value`}),(0,R.jsx)(G,{children:`Count`})]})}),(0,R.jsx)(Wt,{id:`agentBreakdownBody`,children:i.map(e=>(0,R.jsxs)(Gt,{children:[(0,R.jsx)(K,{children:e.label}),(0,R.jsx)(K,{children:e.value}),(0,R.jsx)(K,{children:e.count})]},`${e.label}-${e.value}`))})]})})]}),(0,R.jsxs)(V,{children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`Not understood`}),(0,R.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:`Sanitized request text only.`})]}),(0,R.jsx)(kn,{hidden:a.length!==0,children:`No unsupported agent requests found.`}),(0,R.jsx)(`div`,{className:`overflow-x-auto`,children:(0,R.jsxs)(Ht,{id:`agentUnsupportedTable`,className:L(`min-w-[860px]`,a.length===0&&`hidden`),"aria-label":`Unsupported agent requests`,children:[(0,R.jsx)(Ut,{children:(0,R.jsxs)(Gt,{children:[(0,R.jsx)(G,{children:`Time`}),(0,R.jsx)(G,{children:`Actor`}),(0,R.jsx)(G,{children:`Message`}),(0,R.jsx)(G,{children:`Result`})]})}),(0,R.jsx)(Wt,{id:`agentUnsupportedBody`,children:a.map(e=>(0,R.jsxs)(Gt,{children:[(0,R.jsx)(K,{children:qt(e.occurred_at)}),(0,R.jsx)(K,{children:e.actor}),(0,R.jsx)(K,{children:e.message_sanitized}),(0,R.jsx)(K,{children:(0,R.jsx)(z,{variant:e.result===`success`?`succeeded`:`failed`,children:e.result||`unknown`})})]},`${e.occurred_at||``}-${e.actor||``}-${e.message_sanitized||``}`))})]})})]})]})}function dr({items:e,loading:t,canWrite:n,onRefresh:r,onSave:i,onClear:a,focusCategory:o,focusNonce:s}){let[c,u]=(0,l.useState)(`All`),[d,f]=(0,l.useState)(``),[p,m]=(0,l.useState)({}),h=(0,l.useMemo)(()=>{let t=new Set(e.map(e=>e.category)),n=sn.filter(e=>t.has(e.category)),r=Array.from(t).filter(e=>!cn.has(e)).sort().map(e=>({category:e,label:e,description:`Additional runtime settings.`}));return n.concat(r)},[e]),g=(0,l.useMemo)(()=>{let t=new Map;for(let n of e){if(c!==`All`&&n.category!==c)continue;let e=t.get(n.category)??[];e.push(n),t.set(n.category,e)}return Array.from(t.entries()).map(([e,t])=>{let n=cn.get(e),r=t.sort((e,t)=>Number(!un(e))-Number(!un(t))||e.label.localeCompare(t.label)),i=r.filter(un),a=r.filter(e=>!un(e));return{category:e,label:n?.label??e,description:n?.description??`Additional runtime settings.`,order:n?.index??sn.length,primaryItems:i.length?i:r,advancedItems:i.length?a:[],items:r}}).sort((e,t)=>e.order-t.order||e.label.localeCompare(t.label))},[e,c]),_=(0,l.useMemo)(()=>({configured:e.filter(e=>e.configured).length,envLocked:e.filter(e=>e.env_locked).length,missing:e.filter(e=>!e.configured).length}),[e]),v=g.reduce((e,t)=>e+t.items.length,0);(0,l.useEffect)(()=>{m(Object.fromEntries(e.map(e=>[e.key,e.is_secret?``:String(e.value??``)])))},[e]),(0,l.useEffect)(()=>{c!==`All`&&!e.some(e=>e.category===c)&&u(`All`)},[e,c]),(0,l.useEffect)(()=>{if(!o||!e.some(e=>e.category===o))return;u(o),f(o);let t=window.requestAnimationFrame?.(()=>{document.getElementById(ln(o))?.scrollIntoView({block:`start`,behavior:`smooth`})}),n=window.setTimeout(()=>f(``),4e3);return()=>{t!==void 0&&window.cancelAnimationFrame?.(t),window.clearTimeout(n)}},[o,s,e]);function y(e){return e.source===`env`?`ENV`:e.source===`database`?`DB`:`Default`}function b(e){let r=p[e.key]??``,i=!n||e.env_locked||t[`configuration:${e.key}`];return e.value_type===`bool`?(0,R.jsxs)(Vt,{"aria-label":`${e.label} value`,value:r,disabled:i,onChange:t=>m(n=>({...n,[e.key]:t.target.value})),children:[(0,R.jsx)(`option`,{value:``,children:`Default`}),(0,R.jsx)(`option`,{value:`true`,children:`True`}),(0,R.jsx)(`option`,{value:`false`,children:`False`})]}):(0,R.jsx)(U,{"aria-label":`${e.label} value`,value:r,type:e.is_secret?`password`:e.value_type===`int`?`number`:`text`,inputMode:e.value_type===`int`||e.value_type===`float`?`numeric`:`text`,placeholder:e.is_secret?`Set new value`:``,autoComplete:`off`,disabled:i,onChange:t=>m(n=>({...n,[e.key]:t.target.value}))})}function x(e,t,n){return(0,R.jsx)(`div`,{className:`overflow-x-auto`,children:(0,R.jsxs)(Ht,{id:`configurationTable-${n}`,className:`min-w-[980px]`,"aria-label":`${e} configuration settings`,children:[(0,R.jsx)(Ut,{children:(0,R.jsxs)(Gt,{children:[(0,R.jsx)(G,{className:`w-[26%]`,children:`Setting`}),(0,R.jsx)(G,{className:`w-[12%]`,children:`Source`}),(0,R.jsx)(G,{className:`w-[18%]`,children:`Active`}),(0,R.jsx)(G,{className:`w-[29%]`,children:`Value`}),(0,R.jsx)(G,{className:`w-[15%]`,children:`Actions`})]})}),(0,R.jsx)(Wt,{id:`configurationBody-${n}`,children:t.map(e=>ee(e))})]})})}function ee(e){let r=t[`configuration:${e.key}`],o=n&&!e.env_locked&&!r,s=p[e.key]??``,c=!e.is_secret&&!s.trim();return(0,R.jsxs)(Gt,{children:[(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`grid gap-1`,children:[(0,R.jsx)(`strong`,{children:e.label}),(0,R.jsx)(`span`,{className:`font-mono text-xs text-muted-foreground`,children:e.key}),(0,R.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:e.description}),e.restart_required?(0,R.jsx)(`div`,{children:(0,R.jsx)(z,{variant:`running`,children:`Restart`})}):null]})}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`grid gap-1.5`,children:[(0,R.jsx)(z,{variant:e.source===`env`?`running`:`neutral`,children:y(e)}),e.env_locked?(0,R.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`Environment locked`}):null]})}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`grid gap-1`,children:[(0,R.jsx)(z,{variant:e.configured?`succeeded`:`missing`,children:e.configured?`Configured`:`Missing`}),e.is_secret?(0,R.jsx)(`span`,{className:`font-mono text-xs text-muted-foreground`,children:e.masked_value||(e.configured?`Hidden`:`No secret`)}):(0,R.jsx)(`span`,{className:`break-words text-xs text-muted-foreground`,children:String(e.value??``)||`Default`}),e.is_secret&&e.secret_encryption_configured===!1?(0,R.jsx)(`span`,{className:`text-xs text-red-300`,children:`Encryption key missing`}):null]})}),(0,R.jsx)(K,{children:b(e)}),(0,R.jsx)(K,{children:(0,R.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-2`,children:[(0,R.jsx)(B,{type:`button`,size:`sm`,onClick:()=>i(e.key,s),disabled:!o||e.is_secret&&!s.trim()||c||e.is_secret&&e.secret_encryption_configured===!1,children:`Save`}),(0,R.jsx)(B,{type:`button`,size:`sm`,variant:`outline`,onClick:()=>a(e.key),disabled:!o||e.source!==`database`,children:`Clear`})]})})]},e.key)}return(0,R.jsxs)(`div`,{className:`grid gap-4`,children:[(0,R.jsxs)(V,{children:[(0,R.jsxs)(H,{children:[(0,R.jsx)(zt,{children:`Configuration`}),(0,R.jsxs)(B,{id:`refreshConfiguration`,type:`button`,variant:`outline`,onClick:r,disabled:t.configuration,children:[(0,R.jsx)(C,{}),`Refresh`]})]}),(0,R.jsxs)(Bt,{className:`grid gap-4`,children:[(0,R.jsx)(`section`,{className:`grid gap-3 sm:grid-cols-2 lg:grid-cols-4`,"aria-label":`Configuration summary`,children:[[`Total`,e.length],[`Configured`,_.configured],[`Missing`,_.missing],[`Env locked`,_.envLocked]].map(([e,t])=>(0,R.jsxs)(`div`,{className:`rounded-md border bg-background p-3`,children:[(0,R.jsx)(`span`,{className:`text-[11px] font-extrabold uppercase text-muted-foreground`,children:e}),(0,R.jsx)(`strong`,{className:`mt-1 block text-xl`,children:t})]},e))}),(0,R.jsxs)(`section`,{className:`flex flex-wrap gap-2`,"aria-label":`Configuration groups`,children:[(0,R.jsxs)(B,{type:`button`,size:`sm`,variant:c===`All`?`default`:`outline`,"aria-pressed":c===`All`,onClick:()=>u(`All`),children:[`All groups`,(0,R.jsx)(`span`,{className:`font-mono text-[11px]`,children:e.length})]}),h.map(t=>{let n=e.filter(e=>e.category===t.category).length;return(0,R.jsxs)(B,{type:`button`,size:`sm`,variant:c===t.category?`default`:`outline`,"aria-pressed":c===t.category,onClick:()=>u(t.category),children:[t.label,(0,R.jsx)(`span`,{className:`font-mono text-[11px]`,children:n})]},t.category)})]})]})]}),(0,R.jsx)(kn,{hidden:v!==0,children:`No configuration entries found.`}),g.map(e=>{let t=e.items.filter(e=>e.configured).length,n=e.items.length-t,r=e.items.some(e=>e.restart_required);return(0,R.jsxs)(V,{id:ln(e.category),className:L(`scroll-mt-4 transition-shadow`,d===e.category&&`ring-2 ring-primary ring-offset-2 ring-offset-background`),children:[(0,R.jsxs)(H,{className:`items-start`,children:[(0,R.jsxs)(`div`,{className:`grid gap-1`,children:[(0,R.jsx)(zt,{children:e.label}),(0,R.jsx)(`span`,{className:`text-sm text-muted-foreground`,children:e.description})]}),(0,R.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-1.5`,children:[(0,R.jsxs)(z,{variant:`neutral`,children:[e.items.length,` settings`]}),(0,R.jsxs)(z,{variant:n?`missing`:`succeeded`,children:[t,` configured`]}),r?(0,R.jsx)(z,{variant:`running`,children:`Restart`}):null]})]}),x(e.label,e.primaryItems,e.category),e.advancedItems.length?(0,R.jsxs)(`details`,{className:`border-t bg-background/40`,children:[(0,R.jsxs)(`summary`,{className:`flex min-h-11 cursor-pointer items-center justify-between gap-3 px-4 py-3 text-sm font-extrabold`,children:[(0,R.jsx)(`span`,{children:`Advanced`}),(0,R.jsxs)(`span`,{className:`font-mono text-xs text-muted-foreground`,children:[e.advancedItems.length,` settings`]})]}),(0,R.jsx)(`div`,{className:`border-t`,children:x(`${e.label} advanced`,e.advancedItems,`${e.category}-advanced`)})]}):null]},e.category)})]})}var fr=document.getElementById(`root`);if(fr)(0,fe.createRoot)(fr).render((0,R.jsx)(l.StrictMode,{children:(0,R.jsx)(jn,{})}));else throw Error(`Missing #root container`); \ No newline at end of file diff --git a/apps/api/src/five08/backend/static/dashboard/assets/index-Dtgvsgfe.css b/apps/api/src/five08/backend/static/dashboard/assets/index-Dtgvsgfe.css new file mode 100644 index 00000000..efdc3fe6 --- /dev/null +++ b/apps/api/src/five08/backend/static/dashboard/assets/index-Dtgvsgfe.css @@ -0,0 +1,2 @@ +/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-100:oklch(93.6% .032 17.717);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-700:oklch(50.5% .213 27.518);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-teal-200:oklch(91% .096 180.426);--color-teal-400:oklch(77.7% .152 181.912);--color-teal-500:oklch(70.4% .14 182.503);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--leading-tight:1.25;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){*{outline-color:color-mix(in oklab, var(--ring) 50%, transparent)}}body{margin:calc(var(--spacing) * 0);min-width:calc(var(--spacing) * 80);background-color:var(--background);color:var(--foreground);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}button{cursor:pointer}button:disabled{cursor:not-allowed}}@layer components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing) * 0)}.-top-1{top:calc(var(--spacing) * -1)}.top-0{top:calc(var(--spacing) * 0)}.-right-1{right:calc(var(--spacing) * -1)}.right-0{right:calc(var(--spacing) * 0)}.right-5{right:calc(var(--spacing) * 5)}.bottom-5{bottom:calc(var(--spacing) * 5)}.left-5{left:calc(var(--spacing) * 5)}.z-20{z-index:20}.z-40{z-index:40}.z-50{z-index:50}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing) * 0)}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.ml-2{margin-left:calc(var(--spacing) * 2)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.table-cell{display:table-cell}.table-row{display:table-row}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-full{height:100%}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-56{max-height:calc(var(--spacing) * 56)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-\[78vh\]{max-height:78vh}.max-h-\[90vh\]{max-height:90vh}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-5{min-height:calc(var(--spacing) * 5)}.min-h-7{min-height:calc(var(--spacing) * 7)}.min-h-9{min-height:calc(var(--spacing) * 9)}.min-h-10{min-height:calc(var(--spacing) * 10)}.min-h-11{min-height:calc(var(--spacing) * 11)}.min-h-20{min-height:calc(var(--spacing) * 20)}.min-h-64{min-height:calc(var(--spacing) * 64)}.min-h-\[22px\]{min-height:22px}.w-\[10\%\]{width:10%}.w-\[11\%\]{width:11%}.w-\[12\%\]{width:12%}.w-\[14\%\]{width:14%}.w-\[15\%\]{width:15%}.w-\[16\%\]{width:16%}.w-\[18\%\]{width:18%}.w-\[20\%\]{width:20%}.w-\[22\%\]{width:22%}.w-\[24\%\]{width:24%}.w-\[25\%\]{width:25%}.w-\[26\%\]{width:26%}.w-\[27\%\]{width:27%}.w-\[28\%\]{width:28%}.w-\[29\%\]{width:29%}.w-\[34\%\]{width:34%}.w-\[48px\]{width:48px}.w-\[160px\]{width:160px}.w-\[min\(48rem\,calc\(100vw-2\.5rem\)\)\]{width:min(48rem,100vw - 2.5rem)}.w-fit{width:fit-content}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-56{max-width:calc(var(--spacing) * 56)}.max-w-64{max-width:calc(var(--spacing) * 64)}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-5{min-width:calc(var(--spacing) * 5)}.min-w-44{min-width:calc(var(--spacing) * 44)}.min-w-\[190px\]{min-width:190px}.min-w-\[760px\]{min-width:760px}.min-w-\[860px\]{min-width:860px}.min-w-\[900px\]{min-width:900px}.min-w-\[920px\]{min-width:920px}.min-w-\[980px\]{min-width:980px}.min-w-\[1100px\]{min-width:1100px}.min-w-\[1340px\]{min-width:1340px}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.scroll-mt-4{scroll-margin-top:calc(var(--spacing) * 4)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[minmax\(100px\,1fr\)_auto\]{grid-template-columns:minmax(100px,1fr) auto}.grid-rows-\[auto_minmax\(0\,1fr\)\]{grid-template-rows:auto minmax(0,1fr)}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.content-start{align-content:flex-start}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-1{row-gap:calc(var(--spacing) * 1)}.self-end{align-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-400\/40{border-color:#fcbb0066}@supports (color:color-mix(in lab, red, red)){.border-amber-400\/40{border-color:color-mix(in oklab, var(--color-amber-400) 40%, transparent)}}.border-amber-500\/40{border-color:#f99c0066}@supports (color:color-mix(in lab, red, red)){.border-amber-500\/40{border-color:color-mix(in oklab, var(--color-amber-500) 40%, transparent)}}.border-border{border-color:var(--border)}.border-destructive{border-color:var(--destructive)}.border-emerald-400\/35{border-color:#00d29459}@supports (color:color-mix(in lab, red, red)){.border-emerald-400\/35{border-color:color-mix(in oklab, var(--color-emerald-400) 35%, transparent)}}.border-emerald-500\/40{border-color:#00bb7f66}@supports (color:color-mix(in lab, red, red)){.border-emerald-500\/40{border-color:color-mix(in oklab, var(--color-emerald-500) 40%, transparent)}}.border-input{border-color:var(--input)}.border-primary{border-color:var(--primary)}.border-red-400\/40{border-color:#ff656866}@supports (color:color-mix(in lab, red, red)){.border-red-400\/40{border-color:color-mix(in oklab, var(--color-red-400) 40%, transparent)}}.border-red-500\/25{border-color:#fb2c3640}@supports (color:color-mix(in lab, red, red)){.border-red-500\/25{border-color:color-mix(in oklab, var(--color-red-500) 25%, transparent)}}.border-red-500\/40{border-color:#fb2c3666}@supports (color:color-mix(in lab, red, red)){.border-red-500\/40{border-color:color-mix(in oklab, var(--color-red-500) 40%, transparent)}}.border-teal-400\/40{border-color:#00d3bd66}@supports (color:color-mix(in lab, red, red)){.border-teal-400\/40{border-color:color-mix(in oklab, var(--color-teal-400) 40%, transparent)}}.border-transparent{border-color:#0000}.border-l-muted-foreground\/60{border-left-color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.border-l-muted-foreground\/60{border-left-color:color-mix(in oklab, var(--muted-foreground) 60%, transparent)}}.bg-accent{background-color:var(--accent)}.bg-amber-200{background-color:var(--color-amber-200)}.bg-amber-500\/15{background-color:#f99c0026}@supports (color:color-mix(in lab, red, red)){.bg-amber-500\/15{background-color:color-mix(in oklab, var(--color-amber-500) 15%, transparent)}}.bg-background,.bg-background\/40{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.bg-background\/40{background-color:color-mix(in oklab, var(--background) 40%, transparent)}}.bg-background\/90{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.bg-background\/90{background-color:color-mix(in oklab, var(--background) 90%, transparent)}}.bg-black\/20{background-color:#0003}@supports (color:color-mix(in lab, red, red)){.bg-black\/20{background-color:color-mix(in oklab, var(--color-black) 20%, transparent)}}.bg-black\/30{background-color:#0000004d}@supports (color:color-mix(in lab, red, red)){.bg-black\/30{background-color:color-mix(in oklab, var(--color-black) 30%, transparent)}}.bg-black\/45{background-color:#00000073}@supports (color:color-mix(in lab, red, red)){.bg-black\/45{background-color:color-mix(in oklab, var(--color-black) 45%, transparent)}}.bg-card{background-color:var(--card)}.bg-destructive{background-color:var(--destructive)}.bg-emerald-500\/15{background-color:#00bb7f26}@supports (color:color-mix(in lab, red, red)){.bg-emerald-500\/15{background-color:color-mix(in oklab, var(--color-emerald-500) 15%, transparent)}}.bg-primary{background-color:var(--primary)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-500\/5{background-color:#fb2c360d}@supports (color:color-mix(in lab, red, red)){.bg-red-500\/5{background-color:color-mix(in oklab, var(--color-red-500) 5%, transparent)}}.bg-red-500\/15{background-color:#fb2c3626}@supports (color:color-mix(in lab, red, red)){.bg-red-500\/15{background-color:color-mix(in oklab, var(--color-red-500) 15%, transparent)}}.bg-secondary,.bg-secondary\/30{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.bg-secondary\/30{background-color:color-mix(in oklab, var(--secondary) 30%, transparent)}}.bg-secondary\/35{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.bg-secondary\/35{background-color:color-mix(in oklab, var(--secondary) 35%, transparent)}}.bg-secondary\/45{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.bg-secondary\/45{background-color:color-mix(in oklab, var(--secondary) 45%, transparent)}}.bg-teal-500\/15{background-color:#00baa726}@supports (color:color-mix(in lab, red, red)){.bg-teal-500\/15{background-color:color-mix(in oklab, var(--color-teal-500) 15%, transparent)}}.p-0{padding:calc(var(--spacing) * 0)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-7{padding-block:calc(var(--spacing) * 7)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-\[inherit\]{font-family:inherit}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[11px\]{font-size:11px}.text-\[15px\]{font-size:15px}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent-foreground{color:var(--accent-foreground)}.text-amber-200{color:var(--color-amber-200)}.text-amber-300{color:var(--color-amber-300)}.text-card-foreground{color:var(--card-foreground)}.text-destructive{color:var(--destructive)}.text-emerald-300{color:var(--color-emerald-300)}.text-foreground{color:var(--foreground)}.text-inherit{color:inherit}.text-muted-foreground{color:var(--muted-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-red-100{color:var(--color-red-100)}.text-red-300{color:var(--color-red-300)}.text-red-700{color:var(--color-red-700)}.text-secondary-foreground{color:var(--secondary-foreground)}.text-teal-200{color:var(--color-teal-200)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.underline-offset-4{text-underline-offset:4px}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-\[0_18px_44px_rgb\(0_0_0\/0\.22\)\]{--tw-shadow:0 18px 44px var(--tw-shadow-color,#00000038);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-primary{--tw-ring-color:var(--primary)}.ring-offset-2{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-background{--tw-ring-offset-color:var(--background)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.running{animation-play-state:running}.placeholder\:text-muted-foreground::placeholder{color:var(--muted-foreground)}@media (hover:hover){.hover\:border-border:hover{border-color:var(--border)}.hover\:border-primary:hover{border-color:var(--primary)}.hover\:bg-accent:hover{background-color:var(--accent)}.hover\:bg-destructive\/90:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab, var(--destructive) 90%, transparent)}}.hover\:bg-muted\/45:hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/45:hover{background-color:color-mix(in oklab, var(--muted) 45%, transparent)}}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab, var(--primary) 90%, transparent)}}.hover\:bg-secondary:hover,.hover\:bg-secondary\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab, var(--secondary) 80%, transparent)}}.hover\:text-accent-foreground:hover{color:var(--accent-foreground)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:bg-secondary:focus{background-color:var(--secondary)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width>=48rem){.md\:sticky{position:sticky}.md\:top-24{top:calc(var(--spacing) * 24)}.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:col-span-5{grid-column:span 5/span 5}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:grid-cols-\[7rem_minmax\(0\,1fr\)\]{grid-template-columns:7rem minmax(0,1fr)}.md\:grid-cols-\[190px_minmax\(0\,1fr\)\]{grid-template-columns:190px minmax(0,1fr)}.md\:grid-cols-\[auto_minmax\(0\,1fr\)_auto\]{grid-template-columns:auto minmax(0,1fr) auto}.md\:grid-cols-\[auto_minmax\(150px\,220px\)_minmax\(150px\,220px\)_auto\]{grid-template-columns:auto minmax(150px,220px) minmax(150px,220px) auto}.md\:grid-cols-\[minmax\(0\,1fr\)_180px_auto_auto_auto\]{grid-template-columns:minmax(0,1fr) 180px auto auto auto}.md\:grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.md\:grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,1fr\)_minmax\(130px\,\.6fr\)\]{grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(130px,.6fr)}.md\:grid-cols-\[minmax\(0\,1fr\)_minmax\(130px\,\.6fr\)\]{grid-template-columns:minmax(0,1fr) minmax(130px,.6fr)}.md\:grid-cols-\[minmax\(120px\,\.7fr\)_minmax\(150px\,1fr\)_minmax\(150px\,1fr\)_auto\]{grid-template-columns:minmax(120px,.7fr) minmax(150px,1fr) minmax(150px,1fr) auto}.md\:grid-cols-\[minmax\(140px\,\.8fr\)_minmax\(150px\,1fr\)_minmax\(150px\,1fr\)_minmax\(120px\,\.7fr\)_auto\]{grid-template-columns:minmax(140px,.8fr) minmax(150px,1fr) minmax(150px,1fr) minmax(120px,.7fr) auto}.md\:grid-cols-\[minmax\(140px\,\.75fr\)_minmax\(220px\,1\.25fr\)_auto_auto_auto\]{grid-template-columns:minmax(140px,.75fr) minmax(220px,1.25fr) auto auto auto}.md\:grid-cols-\[minmax\(220px\,1fr\)_auto\]{grid-template-columns:minmax(220px,1fr) auto}.md\:grid-cols-\[minmax\(260px\,1fr\)_minmax\(180px\,\.7fr\)_minmax\(130px\,\.45fr\)_minmax\(130px\,\.45fr\)_auto_auto\]{grid-template-columns:minmax(260px,1fr) minmax(180px,.7fr) minmax(130px,.45fr) minmax(130px,.45fr) auto auto}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:items-end{align-items:flex-end}.md\:items-start{align-items:flex-start}.md\:justify-between{justify-content:space-between}.md\:justify-end{justify-content:flex-end}}@media (width>=64rem){.lg\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-\[1fr_1fr_1fr\]{grid-template-columns:1fr 1fr 1fr}.lg\:grid-cols-\[minmax\(0\,1fr\)_220px_180px\]{grid-template-columns:minmax(0,1fr) 220px 180px}.lg\:items-start{align-items:flex-start}}.dark\:bg-amber-500\/35:is(.dark *){background-color:#f99c0059}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-500\/35:is(.dark *){background-color:color-mix(in oklab, var(--color-amber-500) 35%, transparent)}}.dark\:text-red-300:is(.dark *){color:var(--color-red-300)}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--radius:.5rem;--background:oklch(16% .006 160);--foreground:oklch(94% .008 125);--card:oklch(22% .007 155);--card-foreground:oklch(94% .008 125);--popover:oklch(22% .007 155);--popover-foreground:oklch(94% .008 125);--primary:oklch(73% .11 178);--primary-foreground:oklch(17% .018 178);--secondary:oklch(28% .012 155);--secondary-foreground:oklch(94% .008 125);--muted:oklch(28% .012 155);--muted-foreground:oklch(71% .016 135);--accent:oklch(30% .042 178);--accent-foreground:oklch(88% .064 178);--destructive:oklch(67% .18 23);--border:oklch(100% 0 0/.14);--input:oklch(100% 0 0/.16);--ring:oklch(73% .11 178)}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false} diff --git a/apps/api/src/five08/backend/static/dashboard/index.html b/apps/api/src/five08/backend/static/dashboard/index.html index d1c55b83..4613f8f8 100644 --- a/apps/api/src/five08/backend/static/dashboard/index.html +++ b/apps/api/src/five08/backend/static/dashboard/index.html @@ -4,8 +4,8 @@ 508 Operations Dashboard - - + +
diff --git a/apps/discord_bot/README.md b/apps/discord_bot/README.md index 0e1284ec..c2e4aaa1 100644 --- a/apps/discord_bot/README.md +++ b/apps/discord_bot/README.md @@ -150,6 +150,7 @@ Relevant configuration: - `/create-mailbox` - Description: Create a Migadu mailbox for a 508 user, optionally link it to a CRM contact, and sync `c508Email`. - Prerequisites: `MIGADU_API_USER` and `MIGADU_API_KEY` must be configured (configured in env; command will fail if missing). + - Newsletter sync: if Brevo and/or Keila are configured, the new 508 mailbox and backup email are added to the configured 508 members audience. Brevo uses `BREVO_508_MEMBERS_NEWSLETTER_LIST_ID`, or the list named by `BREVO_508_MEMBERS_NEWSLETTER_LIST_NAME` (default `508 members`) when the explicit ID is unset. Newsletter failures are shown as warnings and do not block mailbox creation. - Required role: Admin - Args: - `mailbox_username` (required): 508 mailbox username or address. If the domain is omitted, `@508.dev` is added automatically. diff --git a/apps/discord_bot/src/five08/discord_bot/cogs/crm.py b/apps/discord_bot/src/five08/discord_bot/cogs/crm.py index 7ddf0d4f..db571713 100644 --- a/apps/discord_bot/src/five08/discord_bot/cogs/crm.py +++ b/apps/discord_bot/src/five08/discord_bot/cogs/crm.py @@ -51,6 +51,11 @@ ResumeProcessorConfig, ResumeProfileProcessor, ) +from five08.newsletter_sync import ( + format_newsletter_sync_warning, + sync_newsletter_contacts, +) +from five08.redaction import redact_email_addresses from five08.skills import normalize_skill, normalize_skill_list from five08.discord_bot.utils.audit import DiscordAuditCogMixin from five08.discord_bot.utils.role_decorators import ( @@ -133,10 +138,12 @@ def __init__( *, mailbox_email: str, partial_success: str, + newsletter_error: str | None = None, ) -> None: super().__init__(message) self.mailbox_email = mailbox_email self.partial_success = partial_success + self.newsletter_error = newsletter_error @dataclass(frozen=True, slots=True) @@ -147,6 +154,7 @@ class MailboxProvisioningResult: created: bool crm_updated: bool backup_email: str + newsletter_error: str | None = None @dataclass(frozen=True, slots=True) @@ -3816,6 +3824,27 @@ def _migadu_client(self) -> MigaduClient: domain=self._migadu_mailbox_domain(), ) + async def _add_emails_to_newsletter(self, emails: list[str]) -> str | None: + """Best-effort subscribe mailbox and backup addresses to newsletter tools.""" + try: + result = await asyncio.to_thread( + sync_newsletter_contacts, + settings, + emails, + source="discord_create_user_accounts", + ) + except Exception as exc: + error_warning = self._sanitize_error_message_for_discord( + redact_email_addresses(f"Newsletter sync failed: {exc}"), + max_length=500, + ) + logger.warning("Newsletter sync warning: %s", error_warning, exc_info=True) + return error_warning + warning = format_newsletter_sync_warning(result) + if warning: + logger.warning("Newsletter sync warning: %s", warning) + return warning + def _normalize_mailbox_request(self, mailbox_username: str) -> tuple[str, str]: """Normalize a bare or full 508 mailbox request to email and local-part.""" normalized = mailbox_username.strip().lower() @@ -8284,6 +8313,12 @@ async def _create_migadu_mailbox_for_contact( created=False, crm_updated=False, backup_email="", + newsletter_error=await self._add_emails_to_newsletter( + [ + existing_email, + self._contact_text_value(contact.get("emailAddress")) or "", + ] + ), ) backup_email = self._normalize_full_email( @@ -8307,6 +8342,10 @@ async def _create_migadu_mailbox_for_contact( partial_success="mailbox_created_address_mismatch", ) + newsletter_error = await self._add_emails_to_newsletter( + [target_email, backup_email] + ) + try: await asyncio.to_thread( self.espo_api.request, @@ -8319,6 +8358,7 @@ async def _create_migadu_mailbox_for_contact( str(exc), mailbox_email=target_email, partial_success="mailbox_created_crm_update_failed", + newsletter_error=newsletter_error, ) from exc contact["c508Email"] = target_email @@ -8327,6 +8367,7 @@ async def _create_migadu_mailbox_for_contact( created=True, crm_updated=True, backup_email=backup_email, + newsletter_error=newsletter_error, ) async def _invite_outline_user_for_contact( @@ -8564,22 +8605,38 @@ async def _create_user_accounts_for_contact( "mailbox_username": mailbox_username, "mailbox_email": exc.mailbox_email, "partial_success": exc.partial_success, + "newsletter_error": exc.newsletter_error, "error": message, }, ) + newsletter_warning = ( + self._sanitize_error_message_for_discord( + exc.newsletter_error, + max_length=500, + ) + if exc.newsletter_error + else None + ) + newsletter_line = ( + f"\nNewsletter: subscription warning: `{newsletter_warning}`" + if newsletter_warning + else "\nNewsletter: added mailbox and backup email." + ) if exc.partial_success == "mailbox_created_crm_update_failed": await interaction.followup.send( "⚠️ Created the mailbox, but failed to update CRM `c508Email`.\n" f"Email: `{exc.mailbox_email}`\n" f"Error: `{message}`\n" - "SSO provisioning and Outline invite were not started.", + "SSO provisioning and Outline invite were not started." + f"{newsletter_line}", ephemeral=True, ) else: await interaction.followup.send( f"⚠️ {message}\n" f"Created mailbox: `{exc.mailbox_email}`\n" - "SSO provisioning and Outline invite were not started.", + "SSO provisioning and Outline invite were not started." + f"{newsletter_line}", ephemeral=True, ) except SSOProvisioningPartialError as exc: @@ -8704,6 +8761,8 @@ async def _create_user_accounts_for_contact( "sso_created": result.sso.created, "sso_crm_updated": result.sso.crm_updated, "outline_invited": result.outline_invited, + "newsletter_subscribed": result.mailbox.newsletter_error is None, + "newsletter_error": result.mailbox.newsletter_error, "recovery_email_error": result.sso.recovery_email_error, }, resource_type="crm_contact", @@ -8720,6 +8779,16 @@ async def _create_user_accounts_for_contact( f"(user ID `{result.sso.user_id}`).", "Outline invite: sent.", ] + if result.mailbox.newsletter_error is None: + message_lines.append("Newsletter: added mailbox and backup email.") + else: + newsletter_warning = self._sanitize_error_message_for_discord( + result.mailbox.newsletter_error, + max_length=500, + ) + message_lines.append( + f"Newsletter: subscription warning: `{newsletter_warning}`" + ) if result.sso.created: if result.sso.recovery_email_error is None: message_lines.append("SSO recovery email: sent.") diff --git a/apps/discord_bot/src/five08/discord_bot/cogs/migadu.py b/apps/discord_bot/src/five08/discord_bot/cogs/migadu.py index 8730683f..0517bfab 100644 --- a/apps/discord_bot/src/five08/discord_bot/cogs/migadu.py +++ b/apps/discord_bot/src/five08/discord_bot/cogs/migadu.py @@ -26,6 +26,11 @@ from five08.discord_bot.config import settings from five08.discord_bot.utils.audit import DiscordAuditCogMixin from five08.discord_bot.utils.role_decorators import require_role +from five08.newsletter_sync import ( + format_newsletter_sync_warning, + sync_newsletter_contacts, +) +from five08.redaction import redact_email_addresses logger = logging.getLogger(__name__) @@ -55,6 +60,7 @@ class MailboxCreationOutcome: mailbox_name: str crm_contact: dict[str, Any] | None sync_error: str | None = None + newsletter_error: str | None = None def _truncate_discord_text(value: str, *, limit: int) -> str: @@ -208,6 +214,27 @@ def _migadu_client(self) -> MigaduClient: domain=self._migadu_mailbox_domain(), ) + async def _add_emails_to_newsletter(self, emails: list[str]) -> str | None: + """Best-effort subscribe mailbox and backup addresses to newsletter tools.""" + try: + result = await asyncio.to_thread( + sync_newsletter_contacts, + settings, + emails, + source="discord_create_mailbox", + ) + except Exception as exc: + error_warning = _truncate_discord_text( + redact_email_addresses(f"Newsletter sync failed: {exc}"), + limit=500, + ) + logger.warning("Newsletter sync warning: %s", error_warning, exc_info=True) + return error_warning + warning = format_newsletter_sync_warning(result) + if warning: + logger.warning("Newsletter sync warning: %s", warning) + return warning + def _normalize_mailbox_request(self, mailbox_username: str) -> tuple[str, str]: """ Normalize user input and derive: @@ -580,6 +607,9 @@ async def _execute_mailbox_creation( name=mailbox_name, ) created_address = str(mailbox.get("address") or context.mailbox_email) + newsletter_error = await self._add_emails_to_newsletter( + [created_address, backup_email] + ) contact_to_update = pre_resolved_contact sync_error: str | None = None @@ -617,6 +647,7 @@ async def _execute_mailbox_creation( mailbox_name=mailbox_name, crm_contact=contact_to_update, sync_error=sync_error, + newsletter_error=newsletter_error, ) def _build_mailbox_embed( @@ -650,6 +681,21 @@ def _build_mailbox_embed( value=outcome.sync_error, inline=False, ) + if outcome.newsletter_error: + embed.add_field( + name="Newsletter", + value=_truncate_discord_text( + f"Newsletter subscription warning: {outcome.newsletter_error}", + limit=1024, + ), + inline=False, + ) + else: + embed.add_field( + name="Newsletter", + value="Added mailbox and backup email to newsletter tools.", + inline=False, + ) return embed @@ -754,6 +800,7 @@ async def _handle_mailbox_creation( else None ), "crm_sync_error": outcome.sync_error, + "newsletter_error": outcome.newsletter_error, "mailbox_created": True, }, resource_type="discord_command", @@ -789,6 +836,8 @@ async def _handle_mailbox_creation( else None ), "forwarded_to": outcome.backup_email, + "newsletter_subscribed": outcome.newsletter_error is None, + "newsletter_error": outcome.newsletter_error, }, resource_type="discord_command", ) diff --git a/apps/worker/src/five08/worker/jobs.py b/apps/worker/src/five08/worker/jobs.py index 5d86de0b..3c876683 100644 --- a/apps/worker/src/five08/worker/jobs.py +++ b/apps/worker/src/five08/worker/jobs.py @@ -6,7 +6,12 @@ from email import message_from_bytes from collections.abc import Callable from typing import Any +from urllib.parse import unquote +from five08.redaction import ( + EMAIL_ADDRESS_PATTERN, + PERCENT_ENCODED_EMAIL_ADDRESS_PATTERN, +) from five08.worker.config import settings from five08.worker.crm.docuseal_processor import DocusealAgreementProcessor from five08.worker.crm.intake_form_processor import IntakeFormProcessor @@ -16,6 +21,7 @@ from five08.worker.erpnext_project_sync import ERPNextProjectSyncProcessor from five08.worker.mailbox_resume_ingest import ResumeMailboxProcessor from five08.worker.masking import mask_email +from five08.newsletter_sync import NewsletterSyncProcessor logger = logging.getLogger(__name__) @@ -164,6 +170,52 @@ def sync_projects_from_erpnext_job() -> dict[str, Any]: return processor.sync_open_projects() +def _mask_newsletter_sync_result(result: dict[str, Any]) -> dict[str, Any]: + """Mask email addresses before newsletter sync results are persisted.""" + crm_failures = result.get("crm_lookup_failures") + if isinstance(crm_failures, list): + for failure in crm_failures: + if not isinstance(failure, dict): + continue + if failure.get("mailbox"): + failure["mailbox"] = mask_email(str(failure["mailbox"])) + if failure.get("error"): + failure["error"] = _mask_emails_in_text(str(failure["error"])) + + providers = result.get("providers") + if isinstance(providers, dict): + for provider_result in providers.values(): + if not isinstance(provider_result, dict): + continue + failures = provider_result.get("failures") + if not isinstance(failures, list): + continue + for failure in failures: + if not isinstance(failure, dict): + continue + if failure.get("email"): + failure["email"] = mask_email(str(failure["email"])) + if failure.get("error"): + failure["error"] = _mask_emails_in_text(str(failure["error"])) + return result + + +def _mask_emails_in_text(text: str) -> str: + """Mask email-like substrings embedded in free-form error text.""" + text = PERCENT_ENCODED_EMAIL_ADDRESS_PATTERN.sub( + lambda match: mask_email(unquote(match.group(0))), + text, + ) + return EMAIL_ADDRESS_PATTERN.sub(lambda match: mask_email(match.group(0)), text) + + +def sync_508_members_newsletters_job() -> dict[str, Any]: + """Sync Migadu member emails into configured newsletter providers.""" + logger.info("Processing 508 members newsletter sync job") + processor = NewsletterSyncProcessor(settings) + return _mask_newsletter_sync_result(processor.sync_508_members()) + + JOB_FUNCTIONS: dict[str, Callable[..., dict[str, Any]]] = { process_webhook_event.__name__: process_webhook_event, process_contact_skills_job.__name__: process_contact_skills_job, @@ -174,5 +226,6 @@ def sync_projects_from_erpnext_job() -> dict[str, Any]: sync_people_from_crm_job.__name__: sync_people_from_crm_job, sync_person_from_crm_job.__name__: sync_person_from_crm_job, sync_projects_from_erpnext_job.__name__: sync_projects_from_erpnext_job, + sync_508_members_newsletters_job.__name__: sync_508_members_newsletters_job, process_docuseal_agreement_job.__name__: process_docuseal_agreement_job, } diff --git a/apps/worker/src/five08/worker/migrations/versions/20260614_0100_create_newsletter_suppressions.py b/apps/worker/src/five08/worker/migrations/versions/20260614_0100_create_newsletter_suppressions.py new file mode 100644 index 00000000..4bec4b7e --- /dev/null +++ b/apps/worker/src/five08/worker/migrations/versions/20260614_0100_create_newsletter_suppressions.py @@ -0,0 +1,111 @@ +"""Create newsletter suppression registry.""" + +from __future__ import annotations + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +revision = "20260614_0100" +down_revision = "20260613_0200" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + """Create provider-independent newsletter suppression records.""" + op.create_table( + "newsletter_suppressions", + sa.Column("email", sa.Text(), nullable=False), + sa.Column("source_provider", sa.Text(), nullable=False), + sa.Column("reason", sa.Text(), nullable=False), + sa.Column( + "active", sa.Boolean(), nullable=False, server_default=sa.text("true") + ), + sa.Column( + "metadata", + postgresql.JSONB(astext_type=sa.Text()), + nullable=False, + server_default=sa.text("'{}'::jsonb"), + ), + sa.Column( + "first_seen_at", + sa.DateTime(timezone=True), + nullable=False, + server_default=sa.text("NOW()"), + ), + sa.Column( + "last_seen_at", + sa.DateTime(timezone=True), + nullable=False, + server_default=sa.text("NOW()"), + ), + sa.Column( + "created_at", + sa.DateTime(timezone=True), + nullable=False, + server_default=sa.text("NOW()"), + ), + sa.Column( + "updated_at", + sa.DateTime(timezone=True), + nullable=False, + server_default=sa.text("NOW()"), + ), + sa.CheckConstraint( + "source_provider IN ('brevo', 'keila', 'manual')", + name="ck_newsletter_suppressions_source_provider", + ), + sa.PrimaryKeyConstraint( + "email", + "source_provider", + name="pk_newsletter_suppressions_email_source", + ), + ) + op.create_index( + "idx_newsletter_suppressions_active_last_seen", + "newsletter_suppressions", + ["active", "last_seen_at"], + ) + op.create_index( + "idx_newsletter_suppressions_source_active", + "newsletter_suppressions", + ["source_provider", "active"], + ) + op.execute( + """ + CREATE FUNCTION newsletter_suppressions_set_updated_at_fn() + RETURNS TRIGGER AS $$ + BEGIN + NEW.updated_at = NOW(); + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + """ + ) + op.execute( + """ + CREATE TRIGGER newsletter_suppressions_set_updated_at_tr + BEFORE UPDATE ON newsletter_suppressions + FOR EACH ROW + EXECUTE FUNCTION newsletter_suppressions_set_updated_at_fn(); + """ + ) + + +def downgrade() -> None: + """Drop newsletter suppression registry.""" + op.execute( + "DROP TRIGGER IF EXISTS newsletter_suppressions_set_updated_at_tr " + "ON newsletter_suppressions" + ) + op.execute("DROP FUNCTION IF EXISTS newsletter_suppressions_set_updated_at_fn()") + op.drop_index( + "idx_newsletter_suppressions_source_active", + table_name="newsletter_suppressions", + ) + op.drop_index( + "idx_newsletter_suppressions_active_last_seen", + table_name="newsletter_suppressions", + ) + op.drop_table("newsletter_suppressions") diff --git a/docs/configuration.md b/docs/configuration.md index 20a9cce1..f1a6ff66 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -36,6 +36,10 @@ and are intentionally not dashboard-configurable. Onboarding email SMTP settings are dashboard-configurable under the Onboarding category when env overrides are not set. +Newsletter sync settings are normally set from the admin dashboard +configuration page. A non-empty env or `.env` value locks the matching +dashboard field. + ## Queue And Jobs - `REDIS_URL` @@ -208,3 +212,26 @@ Agent gateway: Agent model base URLs must be HTTPS endpoints on allowed provider hosts, except the internal Docker-network Bifrost URL `http://bifrost:8080/openai` is allowed for same-host deployments. + +## Migadu Mailbox And Newsletter Sync + +- `MIGADU_API_USER`, `MIGADU_API_KEY`: required for `/create-mailbox` and `/create-user-accounts`. +- `MIGADU_MAILBOX_DOMAIN`: optional, defaults to `508.dev`. +- `BREVO_API_KEY`: optional for Brevo newsletter sync. +- `BREVO_API_BASE_URL`: optional, defaults to `https://api.brevo.com/v3`. +- `BREVO_API_TIMEOUT_SECONDS`: optional, defaults to `20.0`. +- `BREVO_508_MEMBERS_NEWSLETTER_LIST_ID`: optional explicit Brevo list ID override; use `4` for the 508 members list when setting it directly. +- `BREVO_508_MEMBERS_NEWSLETTER_LIST_NAME`: optional, defaults to `508 members` and is used to look up the list ID when the explicit ID is unset. +- `KEILA_API_KEY`: optional for Keila contact sync. +- `KEILA_API_BASE_URL`: optional, defaults to `https://app.keila.io`. +- `KEILA_API_TIMEOUT_SECONDS`: optional, defaults to `20.0`. +- `NEWSLETTER_SYNC_ENABLED`: optional, defaults to `true`; dashboard changes require an API restart because the scheduler starts at startup. +- `NEWSLETTER_SYNC_INTERVAL_SECONDS`: optional, defaults to `604800`; dashboard changes require an API restart because the scheduler sleep interval is startup-bound. +- `NEWSLETTER_SYNC_EXCLUDED_MAILBOXES`: optional comma-separated mailbox local-parts or full addresses to skip during Migadu resync. + +Mailbox and backup email subscription to configured newsletter tools is best +effort. Failures are reported as warnings and do not block mailbox or account +creation. The periodic sync uses Migadu mailboxes and password recovery emails +as the source of truth for `@508.dev`. When CRM is configured, it only syncs +mailboxes that match a CRM contact; it also skips configured excluded mailboxes +and does not re-add provider-suppressed contacts. diff --git a/packages/shared/src/five08/agent/tools.py b/packages/shared/src/five08/agent/tools.py index 0dc3d2e9..a5bd3ddc 100644 --- a/packages/shared/src/five08/agent/tools.py +++ b/packages/shared/src/five08/agent/tools.py @@ -5,6 +5,7 @@ import itertools import re import threading +from collections.abc import Callable from dataclasses import dataclass, field from datetime import date, datetime, timezone from typing import Any @@ -27,6 +28,11 @@ ) from five08.clients.outline import OutlineClient from five08.crm_contacts import EspoContactRepository +from five08.newsletter_sync import ( + format_newsletter_sync_warning, + sync_newsletter_contacts, +) +from five08.redaction import redact_email_addresses SSO_ID_FIELD = "cSsoID" @@ -75,6 +81,15 @@ class ToolRuntimeConfig: outline_base_url: str = "https://app.getoutline.com" outline_api_key: str | None = None outline_api_timeout_seconds: float = 20.0 + brevo_api_key: str | None = None + brevo_api_base_url: str = "https://api.brevo.com/v3" + brevo_api_timeout_seconds: float = 20.0 + brevo_508_members_newsletter_list_id: int | None = None + brevo_508_members_newsletter_list_name: str = "508 members" + keila_api_key: str | None = None + keila_api_base_url: str = "https://app.keila.io" + keila_api_timeout_seconds: float = 20.0 + postgres_url: str | None = None @classmethod def from_settings(cls, settings: Any) -> "ToolRuntimeConfig": @@ -127,6 +142,31 @@ def from_settings(cls, settings: Any) -> "ToolRuntimeConfig": "outline_api_timeout_seconds", 20.0, ), + brevo_api_key=getattr(settings, "brevo_api_key", None), + brevo_api_base_url=getattr( + settings, + "brevo_api_base_url", + "https://api.brevo.com/v3", + ), + brevo_api_timeout_seconds=getattr( + settings, + "brevo_api_timeout_seconds", + 20.0, + ), + brevo_508_members_newsletter_list_id=getattr( + settings, "brevo_508_members_newsletter_list_id", None + ), + brevo_508_members_newsletter_list_name=getattr( + settings, "brevo_508_members_newsletter_list_name", "508 members" + ), + keila_api_key=getattr(settings, "keila_api_key", None), + keila_api_base_url=getattr( + settings, "keila_api_base_url", "https://app.keila.io" + ), + keila_api_timeout_seconds=getattr( + settings, "keila_api_timeout_seconds", 20.0 + ), + postgres_url=getattr(settings, "postgres_url", None), ) @@ -272,10 +312,12 @@ def __init__( *, memory_store: MemoryStore | None = None, runtime_config: ToolRuntimeConfig | None = None, + runtime_config_factory: Callable[[], ToolRuntimeConfig] | None = None, ) -> None: self.task_store = task_store or InMemoryTaskStore() self.memory_store = memory_store or InMemoryMemoryStore() - self.runtime_config = runtime_config or ToolRuntimeConfig() + self._runtime_config = runtime_config or ToolRuntimeConfig() + self._runtime_config_factory = runtime_config_factory self._manifests = { "task_read.search_tasks": ToolManifest( name="task_read.search_tasks", @@ -349,7 +391,7 @@ def __init__( "mail_write.create_mailbox": ToolManifest( name="mail_write.create_mailbox", risk="high", - required_scopes=("mailbox:create",), + required_scopes=("mailbox:create", "integration:manage"), requires_confirmation=True, tenant_scoped=True, idempotent=False, @@ -436,6 +478,13 @@ def __init__( ), } + @property + def runtime_config(self) -> ToolRuntimeConfig: + """Return the current external-tool runtime config.""" + if self._runtime_config_factory is not None: + return self._runtime_config_factory() + return self._runtime_config + def get(self, tool_name: str) -> ToolManifest | None: return self._manifests.get(tool_name) @@ -1389,7 +1438,30 @@ def _migadu_client(self) -> MigaduClient: ), ) - def _create_migadu_mailbox(self, arguments: dict[str, Any]) -> dict[str, Any]: + def _add_emails_to_newsletter(self, emails: list[str]) -> str | None: + try: + result = sync_newsletter_contacts( + self.runtime_config, + emails, + source="agent_account_creation", + ) + except Exception as exc: + text = " ".join( + redact_email_addresses(f"Newsletter sync failed: {exc}").split() + ).strip() + return f"{text[:197]}..." if len(text) > 200 else text + warning = format_newsletter_sync_warning(result) + if not warning: + return None + text = " ".join(warning.split()).strip() + return f"{text[:197]}..." if len(text) > 200 else text + + def _create_migadu_mailbox( + self, + arguments: dict[str, Any], + *, + subscribe_newsletter: bool = True, + ) -> dict[str, Any]: local_part = str(arguments.get("local_part") or "").strip().lower() backup_email = _normalize_full_email( arguments.get("backup_email"), @@ -1398,13 +1470,25 @@ def _create_migadu_mailbox(self, arguments: dict[str, Any]) -> dict[str, Any]: name = str(arguments.get("name") or "").strip() if not local_part or not backup_email or not name: raise ValueError("Mailbox local_part, backup_email, and name are required") - return self._migadu_client().create_mailbox( + mailbox = self._migadu_client().create_mailbox( MigaduMailboxCreateRequest( local_part=local_part, backup_email=backup_email, name=name, ) ) + if subscribe_newsletter: + mailbox_email = str(mailbox.get("address") or "").strip().lower() + if not mailbox_email: + domain = normalize_migadu_mailbox_domain( + self.runtime_config.migadu_mailbox_domain + ) + mailbox_email = f"{local_part}@{domain}" + mailbox["newsletter_error"] = self._add_emails_to_newsletter( + [mailbox_email, backup_email] + ) + mailbox["newsletter_subscribed"] = mailbox["newsletter_error"] is None + return mailbox def _create_migadu_mailbox_for_contact( self, @@ -1427,6 +1511,9 @@ def _create_migadu_mailbox_for_contact( created=False, crm_updated=False, backup_email="", + newsletter_error=self._add_emails_to_newsletter( + [existing_email, _optional_str(contact.get("emailAddress")) or ""] + ), ) backup_email = _normalize_full_email( @@ -1439,7 +1526,8 @@ def _create_migadu_mailbox_for_contact( "local_part": local_part, "backup_email": backup_email, "name": contact_name, - } + }, + subscribe_newsletter=False, ) created_address = str(mailbox.get("address") or target_email).strip().lower() if created_address != target_email: @@ -1457,6 +1545,8 @@ def _create_migadu_mailbox_for_contact( ) raise ToolPartialSuccessError(message, result) + newsletter_error = self._add_emails_to_newsletter([target_email, backup_email]) + try: self._espo_client().update_contact(contact_id, {"c508Email": target_email}) except EspoAPIError as exc: @@ -1467,6 +1557,7 @@ def _create_migadu_mailbox_for_contact( backup_email=backup_email, partial_success="mailbox_created_crm_update_failed", error=_short_error(exc), + newsletter_error=newsletter_error, ) raise ToolPartialSuccessError( "Mailbox was created, but updating CRM c508Email failed.", @@ -1478,6 +1569,7 @@ def _create_migadu_mailbox_for_contact( created=True, crm_updated=True, backup_email=backup_email, + newsletter_error=newsletter_error, ) @staticmethod @@ -1489,12 +1581,15 @@ def _mailbox_result( backup_email: str, partial_success: str | None = None, error: str | None = None, + newsletter_error: str | None = None, ) -> dict[str, Any]: result: dict[str, Any] = { "email": email, "created": created, "crm_updated": crm_updated, "backup_email": backup_email, + "newsletter_subscribed": newsletter_error is None, + "newsletter_error": newsletter_error, } if partial_success is not None: result["partial_success"] = partial_success diff --git a/packages/shared/src/five08/clients/brevo.py b/packages/shared/src/five08/clients/brevo.py new file mode 100644 index 00000000..71eecdab --- /dev/null +++ b/packages/shared/src/five08/clients/brevo.py @@ -0,0 +1,194 @@ +"""Brevo API client helpers shared across services.""" + +from __future__ import annotations + +from typing import Any +from urllib.parse import quote + +import requests + +from five08.clients.contact_email import normalize_provider_contact_email +from five08.redaction import redact_email_addresses + +BREVO_API_BASE_URL = "https://api.brevo.com/v3" +ERROR_BODY_MAX_LENGTH = 500 + + +class BrevoAPIError(RuntimeError): + """Raised when the Brevo API request fails or returns invalid data.""" + + +def _response_body_excerpt(body: object) -> str: + """Return a bounded response-body excerpt for persisted/logged errors.""" + text = redact_email_addresses(" ".join(str(body or "").split())) + if len(text) <= ERROR_BODY_MAX_LENGTH: + return text + return f"{text[:ERROR_BODY_MAX_LENGTH]}..." + + +class BrevoClient: + """Small Brevo API wrapper for newsletter contact subscriptions.""" + + def __init__( + self, + *, + api_key: str, + base_url: str = BREVO_API_BASE_URL, + timeout_seconds: float = 20.0, + ) -> None: + self.api_key = api_key + self.base_url = base_url.rstrip("/") + self.timeout_seconds = timeout_seconds + + def add_contact_to_list( + self, + *, + email: str, + list_id: int, + ) -> dict[str, Any]: + """Create or update one Brevo contact and add it to a list.""" + normalized_email = normalize_provider_contact_email(email, "Brevo") + if list_id <= 0: + raise ValueError("Brevo list ID must be a positive integer.") + + payload = { + "email": normalized_email, + "listIds": [list_id], + "updateEnabled": True, + } + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + "api-key": self.api_key, + } + + try: + response = requests.post( + f"{self.base_url}/contacts", + headers=headers, + json=payload, + timeout=self.timeout_seconds, + ) + except requests.RequestException as exc: + raise BrevoAPIError(f"Brevo API request failed: {exc}") from exc + + if response.status_code not in {200, 201, 204}: + raise BrevoAPIError( + "Brevo contact subscription failed: " + f"status={response.status_code}, " + f"body={_response_body_excerpt(response.text)}" + ) + + if not response.content: + return {} + + try: + data = response.json() + except ValueError as exc: + raise BrevoAPIError("Brevo response payload must be valid JSON.") from exc + + if not isinstance(data, dict): + raise BrevoAPIError("Brevo response payload must be a JSON object.") + return data + + def get_contact(self, email: str) -> dict[str, Any] | None: + """Return one Brevo contact by email, or None when it does not exist.""" + normalized_email = normalize_provider_contact_email(email, "Brevo") + headers = { + "Accept": "application/json", + "api-key": self.api_key, + } + try: + response = requests.get( + f"{self.base_url}/contacts/{quote(normalized_email, safe='')}", + headers=headers, + timeout=self.timeout_seconds, + ) + except requests.RequestException as exc: + raise BrevoAPIError(f"Brevo API request failed: {exc}") from exc + + if response.status_code == 404: + return None + if response.status_code != 200: + raise BrevoAPIError( + "Brevo contact lookup failed: " + f"status={response.status_code}, " + f"body={_response_body_excerpt(response.text)}" + ) + + try: + data = response.json() + except ValueError as exc: + raise BrevoAPIError("Brevo response payload must be valid JSON.") from exc + + if not isinstance(data, dict): + raise BrevoAPIError("Brevo response payload must be a JSON object.") + return data + + def find_list_id_by_name(self, name: str) -> int | None: + """Find a Brevo contact list ID by exact case-insensitive name.""" + normalized_name = name.strip().casefold() + if not normalized_name: + raise ValueError("Brevo list name must be non-empty.") + + limit = 50 + offset = 0 + while True: + payload = self._get_lists_page(limit=limit, offset=offset) + lists = payload.get("lists", []) + if not isinstance(lists, list): + raise BrevoAPIError("Brevo lists payload must include a list array.") + + for item in lists: + if not isinstance(item, dict): + continue + item_name = str(item.get("name") or "").strip().casefold() + if item_name != normalized_name: + continue + list_id = item.get("id") + if not isinstance(list_id, int): + raise BrevoAPIError("Brevo list ID must be an integer.") + return list_id + + count = payload.get("count") + offset += limit + if isinstance(count, int) and offset >= count: + return None + if len(lists) < limit: + return None + + def _get_lists_page(self, *, limit: int, offset: int) -> dict[str, Any]: + headers = { + "Accept": "application/json", + "api-key": self.api_key, + } + params: dict[str, str | int] = { + "limit": limit, + "offset": offset, + "sort": "asc", + } + try: + response = requests.get( + f"{self.base_url}/contacts/lists", + headers=headers, + params=params, + timeout=self.timeout_seconds, + ) + except requests.RequestException as exc: + raise BrevoAPIError(f"Brevo API request failed: {exc}") from exc + + if response.status_code != 200: + raise BrevoAPIError( + "Brevo list lookup failed: " + f"status={response.status_code}, " + f"body={_response_body_excerpt(response.text)}" + ) + + try: + data = response.json() + except ValueError as exc: + raise BrevoAPIError("Brevo response payload must be valid JSON.") from exc + + if not isinstance(data, dict): + raise BrevoAPIError("Brevo response payload must be a JSON object.") + return data diff --git a/packages/shared/src/five08/clients/contact_email.py b/packages/shared/src/five08/clients/contact_email.py new file mode 100644 index 00000000..1c064594 --- /dev/null +++ b/packages/shared/src/five08/clients/contact_email.py @@ -0,0 +1,15 @@ +"""Shared contact email validation for provider clients.""" + +from __future__ import annotations + +from five08.onboarding_email import validate_plain_email + + +def normalize_provider_contact_email(value: str, provider_name: str) -> str: + """Normalize a provider contact email after full-address validation.""" + try: + return validate_plain_email(value, "contact email").lower() + except ValueError as exc: + raise ValueError( + f"{provider_name} contact email must be a full email address." + ) from exc diff --git a/packages/shared/src/five08/clients/keila.py b/packages/shared/src/five08/clients/keila.py new file mode 100644 index 00000000..72684c84 --- /dev/null +++ b/packages/shared/src/five08/clients/keila.py @@ -0,0 +1,175 @@ +"""Keila API client helpers shared across services.""" + +from __future__ import annotations + +from typing import Any +from urllib.parse import quote + +import requests + +from five08.clients.contact_email import normalize_provider_contact_email +from five08.redaction import redact_email_addresses + +KEILA_API_BASE_URL = "https://app.keila.io" +ERROR_BODY_MAX_LENGTH = 500 +_EXISTING_CONTACT_UNSET = object() + + +class KeilaAPIError(RuntimeError): + """Raised when the Keila API request fails or returns invalid data.""" + + +def _response_body_excerpt(body: object) -> str: + """Return a bounded response-body excerpt for persisted/logged errors.""" + text = redact_email_addresses(" ".join(str(body or "").split())) + if len(text) <= ERROR_BODY_MAX_LENGTH: + return text + return f"{text[:ERROR_BODY_MAX_LENGTH]}..." + + +class KeilaClient: + """Small Keila API wrapper for contact synchronization.""" + + def __init__( + self, + *, + api_key: str, + base_url: str = KEILA_API_BASE_URL, + timeout_seconds: float = 20.0, + ) -> None: + self.api_key = api_key + self.base_url = base_url.rstrip("/") + self.timeout_seconds = timeout_seconds + + def get_contact_by_email(self, email: str) -> dict[str, Any] | None: + """Return one Keila contact by email, or None when it does not exist.""" + normalized_email = normalize_provider_contact_email(email, "Keila") + response = self._request( + "GET", + f"/api/v1/contacts/{quote(normalized_email, safe='')}", + params={"id_type": "email"}, + allow_not_found=True, + ) + return response + + def upsert_active_contact( + self, + *, + email: str, + first_name: str | None = None, + last_name: str | None = None, + data: dict[str, Any] | None = None, + existing_contact: dict[str, Any] | None | object = _EXISTING_CONTACT_UNSET, + ) -> dict[str, Any]: + """Create or update a Keila contact without changing suppressed statuses.""" + normalized_email = normalize_provider_contact_email(email, "Keila") + + payload: dict[str, Any] = { + "email": normalized_email, + "status": "active", + "data": data or {}, + } + if first_name: + payload["first_name"] = first_name + if last_name: + payload["last_name"] = last_name + + if existing_contact is _EXISTING_CONTACT_UNSET: + existing = self.get_contact_by_email(normalized_email) + else: + if existing_contact is not None and not isinstance(existing_contact, dict): + raise TypeError("existing_contact must be a Keila contact object.") + existing = existing_contact + if existing is None: + return ( + self._request("POST", "/api/v1/contacts", json={"data": payload}) or {} + ) + + contact_id_value = existing.get("id") + if not contact_id_value: + raise KeilaAPIError( + f"Existing Keila contact {normalized_email} missing id." + ) + contact_id = str(contact_id_value) + existing_data = existing.get("data") + payload["data"] = _merge_contact_data(existing_data, data or {}) + payload.pop("status", None) + return ( + self._request( + "PATCH", + f"/api/v1/contacts/{contact_id}", + json={"data": payload}, + ) + or {} + ) + + def _request( + self, + method: str, + path: str, + *, + params: dict[str, str] | None = None, + json: dict[str, Any] | None = None, + allow_not_found: bool = False, + ) -> dict[str, Any] | None: + headers = { + "Accept": "application/json", + "Authorization": f"Bearer {self.api_key}", + } + if json is not None: + headers["Content-Type"] = "application/json" + + try: + response = requests.request( + method, + f"{self.base_url}{path}", + headers=headers, + params=params, + json=json, + timeout=self.timeout_seconds, + ) + except requests.RequestException as exc: + raise KeilaAPIError(f"Keila API request failed: {exc}") from exc + + if allow_not_found and response.status_code == 404: + return None + if not 200 <= response.status_code < 300: + raise KeilaAPIError( + "Keila API request failed: " + f"status={response.status_code}, " + f"body={_response_body_excerpt(response.text)}" + ) + if not response.content: + return {} + try: + data = response.json() + except ValueError as exc: + raise KeilaAPIError("Keila response payload must be valid JSON.") from exc + if not isinstance(data, dict): + raise KeilaAPIError("Keila response payload must be a JSON object.") + nested = data.get("data") + if isinstance(nested, dict): + return nested + return data + + +def _merge_contact_data( + existing_data: object, + new_data: dict[str, Any], +) -> dict[str, Any]: + """Merge Keila contact data while preserving existing audience tags.""" + existing = existing_data if isinstance(existing_data, dict) else {} + merged = {**existing, **new_data} + existing_audiences = existing.get("audiences") + new_audiences = new_data.get("audiences") + if isinstance(existing_audiences, list) and isinstance(new_audiences, list): + merged["audiences"] = _unique_values([*existing_audiences, *new_audiences]) + return merged + + +def _unique_values(values: list[Any]) -> list[Any]: + unique: list[Any] = [] + for value in values: + if value not in unique: + unique.append(value) + return unique diff --git a/packages/shared/src/five08/clients/migadu.py b/packages/shared/src/five08/clients/migadu.py index ddf60b27..fdab7c6b 100644 --- a/packages/shared/src/five08/clients/migadu.py +++ b/packages/shared/src/five08/clients/migadu.py @@ -7,13 +7,24 @@ import requests +from five08.redaction import redact_email_addresses + MIGADU_API_BASE_URL = "https://api.migadu.com/v1" +ERROR_BODY_MAX_LENGTH = 500 class MigaduAPIError(RuntimeError): """Raised when the Migadu API request fails or returns invalid data.""" +def _response_body_excerpt(body: object) -> str: + """Return a bounded response-body excerpt for persisted/logged errors.""" + text = redact_email_addresses(" ".join(str(body or "").split())) + if len(text) <= ERROR_BODY_MAX_LENGTH: + return text + return f"{text[:ERROR_BODY_MAX_LENGTH]}..." + + def normalize_migadu_mailbox_domain(domain: str | None) -> str: """Normalize the configured Migadu mailbox domain.""" normalized = (domain or "508.dev").strip().lower().lstrip(".") @@ -31,6 +42,15 @@ class MigaduMailboxCreateRequest: name: str +@dataclass(frozen=True, slots=True) +class MigaduMailbox: + """Mailbox fields needed for member audience sync.""" + + address: str + name: str + password_recovery_email: str | None + + class MigaduClient: """Small Migadu API wrapper for mailbox creation.""" @@ -72,7 +92,8 @@ def create_mailbox(self, request: MigaduMailboxCreateRequest) -> dict[str, Any]: if response.status_code not in {200, 201}: raise MigaduAPIError( "Migadu mailbox creation failed: " - f"status={response.status_code}, body={response.text}" + f"status={response.status_code}, " + f"body={_response_body_excerpt(response.text)}" ) try: @@ -84,3 +105,54 @@ def create_mailbox(self, request: MigaduMailboxCreateRequest) -> dict[str, Any]: raise MigaduAPIError("Migadu response payload must be a JSON object.") return data + + def list_mailboxes(self) -> list[MigaduMailbox]: + """List mailboxes for the configured domain.""" + try: + response = requests.get( + f"{self.base_url}/domains/{self.domain}/mailboxes", + auth=(self.username, self.api_key), + timeout=self.timeout_seconds, + ) + except requests.RequestException as exc: + raise MigaduAPIError(f"Migadu API request failed: {exc}") from exc + + if response.status_code != 200: + raise MigaduAPIError( + "Migadu mailbox listing failed: " + f"status={response.status_code}, " + f"body={_response_body_excerpt(response.text)}" + ) + + try: + data = response.json() + except ValueError as exc: + raise MigaduAPIError("Migadu response payload must be valid JSON.") from exc + + if isinstance(data, list): + raw_mailboxes = data + elif isinstance(data, dict): + raw_mailboxes = data.get("mailboxes", []) + else: + raise MigaduAPIError( + "Migadu response payload must be a JSON object or array." + ) + if not isinstance(raw_mailboxes, list): + raise MigaduAPIError("Migadu response payload must include mailboxes list.") + + mailboxes: list[MigaduMailbox] = [] + for item in raw_mailboxes: + if not isinstance(item, dict): + continue + address = str(item.get("address") or "").strip().lower() + if not address: + continue + recovery = str(item.get("password_recovery_email") or "").strip().lower() + mailboxes.append( + MigaduMailbox( + address=address, + name=str(item.get("name") or "").strip(), + password_recovery_email=recovery or None, + ) + ) + return mailboxes diff --git a/packages/shared/src/five08/newsletter_suppressions.py b/packages/shared/src/five08/newsletter_suppressions.py new file mode 100644 index 00000000..4c67a9b8 --- /dev/null +++ b/packages/shared/src/five08/newsletter_suppressions.py @@ -0,0 +1,186 @@ +"""Internal newsletter suppression registry.""" + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Iterable + +from psycopg.rows import dict_row +from psycopg.types.json import Jsonb + +from five08.queue import get_postgres_connection +from five08.settings import SharedSettings + +NEWSLETTER_SUPPRESSION_SOURCE_PROVIDERS = {"brevo", "keila", "manual"} + + +@dataclass(frozen=True, slots=True) +class NewsletterSuppressionRecord: + """Persisted provider suppression state for one email address.""" + + email: str + source_provider: str + reason: str + active: bool + metadata: dict[str, Any] + first_seen_at: datetime + last_seen_at: datetime + created_at: datetime + updated_at: datetime + + +def _normalize_email(email: str) -> str: + return email.strip().lower() + + +def _as_record(row: dict[str, Any]) -> NewsletterSuppressionRecord: + metadata = row.get("metadata") + return NewsletterSuppressionRecord( + email=str(row["email"]), + source_provider=str(row["source_provider"]), + reason=str(row["reason"]), + active=bool(row["active"]), + metadata=metadata if isinstance(metadata, dict) else {}, + first_seen_at=row["first_seen_at"], + last_seen_at=row["last_seen_at"], + created_at=row["created_at"], + updated_at=row["updated_at"], + ) + + +def upsert_newsletter_suppression( + settings: SharedSettings, + *, + email: str, + source_provider: str, + reason: str, + metadata: dict[str, Any] | None = None, +) -> None: + """Record an active provider suppression observation.""" + normalized_email = _normalize_email(email) + normalized_source = source_provider.strip().lower() + normalized_reason = reason.strip().lower() + if not normalized_email: + raise ValueError("email is required") + if normalized_source not in NEWSLETTER_SUPPRESSION_SOURCE_PROVIDERS: + raise ValueError("unknown newsletter suppression source provider") + if not normalized_reason: + raise ValueError("reason is required") + + with get_postgres_connection(settings) as conn: + with conn.cursor() as cursor: + cursor.execute( + """ + INSERT INTO newsletter_suppressions ( + email, + source_provider, + reason, + active, + metadata, + first_seen_at, + last_seen_at + ) VALUES (%s, %s, %s, true, %s, NOW(), NOW()) + ON CONFLICT (email, source_provider) + DO UPDATE SET + reason = EXCLUDED.reason, + active = true, + metadata = EXCLUDED.metadata, + last_seen_at = NOW() + """, + ( + normalized_email, + normalized_source, + normalized_reason, + Jsonb(metadata or {}), + ), + ) + + +def deactivate_newsletter_suppression( + settings: SharedSettings, + *, + email: str, + source_provider: str, +) -> bool: + """Deactivate one provider suppression row, returning whether it changed.""" + normalized_email = _normalize_email(email) + normalized_source = source_provider.strip().lower() + if not normalized_email: + raise ValueError("email is required") + if normalized_source not in NEWSLETTER_SUPPRESSION_SOURCE_PROVIDERS: + raise ValueError("unknown newsletter suppression source provider") + + with get_postgres_connection(settings) as conn: + with conn.cursor() as cursor: + cursor.execute( + """ + UPDATE newsletter_suppressions + SET active = false + WHERE email = %s + AND source_provider = %s + AND active = true + """, + (normalized_email, normalized_source), + ) + return cursor.rowcount > 0 + + +def load_active_newsletter_suppressions_by_email( + settings: SharedSettings, + emails: Iterable[str], +) -> dict[str, list[NewsletterSuppressionRecord]]: + """Return active suppression records keyed by normalized email.""" + normalized_emails = sorted({_normalize_email(email) for email in emails if email}) + if not normalized_emails: + return {} + + with get_postgres_connection(settings) as conn: + with conn.cursor(row_factory=dict_row) as cursor: + cursor.execute( + """ + SELECT * + FROM newsletter_suppressions + WHERE active = true + AND email = ANY(%s) + ORDER BY email ASC, source_provider ASC + """, + (normalized_emails,), + ) + rows = cursor.fetchall() + + records_by_email: dict[str, list[NewsletterSuppressionRecord]] = {} + for row in rows: + record = _as_record(row) + records_by_email.setdefault(record.email, []).append(record) + return records_by_email + + +def list_newsletter_suppressions( + settings: SharedSettings, + *, + limit: int = 200, + active_only: bool = True, +) -> list[NewsletterSuppressionRecord]: + """List newsletter suppression records for admin visibility.""" + conditions: list[str] = [] + params: list[Any] = [] + if active_only: + conditions.append("active = true") + where_clause = f"WHERE {' AND '.join(conditions)}" if conditions else "" + params.append(limit) + + with get_postgres_connection(settings) as conn: + with conn.cursor(row_factory=dict_row) as cursor: + cursor.execute( + f""" + SELECT * + FROM newsletter_suppressions + {where_clause} + ORDER BY last_seen_at DESC, email ASC, source_provider ASC + LIMIT %s + """, + params, + ) + rows = cursor.fetchall() + return [_as_record(row) for row in rows] diff --git a/packages/shared/src/five08/newsletter_sync.py b/packages/shared/src/five08/newsletter_sync.py new file mode 100644 index 00000000..9560dfb1 --- /dev/null +++ b/packages/shared/src/five08/newsletter_sync.py @@ -0,0 +1,1092 @@ +"""508 member newsletter audience synchronization.""" + +from __future__ import annotations + +from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import dataclass +from threading import Lock +from typing import Any, Iterable, Protocol + +from five08.clients.brevo import BrevoClient +from five08.clients.espo import EspoAPIError, EspoClient +from five08.clients.keila import KeilaClient +from five08.clients.migadu import MigaduClient, MigaduMailbox +from five08.newsletter_suppressions import ( + NewsletterSuppressionRecord, + deactivate_newsletter_suppression, + load_active_newsletter_suppressions_by_email, + upsert_newsletter_suppression, +) +from five08.redaction import redact_email_addresses + +CRM_BLOCKED_TYPES = {"inactive member", "rejected", "blocked"} +CRM_BLOCKED_ONBOARDING_STATES = {"rejected", "waitlist"} +PROVIDER_SUPPRESSED_STATUSES = {"unsubscribed", "blocked"} +CRM_LOOKUP_BATCH_SIZE = 10 +CRM_LOOKUP_BATCH_MAX_SIZE = 200 +DRY_RUN_PROVIDER_PREVIEW_MAX_WORKERS = 8 + + +class CRMContactLookupError(RuntimeError): + """Raised when CRM block-state lookup fails during member sync.""" + + +@dataclass(frozen=True, slots=True) +class NewsletterContact: + """One email address derived from one Migadu mailbox.""" + + email: str + mailbox_email: str + name: str + source: str + + +@dataclass(frozen=True, slots=True) +class NewsletterSuppressionSignal: + """One provider-observed suppression for one email address.""" + + email: str + source_provider: str + reason: str + metadata: dict[str, Any] + + +class NewsletterProvider(Protocol): + """Provider interface for additive newsletter subscription sync.""" + + name: str + + def ensure_contact(self, contact: NewsletterContact) -> str: + """Ensure contact exists, returning a sync status key.""" + + def preview_contact(self, contact: NewsletterContact) -> str: + """Return the sync status key that would be produced without writing.""" + + def get_suppression( + self, contact: NewsletterContact + ) -> NewsletterSuppressionSignal | None: + """Return provider suppression state without changing the contact.""" + + +def _split_name(full_name: str) -> tuple[str | None, str | None]: + normalized = full_name.strip() + if not normalized: + return None, None + parts = normalized.rsplit(" ", 1) + if len(parts) == 1: + return parts[0], None + return parts[0], parts[1] + + +def _normalized_emails_for_mailbox(mailbox: MigaduMailbox) -> list[NewsletterContact]: + contacts = [ + NewsletterContact( + email=mailbox.address, + mailbox_email=mailbox.address, + name=mailbox.name, + source="migadu_mailbox", + ) + ] + if mailbox.password_recovery_email: + contacts.append( + NewsletterContact( + email=mailbox.password_recovery_email, + mailbox_email=mailbox.address, + name=mailbox.name, + source="migadu_password_recovery_email", + ) + ) + deduped: list[NewsletterContact] = [] + seen: set[str] = set() + for contact in contacts: + if contact.email in seen: + continue + seen.add(contact.email) + deduped.append(contact) + return deduped + + +def _normalized_csv_set(value: str) -> set[str]: + return {item.strip().lower() for item in value.split(",") if item.strip()} + + +def _mailbox_local_part(email: str) -> str: + return email.split("@", 1)[0].strip().lower() + + +def _is_mailbox_excluded(email: str, excluded_mailboxes: set[str]) -> bool: + normalized_email = email.strip().lower() + if normalized_email in excluded_mailboxes: + return True + local_part = _mailbox_local_part(normalized_email) + return bool(local_part and local_part in excluded_mailboxes) + + +def _is_crm_blocked(contact: dict[str, Any] | None) -> bool: + if contact is None: + return False + contact_type = str(contact.get("type") or "").strip().casefold() + onboarding = str(contact.get("cOnboardingState") or "").strip().casefold() + return ( + contact_type in CRM_BLOCKED_TYPES or onboarding in CRM_BLOCKED_ONBOARDING_STATES + ) + + +def _contains_list_id(value: object, list_id: int) -> bool: + if not isinstance(value, list): + return False + return any(str(item).strip() == str(list_id) for item in value) + + +def _joined_reason(reasons: list[str]) -> str: + return "+".join(sorted(set(reasons))) + + +def _provider_suppression_sources( + suppressions: list[NewsletterSuppressionSignal], +) -> set[str]: + return {suppression.source_provider for suppression in suppressions} + + +def _reconcile_registry_suppressions( + settings: Any, + *, + contact: NewsletterContact, + active_suppressions: list[NewsletterSuppressionRecord], + provider_suppressions: list[NewsletterSuppressionSignal], + checked_provider_names: set[str], + result: dict[str, Any], +) -> list[NewsletterSuppressionRecord]: + """Clear stale provider-observed suppressions after a clean provider check.""" + if not active_suppressions: + return [] + if not bool(str(getattr(settings, "postgres_url", "") or "").strip()): + return active_suppressions + + provider_sources = _provider_suppression_sources(provider_suppressions) + remaining: list[NewsletterSuppressionRecord] = [] + for record in active_suppressions: + if record.source_provider == "manual": + remaining.append(record) + continue + if record.source_provider not in checked_provider_names: + remaining.append(record) + continue + if record.source_provider in provider_sources: + remaining.append(record) + continue + try: + changed = deactivate_newsletter_suppression( + settings, + email=contact.email, + source_provider=record.source_provider, + ) + except Exception as exc: + result["suppression_registry_warning"] = redact_email_addresses(str(exc)) + remaining.append(record) + continue + if changed: + result["suppression_registry_deactivated"] = ( + int(result.get("suppression_registry_deactivated") or 0) + 1 + ) + return remaining + + +def _chunks[T](items: list[T], size: int) -> Iterable[list[T]]: + for index in range(0, len(items), size): + yield items[index : index + size] + + +def _mailbox_crm_lookup_values(mailbox: MigaduMailbox) -> set[str]: + values = {mailbox.address.strip().lower()} + if mailbox.password_recovery_email: + values.add(mailbox.password_recovery_email.strip().lower()) + return {value for value in values if value} + + +def _crm_filters_for_mailboxes(mailboxes: list[MigaduMailbox]) -> list[dict[str, str]]: + filters: list[dict[str, str]] = [] + seen: set[tuple[str, str]] = set() + for mailbox in mailboxes: + for attribute, value in ( + ("c508Email", mailbox.address), + ("emailAddress", mailbox.address), + ): + normalized = value.strip().lower() + key = (attribute, normalized) + if normalized and key not in seen: + filters.append( + {"type": "equals", "attribute": attribute, "value": normalized} + ) + seen.add(key) + if mailbox.password_recovery_email: + normalized = mailbox.password_recovery_email.strip().lower() + key = ("emailAddress", normalized) + if normalized and key not in seen: + filters.append( + { + "type": "equals", + "attribute": "emailAddress", + "value": normalized, + } + ) + seen.add(key) + return filters + + +def _contact_matches_mailbox(contact: dict[str, Any], mailbox: MigaduMailbox) -> bool: + mailbox_values = _mailbox_crm_lookup_values(mailbox) + contact_values = { + str(contact.get("c508Email") or "").strip().lower(), + str(contact.get("emailAddress") or "").strip().lower(), + } + return bool(mailbox_values.intersection(value for value in contact_values if value)) + + +class BrevoNewsletterProvider: + """Brevo implementation for the 508 members newsletter list.""" + + name = "brevo" + + def __init__( + self, + client: BrevoClient, + *, + list_id: int | None, + list_name: str, + ) -> None: + self.client = client + self.list_id = list_id + self.list_name = list_name + self._list_id_lookup_completed = list_id is not None + self._resolved_list_id = list_id + self._list_id_lookup_lock = Lock() + self._contact_lookup_lock = Lock() + self._contact_cache: dict[str, dict[str, Any] | None] = {} + + def _list_id(self) -> int | None: + if not self._list_id_lookup_completed: + with self._list_id_lookup_lock: + if not self._list_id_lookup_completed: + self._resolved_list_id = self.client.find_list_id_by_name( + self.list_name + ) + self._list_id_lookup_completed = True + return self._resolved_list_id + + def ensure_contact(self, contact: NewsletterContact) -> str: + return self._sync_contact(contact, write=True) + + def preview_contact(self, contact: NewsletterContact) -> str: + return self._sync_contact(contact, write=False) + + def get_suppression( + self, contact: NewsletterContact + ) -> NewsletterSuppressionSignal | None: + list_id = self._list_id() + if list_id is None: + return None + existing = self._get_existing_contact(contact.email) + return self._suppression_from_existing(contact, existing, list_id) + + def _get_existing_contact(self, email: str) -> dict[str, Any] | None: + normalized_email = email.strip().lower() + with self._contact_lookup_lock: + if normalized_email in self._contact_cache: + return self._contact_cache[normalized_email] + existing = self.client.get_contact(normalized_email) + with self._contact_lookup_lock: + self._contact_cache[normalized_email] = existing + return existing + + def _suppression_from_existing( + self, + contact: NewsletterContact, + existing: dict[str, Any] | None, + list_id: int, + ) -> NewsletterSuppressionSignal | None: + if existing is None: + return None + reasons: list[str] = [] + if bool(existing.get("emailBlacklisted")): + reasons.append("email_blacklisted") + status = str(existing.get("status") or "").strip().casefold() + if status in PROVIDER_SUPPRESSED_STATUSES: + reasons.append(f"status_{status}") + if _contains_list_id(existing.get("listUnsubscribed"), list_id): + reasons.append("list_unsubscribed") + if not reasons: + return None + return NewsletterSuppressionSignal( + email=contact.email, + source_provider=self.name, + reason=_joined_reason(reasons), + metadata={"list_id": list_id, "reasons": reasons}, + ) + + def _sync_contact(self, contact: NewsletterContact, *, write: bool) -> str: + list_id = self._list_id() + if list_id is None: + return "skipped_list_missing" + + existing = self._get_existing_contact(contact.email) + if self._suppression_from_existing(contact, existing, list_id) is not None: + return "skipped_provider_suppressed" + if not write: + return "would_sync" + self.client.add_contact_to_list(email=contact.email, list_id=list_id) + return "synced" + + +class KeilaNewsletterProvider: + """Keila implementation using project contacts and contact data tags.""" + + name = "keila" + + def __init__(self, client: KeilaClient) -> None: + self.client = client + self._contact_lookup_lock = Lock() + self._contact_cache: dict[str, dict[str, Any] | None] = {} + + def ensure_contact(self, contact: NewsletterContact) -> str: + return self._sync_contact(contact, write=True) + + def preview_contact(self, contact: NewsletterContact) -> str: + return self._sync_contact(contact, write=False) + + def get_suppression( + self, contact: NewsletterContact + ) -> NewsletterSuppressionSignal | None: + existing = self._get_existing_contact(contact.email) + return self._suppression_from_existing(contact, existing) + + def _get_existing_contact(self, email: str) -> dict[str, Any] | None: + normalized_email = email.strip().lower() + with self._contact_lookup_lock: + if normalized_email in self._contact_cache: + return self._contact_cache[normalized_email] + existing = self.client.get_contact_by_email(normalized_email) + with self._contact_lookup_lock: + self._contact_cache[normalized_email] = existing + return existing + + def _suppression_from_existing( + self, + contact: NewsletterContact, + existing: dict[str, Any] | None, + ) -> NewsletterSuppressionSignal | None: + if existing is None: + return None + status = str(existing.get("status") or "").strip().casefold() + if status not in PROVIDER_SUPPRESSED_STATUSES: + return None + return NewsletterSuppressionSignal( + email=contact.email, + source_provider=self.name, + reason=f"status_{status}", + metadata={"status": status}, + ) + + def _sync_contact(self, contact: NewsletterContact, *, write: bool) -> str: + existing = self._get_existing_contact(contact.email) + if self._suppression_from_existing(contact, existing) is not None: + return "skipped_provider_suppressed" + + first_name, last_name = _split_name(contact.name) + if not write: + return "would_sync" + self.client.upsert_active_contact( + email=contact.email, + first_name=first_name, + last_name=last_name, + data={ + "audiences": ["508_members"], + "source": contact.source, + "mailbox_email": contact.mailbox_email, + }, + existing_contact=existing, + ) + return "synced" + + +class NewsletterSyncProcessor: + """Synchronize Migadu member mailboxes into configured newsletter providers.""" + + def __init__(self, settings: Any) -> None: + self.settings = settings + self.excluded_mailboxes = _normalized_csv_set( + settings.newsletter_sync_excluded_mailboxes + ) + + def sync_508_members(self, *, dry_run: bool = False) -> dict[str, Any]: + providers = build_newsletter_providers(self.settings) + synced_key = "would_sync" if dry_run else "synced" + result: dict[str, Any] = { + "mailboxes_scanned": 0, + "system_mailboxes_skipped": 0, + "crm_blocked_skipped": 0, + "crm_unmatched_skipped": 0, + "crm_lookup_failed_skipped": 0, + "contacts_considered": 0, + "suppressed_contacts_skipped": 0, + "suppression_lookup_failed_skipped": 0, + "providers": { + provider.name: {synced_key: 0, "skipped": 0, "failed": 0} + for provider in providers + }, + } + if dry_run: + result["dry_run"] = True + if not providers: + result["warning"] = "no_newsletter_providers_configured" + return result + + crm_lookup_enabled = self._crm_lookup_enabled() + eligible_mailboxes: list[MigaduMailbox] = [] + for mailbox in self._migadu_client().list_mailboxes(): + result["mailboxes_scanned"] += 1 + if _is_mailbox_excluded(mailbox.address, self.excluded_mailboxes): + result["system_mailboxes_skipped"] += 1 + continue + eligible_mailboxes.append(mailbox) + + crm_contacts_by_mailbox, crm_lookup_failures = self._list_crm_contacts_batch( + eligible_mailboxes + ) + sync_contacts: list[NewsletterContact] = [] + for mailbox in eligible_mailboxes: + mailbox_key = mailbox.address.strip().lower() + crm_lookup_error = crm_lookup_failures.get(mailbox_key) + if crm_lookup_error is not None: + result["crm_lookup_failed_skipped"] += 1 + failures = result.setdefault("crm_lookup_failures", []) + if isinstance(failures, list) and len(failures) < 20: + failures.append( + {"mailbox": mailbox.address, "error": str(crm_lookup_error)} + ) + continue + crm_contacts = crm_contacts_by_mailbox.get(mailbox_key, []) + if crm_lookup_enabled and not crm_contacts: + result["crm_unmatched_skipped"] += 1 + continue + if any(_is_crm_blocked(contact) for contact in crm_contacts): + result["crm_blocked_skipped"] += 1 + continue + + for contact in _normalized_emails_for_mailbox(mailbox): + result["contacts_considered"] += 1 + sync_contacts.append(contact) + if dry_run and sync_contacts: + self._preview_provider_contacts( + contacts=sync_contacts, + providers=providers, + result=result, + synced_key=synced_key, + ) + return result + + registry_suppressions = self._load_registry_suppressions(sync_contacts, result) + for contact in sync_contacts: + active_suppressions = list( + registry_suppressions.get(contact.email.strip().lower(), []) + ) + ( + provider_suppressions, + failed_provider_names, + checked_provider_names, + ) = self._provider_suppressions( + contact, + providers, + result, + synced_key, + ) + if failed_provider_names: + result["suppression_lookup_failed_skipped"] += 1 + for provider in providers: + if provider.name in failed_provider_names: + continue + self._apply_provider_status( + result, + provider.name, + contact.email, + synced_key, + status="skipped_suppression_lookup_failed", + ) + continue + self._record_provider_suppressions(provider_suppressions, result) + active_suppressions = _reconcile_registry_suppressions( + self.settings, + contact=contact, + active_suppressions=active_suppressions, + provider_suppressions=provider_suppressions, + checked_provider_names=checked_provider_names, + result=result, + ) + if active_suppressions or provider_suppressions: + result["suppressed_contacts_skipped"] += 1 + status = ( + "skipped_provider_suppressed" + if provider_suppressions + else "skipped_internal_suppression" + ) + for provider in providers: + self._apply_provider_status( + result, + provider.name, + contact.email, + synced_key, + status=status, + ) + continue + for provider in providers: + try: + status = provider.ensure_contact(contact) + except Exception as exc: + self._apply_provider_status( + result, + provider.name, + contact.email, + synced_key, + error=exc, + ) + continue + self._apply_provider_status( + result, + provider.name, + contact.email, + synced_key, + status=status, + ) + return result + + def _suppression_registry_enabled(self) -> bool: + return bool(str(getattr(self.settings, "postgres_url", "") or "").strip()) + + def _load_registry_suppressions( + self, + contacts: list[NewsletterContact], + result: dict[str, Any], + ) -> dict[str, list[NewsletterSuppressionRecord]]: + if not self._suppression_registry_enabled() or not contacts: + return {} + try: + return load_active_newsletter_suppressions_by_email( + self.settings, + [contact.email for contact in contacts], + ) + except Exception as exc: + result["suppression_registry_warning"] = redact_email_addresses(str(exc)) + return {} + + def _provider_suppressions( + self, + contact: NewsletterContact, + providers: list[NewsletterProvider], + result: dict[str, Any], + synced_key: str, + ) -> tuple[list[NewsletterSuppressionSignal], set[str], set[str]]: + suppressions: list[NewsletterSuppressionSignal] = [] + failed_provider_names: set[str] = set() + checked_provider_names: set[str] = set() + for provider in providers: + try: + suppression = provider.get_suppression(contact) + except Exception as exc: + failed_provider_names.add(provider.name) + self._apply_provider_status( + result, + provider.name, + contact.email, + synced_key, + error=exc, + ) + continue + checked_provider_names.add(provider.name) + if suppression is not None: + suppressions.append(suppression) + return suppressions, failed_provider_names, checked_provider_names + + def _record_provider_suppressions( + self, + suppressions: list[NewsletterSuppressionSignal], + result: dict[str, Any], + ) -> None: + if not suppressions or not self._suppression_registry_enabled(): + return + recorded = 0 + failed = 0 + for suppression in suppressions: + try: + upsert_newsletter_suppression( + self.settings, + email=suppression.email, + source_provider=suppression.source_provider, + reason=suppression.reason, + metadata=suppression.metadata, + ) + except Exception as exc: + failed += 1 + result["suppression_registry_warning"] = redact_email_addresses( + str(exc) + ) + continue + recorded += 1 + result["suppression_registry_recorded"] = ( + int(result.get("suppression_registry_recorded") or 0) + recorded + ) + if failed: + result["suppression_registry_failed"] = ( + int(result.get("suppression_registry_failed") or 0) + failed + ) + + def _apply_provider_status( + self, + result: dict[str, Any], + provider_name: str, + email: str, + synced_key: str, + status: str | None = None, + error: Exception | None = None, + ) -> None: + provider_result = result["providers"][provider_name] + if error is not None: + provider_result["failed"] += 1 + failures = provider_result.setdefault("failures", []) + if isinstance(failures, list) and len(failures) < 20: + failures.append({"email": email, "error": str(error)}) + return + if status in {"synced", "would_sync"}: + provider_result[synced_key] += 1 + else: + provider_result["skipped"] += 1 + statuses = provider_result.setdefault("statuses", {}) + if isinstance(statuses, dict) and status is not None: + statuses[status] = int(statuses.get(status, 0)) + 1 + + def _preview_provider_contacts( + self, + *, + contacts: list[NewsletterContact], + providers: list[NewsletterProvider], + result: dict[str, Any], + synced_key: str, + ) -> None: + work_count = len(contacts) * len(providers) + max_workers = max(1, min(DRY_RUN_PROVIDER_PREVIEW_MAX_WORKERS, work_count)) + with ThreadPoolExecutor(max_workers=max_workers) as executor: + futures = { + executor.submit(provider.preview_contact, contact): ( + provider.name, + contact.email, + ) + for contact in contacts + for provider in providers + } + for future in as_completed(futures): + provider_name, email = futures[future] + try: + status = future.result() + except Exception as exc: + self._apply_provider_status( + result, + provider_name, + email, + synced_key, + error=exc, + ) + continue + self._apply_provider_status( + result, + provider_name, + email, + synced_key, + status=status, + ) + + def _migadu_client(self) -> MigaduClient: + return MigaduClient( + username=_required(self.settings.migadu_api_user, "MIGADU_API_USER"), + api_key=_required(self.settings.migadu_api_key, "MIGADU_API_KEY"), + domain=self.settings.migadu_mailbox_domain, + ) + + def _crm_client(self) -> EspoClient | None: + base_url = getattr(self.settings, "espo_base_url", None) + api_key = getattr(self.settings, "espo_api_key", None) + if not base_url or not api_key: + return None + return EspoClient(base_url, api_key) + + def _crm_lookup_enabled(self) -> bool: + return self._crm_client() is not None + + def _list_crm_contacts(self, mailbox: MigaduMailbox) -> list[dict[str, Any]]: + client = self._crm_client() + if client is None: + return [] + return self._list_crm_contacts_for_mailbox(client, mailbox) + + def _list_crm_contacts_for_mailbox( + self, + client: EspoClient, + mailbox: MigaduMailbox, + ) -> list[dict[str, Any]]: + filters: list[dict[str, Any]] = [ + {"type": "equals", "attribute": "c508Email", "value": mailbox.address}, + {"type": "equals", "attribute": "emailAddress", "value": mailbox.address}, + ] + if mailbox.password_recovery_email: + filters.append( + { + "type": "equals", + "attribute": "emailAddress", + "value": mailbox.password_recovery_email, + } + ) + try: + response = client.list_contacts( + { + "where": [{"type": "or", "value": filters}], + "maxSize": 20, + "select": "id,name,emailAddress,c508Email,type,cOnboardingState", + } + ) + except EspoAPIError as exc: + raise CRMContactLookupError( + f"CRM contact lookup failed for {mailbox.address}: {exc}" + ) from exc + contacts = response.get("list", []) + if not isinstance(contacts, list): + return [] + return [contact for contact in contacts if isinstance(contact, dict)] + + def _list_crm_contacts_batch( + self, + mailboxes: list[MigaduMailbox], + ) -> tuple[dict[str, list[dict[str, Any]]], dict[str, CRMContactLookupError]]: + client = self._crm_client() + if client is None or not mailboxes: + return {}, {} + + contacts_by_mailbox: dict[str, list[dict[str, Any]]] = { + mailbox.address.strip().lower(): [] for mailbox in mailboxes + } + failures: dict[str, CRMContactLookupError] = {} + for chunk in _chunks(mailboxes, CRM_LOOKUP_BATCH_SIZE): + try: + contacts = self._list_crm_contacts_for_mailboxes(client, chunk) + except CRMContactLookupError: + for mailbox in chunk: + mailbox_key = mailbox.address.strip().lower() + try: + contacts_by_mailbox[mailbox_key] = ( + self._list_crm_contacts_for_mailbox(client, mailbox) + ) + except CRMContactLookupError as exc: + failures[mailbox_key] = exc + continue + for mailbox in chunk: + mailbox_key = mailbox.address.strip().lower() + contacts_by_mailbox[mailbox_key] = [ + contact + for contact in contacts + if _contact_matches_mailbox(contact, mailbox) + ] + return contacts_by_mailbox, failures + + def _list_crm_contacts_for_mailboxes( + self, + client: EspoClient, + mailboxes: list[MigaduMailbox], + ) -> list[dict[str, Any]]: + filters = _crm_filters_for_mailboxes(mailboxes) + if not filters: + return [] + try: + response = client.list_contacts( + { + "where": [{"type": "or", "value": filters}], + "maxSize": CRM_LOOKUP_BATCH_MAX_SIZE, + "select": "id,name,emailAddress,c508Email,type,cOnboardingState", + } + ) + except EspoAPIError as exc: + raise CRMContactLookupError( + f"CRM contact batch lookup failed: {exc}" + ) from exc + contacts = response.get("list", []) + if not isinstance(contacts, list): + return [] + return [contact for contact in contacts if isinstance(contact, dict)] + + +def _required(value: str | None, name: str) -> str: + normalized = (value or "").strip() + if not normalized: + raise ValueError(f"{name} is required.") + return normalized + + +def build_newsletter_providers(settings: Any) -> list[NewsletterProvider]: + """Build configured newsletter providers from shared-like settings.""" + providers: list[NewsletterProvider] = [] + brevo_api_key = str(getattr(settings, "brevo_api_key", "") or "").strip() + if brevo_api_key: + list_name = ( + str( + getattr( + settings, "brevo_508_members_newsletter_list_name", "508 members" + ) + or "" + ).strip() + or "508 members" + ) + providers.append( + BrevoNewsletterProvider( + BrevoClient( + api_key=brevo_api_key, + base_url=getattr( + settings, "brevo_api_base_url", "https://api.brevo.com/v3" + ), + timeout_seconds=getattr( + settings, "brevo_api_timeout_seconds", 20.0 + ), + ), + list_id=getattr(settings, "brevo_508_members_newsletter_list_id", None), + list_name=list_name, + ) + ) + + keila_api_key = str(getattr(settings, "keila_api_key", "") or "").strip() + if keila_api_key: + providers.append( + KeilaNewsletterProvider( + KeilaClient( + api_key=keila_api_key, + base_url=getattr( + settings, "keila_api_base_url", "https://app.keila.io" + ), + timeout_seconds=getattr( + settings, "keila_api_timeout_seconds", 20.0 + ), + ) + ) + ) + return providers + + +def sync_newsletter_contacts( + settings: Any, + emails: Iterable[str], + *, + name: str = "", + mailbox_email: str | None = None, + source: str = "account_creation", +) -> dict[str, Any]: + """Best-effort additive sync for known member emails at creation time.""" + providers = build_newsletter_providers(settings) + result: dict[str, Any] = { + "contacts_considered": 0, + "suppression_lookup_failed_skipped": 0, + "providers": { + provider.name: {"synced": 0, "skipped": 0, "failed": 0} + for provider in providers + }, + } + if not providers: + result["warning"] = "no_newsletter_providers_configured" + return result + + seen: set[str] = set() + default_mailbox_email = (mailbox_email or "").strip().lower() + contacts: list[NewsletterContact] = [] + for email in emails: + normalized_email = email.strip().lower() + if not normalized_email or normalized_email in seen: + continue + seen.add(normalized_email) + if not default_mailbox_email: + default_mailbox_email = normalized_email + result["contacts_considered"] += 1 + contacts.append( + NewsletterContact( + email=normalized_email, + mailbox_email=default_mailbox_email, + name=name, + source=source, + ) + ) + + registry_suppressions: dict[str, list[NewsletterSuppressionRecord]] = {} + if bool(str(getattr(settings, "postgres_url", "") or "").strip()) and contacts: + try: + registry_suppressions = load_active_newsletter_suppressions_by_email( + settings, + [contact.email for contact in contacts], + ) + except Exception as exc: + result["suppression_registry_warning"] = redact_email_addresses(str(exc)) + + for contact in contacts: + provider_suppressions: list[NewsletterSuppressionSignal] = [] + failed_provider_names: set[str] = set() + checked_provider_names: set[str] = set() + for provider in providers: + provider_result = result["providers"][provider.name] + try: + suppression = provider.get_suppression(contact) + except Exception as exc: + failed_provider_names.add(provider.name) + provider_result["failed"] += 1 + failures = provider_result.setdefault("failures", []) + if isinstance(failures, list) and len(failures) < 20: + failures.append({"email": contact.email, "error": str(exc)}) + continue + checked_provider_names.add(provider.name) + if suppression is not None: + provider_suppressions.append(suppression) + if failed_provider_names: + result["suppression_lookup_failed_skipped"] = ( + int(result.get("suppression_lookup_failed_skipped") or 0) + 1 + ) + for provider in providers: + if provider.name in failed_provider_names: + continue + _apply_direct_provider_status( + result, + provider.name, + contact.email, + status="skipped_suppression_lookup_failed", + ) + continue + if provider_suppressions: + for suppression in provider_suppressions: + if bool(str(getattr(settings, "postgres_url", "") or "").strip()): + try: + upsert_newsletter_suppression( + settings, + email=suppression.email, + source_provider=suppression.source_provider, + reason=suppression.reason, + metadata=suppression.metadata, + ) + except Exception as exc: + result["suppression_registry_warning"] = redact_email_addresses( + str(exc) + ) + + active_suppressions = registry_suppressions.get( + contact.email.strip().lower(), [] + ) + active_suppressions = _reconcile_registry_suppressions( + settings, + contact=contact, + active_suppressions=list(active_suppressions), + provider_suppressions=provider_suppressions, + checked_provider_names=checked_provider_names, + result=result, + ) + if provider_suppressions or active_suppressions: + result["suppressed_contacts_skipped"] = ( + int(result.get("suppressed_contacts_skipped") or 0) + 1 + ) + status = ( + "skipped_provider_suppressed" + if provider_suppressions + else "skipped_internal_suppression" + ) + for provider in providers: + _apply_direct_provider_status( + result, + provider.name, + contact.email, + status=status, + ) + continue + + for provider in providers: + try: + status = provider.ensure_contact(contact) + except Exception as exc: + _apply_direct_provider_status( + result, + provider.name, + contact.email, + error=exc, + ) + continue + _apply_direct_provider_status( + result, + provider.name, + contact.email, + status=status, + ) + return result + + +def _apply_direct_provider_status( + result: dict[str, Any], + provider_name: str, + email: str, + *, + status: str | None = None, + error: Exception | None = None, +) -> None: + provider_result = result["providers"][provider_name] + if error is not None: + provider_result["failed"] += 1 + failures = provider_result.setdefault("failures", []) + if isinstance(failures, list) and len(failures) < 20: + failures.append({"email": email, "error": str(error)}) + return + if status == "synced": + provider_result["synced"] += 1 + else: + provider_result["skipped"] += 1 + statuses = provider_result.setdefault("statuses", {}) + if isinstance(statuses, dict) and status is not None: + statuses[status] = int(statuses.get(status, 0)) + 1 + + +def format_newsletter_sync_warning(result: dict[str, Any]) -> str | None: + """Format direct-provisioning sync failures for user-visible warnings.""" + if result.get("warning") == "no_newsletter_providers_configured": + return "No newsletter providers are configured." + + messages: list[str] = [] + providers = result.get("providers", {}) + if not isinstance(providers, dict): + return None + + for provider_name, provider_result in providers.items(): + if not isinstance(provider_result, dict): + continue + failed = int(provider_result.get("failed") or 0) + failures = provider_result.get("failures") + if failed and isinstance(failures, list) and failures: + detail = "; ".join( + redact_email_addresses(str(item.get("error") or "unknown error")) + for item in failures[:3] + if isinstance(item, dict) + ) + messages.append(f"{provider_name} failed for {failed} contact(s): {detail}") + elif failed: + messages.append(f"{provider_name} failed for {failed} contact(s)") + + statuses = provider_result.get("statuses") + if isinstance(statuses, dict): + if statuses.get("skipped_list_missing"): + messages.append(f"{provider_name} list was not found") + suppressed = int(statuses.get("skipped_provider_suppressed") or 0) + if suppressed: + messages.append( + f"{provider_name} skipped {suppressed} suppressed contact(s)" + ) + lookup_failed = int(statuses.get("skipped_suppression_lookup_failed") or 0) + if lookup_failed: + messages.append( + f"{provider_name} skipped {lookup_failed} contact(s) " + "because suppression lookup failed" + ) + + return "; ".join(messages) if messages else None diff --git a/packages/shared/src/five08/redaction.py b/packages/shared/src/five08/redaction.py new file mode 100644 index 00000000..9cf2d7b8 --- /dev/null +++ b/packages/shared/src/five08/redaction.py @@ -0,0 +1,19 @@ +"""Small redaction helpers for persisted/logged diagnostic strings.""" + +from __future__ import annotations + +import re + +EMAIL_ADDRESS_PATTERN = re.compile( + r"(? str: + """Replace email-like substrings in text with a stable placeholder.""" + text = PERCENT_ENCODED_EMAIL_ADDRESS_PATTERN.sub("[redacted-email]", text) + return EMAIL_ADDRESS_PATTERN.sub("[redacted-email]", text) diff --git a/packages/shared/src/five08/runtime_config.py b/packages/shared/src/five08/runtime_config.py index cc711595..f3a33945 100644 --- a/packages/shared/src/five08/runtime_config.py +++ b/packages/shared/src/five08/runtime_config.py @@ -90,6 +90,135 @@ class RuntimeConfigDBSnapshot: is_secret=True, env_names=("OUTLINE_API_KEY",), ), + RuntimeConfigDefinition( + key="MIGADU_API_USER", + attr="migadu_api_user", + label="Migadu API user", + category="Mailbox", + description="Migadu API username used for mailbox automation and newsletter dry runs.", + env_names=("MIGADU_API_USER", "MIGADU_USER"), + ), + RuntimeConfigDefinition( + key="MIGADU_API_KEY", + attr="migadu_api_key", + label="Migadu API key", + category="Mailbox", + description="Migadu API key used for mailbox automation and newsletter dry runs.", + is_secret=True, + env_names=("MIGADU_API_KEY",), + ), + RuntimeConfigDefinition( + key="MIGADU_MAILBOX_DOMAIN", + attr="migadu_mailbox_domain", + label="Migadu mailbox domain", + category="Mailbox", + description="Migadu domain scanned for 508 member newsletter sync and mailbox creation.", + env_names=("MIGADU_MAILBOX_DOMAIN", "MIGADU_DOMAIN"), + ), + RuntimeConfigDefinition( + key="BREVO_API_KEY", + attr="brevo_api_key", + label="Brevo API key", + category="Newsletter", + description="Brevo API key used to add 508 member contacts to the newsletter list.", + is_secret=True, + env_names=("BREVO_API_KEY",), + ), + RuntimeConfigDefinition( + key="BREVO_API_BASE_URL", + attr="brevo_api_base_url", + label="Brevo API base URL", + category="Newsletter", + description="Brevo API endpoint for newsletter contact sync.", + value_type="url", + env_names=("BREVO_API_BASE_URL",), + ), + RuntimeConfigDefinition( + key="BREVO_API_TIMEOUT_SECONDS", + attr="brevo_api_timeout_seconds", + label="Brevo API timeout seconds", + category="Newsletter", + description="Network timeout for Brevo newsletter requests.", + value_type="float", + env_names=("BREVO_API_TIMEOUT_SECONDS",), + min_value=1, + ), + RuntimeConfigDefinition( + key="BREVO_508_MEMBERS_NEWSLETTER_LIST_ID", + attr="brevo_508_members_newsletter_list_id", + label="Brevo 508 members list ID", + category="Newsletter", + description="Optional explicit Brevo list ID; when unset the list is resolved by name.", + value_type="int", + env_names=("BREVO_508_MEMBERS_NEWSLETTER_LIST_ID",), + min_value=1, + ), + RuntimeConfigDefinition( + key="BREVO_508_MEMBERS_NEWSLETTER_LIST_NAME", + attr="brevo_508_members_newsletter_list_name", + label="Brevo 508 members list name", + category="Newsletter", + description="Brevo list name used when the explicit list ID is unset.", + env_names=("BREVO_508_MEMBERS_NEWSLETTER_LIST_NAME",), + ), + RuntimeConfigDefinition( + key="KEILA_API_KEY", + attr="keila_api_key", + label="Keila API key", + category="Newsletter", + description="Keila API key used to tag 508 member contacts.", + is_secret=True, + env_names=("KEILA_API_KEY",), + ), + RuntimeConfigDefinition( + key="KEILA_API_BASE_URL", + attr="keila_api_base_url", + label="Keila API base URL", + category="Newsletter", + description="Keila API endpoint for newsletter contact sync.", + value_type="url", + env_names=("KEILA_API_BASE_URL", "KEILA_BASE_URL"), + ), + RuntimeConfigDefinition( + key="KEILA_API_TIMEOUT_SECONDS", + attr="keila_api_timeout_seconds", + label="Keila API timeout seconds", + category="Newsletter", + description="Network timeout for Keila newsletter requests.", + value_type="float", + env_names=("KEILA_API_TIMEOUT_SECONDS",), + min_value=1, + ), + RuntimeConfigDefinition( + key="NEWSLETTER_SYNC_ENABLED", + attr="newsletter_sync_enabled", + label="Newsletter sync enabled", + category="Newsletter", + description="Whether the API starts the recurring 508 members newsletter sync scheduler.", + value_type="bool", + env_names=("NEWSLETTER_SYNC_ENABLED",), + restart_required=True, + ), + RuntimeConfigDefinition( + key="NEWSLETTER_SYNC_INTERVAL_SECONDS", + attr="newsletter_sync_interval_seconds", + label="Newsletter sync interval seconds", + category="Newsletter", + description="Seconds between recurring 508 members newsletter sync enqueue attempts.", + value_type="int", + env_names=("NEWSLETTER_SYNC_INTERVAL_SECONDS",), + restart_required=True, + min_value=60, + ), + RuntimeConfigDefinition( + key="NEWSLETTER_SYNC_EXCLUDED_MAILBOXES", + attr="newsletter_sync_excluded_mailboxes", + label="Newsletter excluded mailboxes", + category="Newsletter", + description="Comma-separated Migadu mailbox local-parts or full addresses skipped by the 508 members resync.", + value_type="csv", + env_names=("NEWSLETTER_SYNC_EXCLUDED_MAILBOXES",), + ), RuntimeConfigDefinition( key="DOCUSEAL_BASE_URL", attr="docuseal_base_url", diff --git a/packages/shared/src/five08/settings.py b/packages/shared/src/five08/settings.py index 900b859e..ad61bb30 100644 --- a/packages/shared/src/five08/settings.py +++ b/packages/shared/src/five08/settings.py @@ -66,9 +66,23 @@ class SharedSettings(BaseSettings): erpnext_base_url: str | None = None erpnext_api_key: str | None = None erpnext_api_timeout_seconds: float = 20.0 - migadu_api_user: str | None = None + migadu_api_user: str | None = Field( + default=None, + validation_alias=AliasChoices( + "MIGADU_API_USER", + "MIGADU_USER", + "migadu_api_user", + ), + ) migadu_api_key: str | None = None - migadu_mailbox_domain: str = "508.dev" + migadu_mailbox_domain: str = Field( + default="508.dev", + validation_alias=AliasChoices( + "MIGADU_MAILBOX_DOMAIN", + "MIGADU_DOMAIN", + "migadu_mailbox_domain", + ), + ) authentik_api_base_url: str | None = None authentik_api_token: str | None = None authentik_api_timeout_seconds: float = 20.0 @@ -77,6 +91,24 @@ class SharedSettings(BaseSettings): outline_base_url: str = "https://app.getoutline.com" outline_api_key: str | None = None outline_api_timeout_seconds: float = 20.0 + brevo_api_key: str | None = None + brevo_api_base_url: str = "https://api.brevo.com/v3" + brevo_api_timeout_seconds: float = 20.0 + brevo_508_members_newsletter_list_id: int | None = Field(default=None, ge=1) + brevo_508_members_newsletter_list_name: str = "508 members" + keila_api_key: str | None = None + keila_api_base_url: str = Field( + default="https://app.keila.io", + validation_alias=AliasChoices( + "KEILA_API_BASE_URL", + "KEILA_BASE_URL", + "keila_api_base_url", + ), + ) + keila_api_timeout_seconds: float = 20.0 + newsletter_sync_enabled: bool = True + newsletter_sync_interval_seconds: int = 604800 + newsletter_sync_excluded_mailboxes: str = "" onboarding_email_smtp_server: str | None = Field( default=None, validation_alias=AliasChoices( @@ -181,6 +213,28 @@ def _normalize_docuseal_member_agreement_template_id( ) from exc raise TypeError("DOCUSEAL_MEMBER_AGREEMENT_TEMPLATE_ID must be an integer") + @field_validator("brevo_508_members_newsletter_list_id", mode="before") + @classmethod + def _normalize_brevo_508_members_newsletter_list_id( + cls, + value: object, + ) -> int | None: + if value is None: + return None + if isinstance(value, int): + return value + if isinstance(value, str): + normalized = value.strip() + if not normalized: + return None + try: + return int(normalized) + except ValueError as exc: + raise ValueError( + "BREVO_508_MEMBERS_NEWSLETTER_LIST_ID must be an integer" + ) from exc + raise TypeError("BREVO_508_MEMBERS_NEWSLETTER_LIST_ID must be an integer") + @classmethod def _skip_dotenv(cls) -> bool: if os.getenv("ENVIRONMENT", "").strip().lower() == "test": diff --git a/tests/evals/discord-agent/fixtures/v1/mailbox_create_confirmation_001.json b/tests/evals/discord-agent/fixtures/v1/mailbox_create_confirmation_001.json index 9305cda9..b4b90fd9 100644 --- a/tests/evals/discord-agent/fixtures/v1/mailbox_create_confirmation_001.json +++ b/tests/evals/discord-agent/fixtures/v1/mailbox_create_confirmation_001.json @@ -25,7 +25,7 @@ { "tool_name": "mail_write.create_mailbox", "requires_confirmation": true, - "required_scopes": ["mailbox:create"], + "required_scopes": ["mailbox:create", "integration:manage"], "arguments": { "local_part": "john", "backup_email": "john@gmail.com", diff --git a/tests/unit/test_agent_gateway.py b/tests/unit/test_agent_gateway.py index cbfcd24b..372f7634 100644 --- a/tests/unit/test_agent_gateway.py +++ b/tests/unit/test_agent_gateway.py @@ -1663,6 +1663,7 @@ def test_mailbox_create_uses_explicit_backup_email_not_mailbox_address() -> None assert response.plan is not None action = response.plan.actions[0] assert action.tool_name == "mail_write.create_mailbox" + assert action.required_scopes == ["mailbox:create", "integration:manage"] assert action.arguments == { "local_part": "john", "backup_email": "john@gmail.com", @@ -1866,6 +1867,8 @@ def test_user_accounts_create_is_admin_only() -> None: def _account_runtime_config( *, outline_api_key: str | None = "outline-key", + brevo_api_key: str | None = None, + keila_api_key: str | None = None, ) -> ToolRuntimeConfig: return ToolRuntimeConfig( espo_base_url="https://crm.example", @@ -1876,6 +1879,9 @@ def _account_runtime_config( authentik_api_token="authentik-token", authentik_recovery_email_stage_id="stage-1", outline_api_key=outline_api_key, + brevo_api_key=brevo_api_key, + brevo_508_members_newsletter_list_id=4, + keila_api_key=keila_api_key, ) @@ -2068,15 +2074,87 @@ def invite_user( self.invites.append(invite) return invite + class FakeBrevoClient: + contacts: dict[str, dict[str, Any]] = {} + subscriptions: list[dict[str, Any]] = [] + + def __init__( + self, + *, + api_key: str, + base_url: str = "https://api.brevo.com/v3", + timeout_seconds: float = 20.0, + ) -> None: + self.api_key = api_key + self.base_url = base_url + self.timeout_seconds = timeout_seconds + + def add_contact_to_list(self, *, email: str, list_id: int) -> dict[str, Any]: + self.subscriptions.append({"email": email, "list_id": list_id}) + return {"id": len(self.subscriptions)} + + def get_contact(self, email: str) -> dict[str, Any] | None: + return self.contacts.get(email) + + def find_list_id_by_name(self, name: str) -> int | None: + return 4 if name == "508 members" else None + + class FakeKeilaClient: + contacts: dict[str, dict[str, Any]] = {} + upserts: list[dict[str, Any]] = [] + + def __init__( + self, + *, + api_key: str, + base_url: str = "https://app.keila.io", + timeout_seconds: float = 20.0, + ) -> None: + self.api_key = api_key + self.base_url = base_url + self.timeout_seconds = timeout_seconds + + def get_contact_by_email(self, email: str) -> dict[str, Any] | None: + return self.contacts.get(email) + + def upsert_active_contact( + self, + *, + email: str, + first_name: str | None = None, + last_name: str | None = None, + data: dict[str, Any] | None = None, + existing_contact: dict[str, Any] | None | object = None, + ) -> dict[str, Any]: + self.upserts.append( + { + "email": email, + "first_name": first_name, + "last_name": last_name, + "data": data, + "existing_contact": existing_contact, + } + ) + return {"id": str(len(self.upserts))} + + FakeBrevoClient.contacts = {} + FakeBrevoClient.subscriptions = [] + FakeKeilaClient.contacts = {} + FakeKeilaClient.upserts = [] + monkeypatch.setattr("five08.agent.tools.EspoClient", FakeEspoClient) monkeypatch.setattr("five08.agent.tools.AuthentikClient", FakeAuthentikClient) monkeypatch.setattr("five08.agent.tools.MigaduClient", FakeMigaduClient) monkeypatch.setattr("five08.agent.tools.OutlineClient", FakeOutlineClient) + monkeypatch.setattr("five08.newsletter_sync.BrevoClient", FakeBrevoClient) + monkeypatch.setattr("five08.newsletter_sync.KeilaClient", FakeKeilaClient) return SimpleNamespace( espo=FakeEspoClient, authentik=FakeAuthentikClient, migadu=FakeMigaduClient, outline=FakeOutlineClient, + brevo=FakeBrevoClient, + keila=FakeKeilaClient, events=events, ) @@ -2416,7 +2494,7 @@ def test_mailbox_create_tool_validates_backup_email( }, organization_id="org-1", actor_id="123", - actor_scopes={"mailbox:create"}, + actor_scopes={"mailbox:create", "integration:manage"}, ) assert fakes.migadu.created_mailboxes == [] @@ -2451,6 +2529,162 @@ def test_user_accounts_tool_executes_all_steps( assert ("contact-1", {"cSsoID": "42"}) in fakes.espo.updates +def test_user_accounts_tool_subscribes_mailbox_and_backup_email_to_brevo( + monkeypatch: pytest.MonkeyPatch, +) -> None: + fakes = _install_account_tool_fakes(monkeypatch) + registry = ToolRegistry(runtime_config=_account_runtime_config(brevo_api_key="key")) + + result = registry.execute( + "account_write.create_user_accounts", + {"contact_id": "contact-1", "mailbox_username": "jane@508.dev"}, + organization_id="org-1", + actor_id="123", + actor_scopes={ + "mailbox:create", + "user:manage", + "integration:manage", + "crm:contact:read", + "crm:contact:update", + }, + ) + + assert result["mailbox"]["newsletter_subscribed"] is True + assert result["mailbox"]["newsletter_error"] is None + assert fakes.brevo.subscriptions == [ + {"email": "jane@508.dev", "list_id": 4}, + {"email": "jane@example.com", "list_id": 4}, + ] + + +def test_user_accounts_tool_reads_dynamic_newsletter_runtime_config( + monkeypatch: pytest.MonkeyPatch, +) -> None: + fakes = _install_account_tool_fakes(monkeypatch) + runtime_values = {"brevo_api_key": None} + registry = ToolRegistry( + runtime_config_factory=lambda: _account_runtime_config( + brevo_api_key=runtime_values["brevo_api_key"] + ) + ) + runtime_values["brevo_api_key"] = "key" + + result = registry.execute( + "account_write.create_user_accounts", + {"contact_id": "contact-1", "mailbox_username": "jane@508.dev"}, + organization_id="org-1", + actor_id="123", + actor_scopes={ + "mailbox:create", + "user:manage", + "integration:manage", + "crm:contact:read", + "crm:contact:update", + }, + ) + + assert result["mailbox"]["newsletter_subscribed"] is True + assert fakes.brevo.subscriptions == [ + {"email": "jane@508.dev", "list_id": 4}, + {"email": "jane@example.com", "list_id": 4}, + ] + + +def test_user_accounts_tool_reports_suppressed_newsletter_contact( + monkeypatch: pytest.MonkeyPatch, +) -> None: + fakes = _install_account_tool_fakes(monkeypatch) + fakes.brevo.contacts = {"jane@example.com": {"emailBlacklisted": True}} + registry = ToolRegistry(runtime_config=_account_runtime_config(brevo_api_key="key")) + + result = registry.execute( + "account_write.create_user_accounts", + {"contact_id": "contact-1", "mailbox_username": "jane@508.dev"}, + organization_id="org-1", + actor_id="123", + actor_scopes={ + "mailbox:create", + "user:manage", + "integration:manage", + "crm:contact:read", + "crm:contact:update", + }, + ) + + assert result["mailbox"]["newsletter_subscribed"] is False + assert result["mailbox"]["newsletter_error"] == ( + "brevo skipped 1 suppressed contact(s)" + ) + assert fakes.brevo.subscriptions == [{"email": "jane@508.dev", "list_id": 4}] + + +def test_user_accounts_tool_reports_newsletter_sync_exceptions( + monkeypatch: pytest.MonkeyPatch, +) -> None: + _install_account_tool_fakes(monkeypatch) + + def _raise_newsletter_error(*args: object, **kwargs: object) -> dict[str, object]: + raise RuntimeError("provider exploded for jane@example.com") + + monkeypatch.setattr( + "five08.agent.tools.sync_newsletter_contacts", + _raise_newsletter_error, + ) + registry = ToolRegistry(runtime_config=_account_runtime_config(brevo_api_key="key")) + + result = registry.execute( + "account_write.create_user_accounts", + {"contact_id": "contact-1", "mailbox_username": "jane@508.dev"}, + organization_id="org-1", + actor_id="123", + actor_scopes={ + "mailbox:create", + "user:manage", + "integration:manage", + "crm:contact:read", + "crm:contact:update", + }, + ) + + assert result["mailbox"]["newsletter_subscribed"] is False + assert result["mailbox"]["newsletter_error"] == ( + "Newsletter sync failed: provider exploded for [redacted-email]" + ) + + +def test_user_accounts_tool_subscribes_mailbox_and_backup_email_to_keila( + monkeypatch: pytest.MonkeyPatch, +) -> None: + fakes = _install_account_tool_fakes(monkeypatch) + registry = ToolRegistry(runtime_config=_account_runtime_config(keila_api_key="key")) + + result = registry.execute( + "account_write.create_user_accounts", + {"contact_id": "contact-1", "mailbox_username": "jane@508.dev"}, + organization_id="org-1", + actor_id="123", + actor_scopes={ + "mailbox:create", + "user:manage", + "integration:manage", + "crm:contact:read", + "crm:contact:update", + }, + ) + + assert result["mailbox"]["newsletter_subscribed"] is True + assert result["mailbox"]["newsletter_error"] is None + assert [item["email"] for item in fakes.keila.upserts] == [ + "jane@508.dev", + "jane@example.com", + ] + assert fakes.keila.upserts[0]["data"] == { + "audiences": ["508_members"], + "source": "agent_account_creation", + "mailbox_email": "jane@508.dev", + } + + def test_user_accounts_tool_preflights_before_mailbox_creation( monkeypatch: pytest.MonkeyPatch, ) -> None: diff --git a/tests/unit/test_backend_api.py b/tests/unit/test_backend_api.py index 7e3ca110..9fe4fd89 100644 --- a/tests/unit/test_backend_api.py +++ b/tests/unit/test_backend_api.py @@ -7030,6 +7030,224 @@ def test_dashboard_sync_people_workflows_engineer_is_dry_run( mock_insert.assert_not_called() +def test_dashboard_sync_newsletters_audits_discord_session(client: TestClient) -> None: + session = api.AuthSession( + subject="123456789", + email="admin@508.dev", + display_name="Discord Admin", + groups=["discord_admin"], + is_admin=True, + id_token="id-token-1", + expires_at=4_102_444_800, + actor_provider=api.ActorProvider.DISCORD.value, + crm_contact_id="contact-123", + ) + + with ( + patch( + "five08.backend.api._current_session", + new_callable=AsyncMock, + return_value=("session-1", session), + ), + patch( + "five08.backend.api._enqueue_newsletter_sync_job", + new_callable=AsyncMock, + return_value=Mock(id="job-newsletter-1", created=True), + ), + patch("five08.backend.api.insert_audit_event") as mock_insert, + ): + response = client.post("/dashboard/api/sync/newsletters") + + assert response.status_code == 202 + assert response.json()["job_id"] == "job-newsletter-1" + audit_payload = mock_insert.call_args.args[1] + assert audit_payload.source == api.AuditSource.ADMIN_DASHBOARD + assert audit_payload.action == "newsletter.508_members_sync" + assert audit_payload.result == api.AuditResult.SUCCESS + assert audit_payload.actor_provider == api.ActorProvider.DISCORD + assert audit_payload.actor_subject == "123456789" + assert audit_payload.resource_type == "newsletter_sync" + assert audit_payload.resource_id == "job-newsletter-1" + assert audit_payload.metadata is not None + assert audit_payload.metadata["source"] == "dashboard" + + +@pytest.mark.asyncio +async def test_manual_newsletter_sync_idempotency_keys_are_unique() -> None: + with patch( + "five08.backend.api.enqueue_job", + side_effect=[ + Mock(id="job-newsletter-1", created=True), + Mock(id="job-newsletter-2", created=True), + ], + ) as mock_enqueue: + await api._enqueue_newsletter_sync_job(Mock(), reason="dashboard") + await api._enqueue_newsletter_sync_job(Mock(), reason="dashboard") + + keys = [item.kwargs["idempotency_key"] for item in mock_enqueue.call_args_list] + assert keys[0] != keys[1] + assert all(key.startswith("newsletter-sync:508-members:dashboard:") for key in keys) + + +def test_dashboard_sync_newsletters_workflows_engineer_is_dry_run( + client: TestClient, +) -> None: + session = api.AuthSession( + subject="workflows-1", + email="workflows@508.dev", + display_name="Workflows Engineer", + groups=["Workflows Engineer"], + is_admin=False, + id_token="", + expires_at=4_102_444_800, + actor_provider=api.ActorProvider.DISCORD.value, + ) + + with ( + patch( + "five08.backend.api._current_session", + new_callable=AsyncMock, + return_value=("session-1", session), + ), + patch( + "five08.backend.api._enqueue_newsletter_sync_job", + new_callable=AsyncMock, + ) as mock_enqueue, + patch("five08.backend.api.NewsletterSyncProcessor") as mock_processor_cls, + patch("five08.backend.api.insert_audit_event") as mock_insert, + ): + mock_processor_cls.return_value.sync_508_members.return_value = { + "dry_run": True, + "mailboxes_scanned": 2, + "contacts_considered": 3, + "providers": {"brevo": {"would_sync": 3, "skipped": 0, "failed": 0}}, + "crm_lookup_failures": [ + { + "mailbox": "jane@508.dev", + "error": "CRM failed for jane@example.com", + } + ], + } + response = client.post("/dashboard/api/sync/newsletters") + + assert response.status_code == 200 + assert response.json()["status"] == "dry_run" + assert response.json()["preview"]["providers"]["brevo"]["would_sync"] == 3 + preview_failure = response.json()["preview"]["crm_lookup_failures"][0] + assert preview_failure["mailbox"] == "[redacted-email]" + assert preview_failure["error"] == "CRM failed for [redacted-email]" + assert response.json()["would_enqueue"]["job_type"] == ( + "sync_508_members_newsletters_job" + ) + assert response.json()["would_enqueue"]["idempotency_key_pattern"] == ( + "newsletter-sync:508-members:dashboard::" + ) + mock_processor_cls.return_value.sync_508_members.assert_called_once_with( + dry_run=True + ) + mock_enqueue.assert_not_called() + mock_insert.assert_not_called() + + +def test_dashboard_sync_newsletters_dry_run_hides_exception_details( + client: TestClient, +) -> None: + session = api.AuthSession( + subject="workflows-1", + email="workflows@508.dev", + display_name="Workflows Engineer", + groups=["Workflows Engineer"], + is_admin=False, + id_token="", + expires_at=4_102_444_800, + actor_provider=api.ActorProvider.DISCORD.value, + ) + + with ( + patch( + "five08.backend.api._current_session", + new_callable=AsyncMock, + return_value=("session-1", session), + ), + patch( + "five08.backend.api.NewsletterSyncProcessor", + ) as mock_processor_cls, + ): + mock_processor_cls.return_value.sync_508_members.side_effect = RuntimeError( + "failed for jane@example.com" + ) + response = client.post("/dashboard/api/sync/newsletters") + + assert response.status_code == 502 + assert response.json()["error"] == "newsletter_dry_run_failed" + assert "jane@example.com" not in response.text + + +def test_dashboard_newsletter_status_reports_latest_job_and_suppressions( + client: TestClient, + monkeypatch: pytest.MonkeyPatch, +) -> None: + session = api.AuthSession( + subject="123456789", + email="admin@508.dev", + display_name="Discord Admin", + groups=["discord_admin"], + is_admin=True, + id_token="id-token-1", + expires_at=4_102_444_800, + actor_provider=api.ActorProvider.DISCORD.value, + ) + now = datetime(2026, 6, 26, 12, 0, tzinfo=timezone.utc) + latest_job = Mock( + id="job-newsletter-1", + type="sync_508_members_newsletters_job", + status=Mock(value="succeeded"), + attempts=1, + max_attempts=3, + run_after=None, + locked_at=None, + locked_by=None, + last_error=None, + idempotency_key="newsletter-sync:508-members:dashboard:20260626:test", + created_at=now - timedelta(minutes=1), + updated_at=now, + payload={"result": {"contacts_considered": 12}}, + ) + suppressions = [ + Mock(email="one@example.com"), + Mock(email="one@example.com"), + Mock(email="two@example.com"), + ] + monkeypatch.setattr(api.settings, "newsletter_sync_enabled", True) + monkeypatch.setattr(api.settings, "newsletter_sync_interval_seconds", 604800) + + with ( + patch( + "five08.backend.api._current_session", + new_callable=AsyncMock, + return_value=("session-1", session), + ), + patch("five08.backend.api.list_jobs", return_value=[latest_job]) as mock_jobs, + patch( + "five08.backend.api.list_newsletter_suppressions", + return_value=suppressions, + ) as mock_suppressions, + ): + response = client.get("/dashboard/api/newsletter/status") + + assert response.status_code == 200 + payload = response.json() + assert payload["scheduler_enabled"] is True + assert payload["interval_seconds"] == 604800 + assert payload["active_suppression_count"] == 3 + assert payload["active_suppressed_email_count"] == 2 + assert payload["latest_job"]["job_id"] == "job-newsletter-1" + assert payload["latest_job"]["status"] == "succeeded" + assert payload["latest_job"]["result"] == {"contacts_considered": 12} + assert mock_jobs.call_args.kwargs["job_type"] == "sync_508_members_newsletters_job" + assert mock_suppressions.call_args.kwargs["active_only"] is True + + def test_dashboard_sync_projects_workflows_engineer_is_dry_run( client: TestClient, ) -> None: diff --git a/tests/unit/test_brevo_client.py b/tests/unit/test_brevo_client.py new file mode 100644 index 00000000..36c34279 --- /dev/null +++ b/tests/unit/test_brevo_client.py @@ -0,0 +1,199 @@ +"""Unit tests for the shared Brevo API client.""" + +from unittest.mock import Mock, patch + +import pytest +import requests + +from five08.clients.brevo import BrevoAPIError, BrevoClient + + +def test_add_contact_to_list_posts_create_or_update_payload() -> None: + response = Mock() + response.status_code = 201 + response.content = b'{"id": 21}' + response.json.return_value = {"id": 21} + + with patch("five08.clients.brevo.requests.post", return_value=response) as post: + result = BrevoClient( + api_key="brevo-key", + timeout_seconds=7.0, + ).add_contact_to_list(email="Jane@Example.com", list_id=4) + + post.assert_called_once_with( + "https://api.brevo.com/v3/contacts", + headers={ + "Accept": "application/json", + "Content-Type": "application/json", + "api-key": "brevo-key", + }, + json={ + "email": "jane@example.com", + "listIds": [4], + "updateEnabled": True, + }, + timeout=7.0, + ) + assert result == {"id": 21} + + +def test_add_contact_to_list_accepts_empty_success_body() -> None: + response = Mock() + response.status_code = 204 + response.content = b"" + + with patch("five08.clients.brevo.requests.post", return_value=response): + result = BrevoClient(api_key="brevo-key").add_contact_to_list( + email="jane@example.com", + list_id=4, + ) + + assert result == {} + + +def test_add_contact_to_list_raises_on_request_error() -> None: + with patch( + "five08.clients.brevo.requests.post", + side_effect=requests.Timeout("timed out"), + ): + with pytest.raises(BrevoAPIError, match="request failed"): + BrevoClient(api_key="brevo-key").add_contact_to_list( + email="jane@example.com", + list_id=4, + ) + + +def test_add_contact_to_list_truncates_error_response_body() -> None: + response = Mock() + response.status_code = 400 + response.text = f"email=jane@example.com {'x' * 600}" + + with patch("five08.clients.brevo.requests.post", return_value=response): + with pytest.raises(BrevoAPIError) as exc_info: + BrevoClient(api_key="brevo-key").add_contact_to_list( + email="jane@example.com", + list_id=4, + ) + + message = str(exc_info.value) + assert "jane@example.com" not in message + assert "[redacted-email]" in message + assert len(message) < 600 + + +def test_get_contact_fetches_contact_by_email() -> None: + response = Mock() + response.status_code = 200 + response.json.return_value = {"email": "jane@example.com"} + + with patch("five08.clients.brevo.requests.get", return_value=response) as get: + result = BrevoClient(api_key="brevo-key").get_contact("Jane@Example.com") + + get.assert_called_once_with( + "https://api.brevo.com/v3/contacts/jane%40example.com", + headers={"Accept": "application/json", "api-key": "brevo-key"}, + timeout=20.0, + ) + assert result == {"email": "jane@example.com"} + + +def test_get_contact_url_encodes_plus_in_email() -> None: + response = Mock() + response.status_code = 200 + response.json.return_value = {"email": "jane+tag@example.com"} + + with patch("five08.clients.brevo.requests.get", return_value=response) as get: + result = BrevoClient(api_key="brevo-key").get_contact("Jane+Tag@Example.com") + + get.assert_called_once_with( + "https://api.brevo.com/v3/contacts/jane%2Btag%40example.com", + headers={"Accept": "application/json", "api-key": "brevo-key"}, + timeout=20.0, + ) + assert result == {"email": "jane+tag@example.com"} + + +def test_get_contact_returns_none_for_missing_contact() -> None: + response = Mock() + response.status_code = 404 + + with patch("five08.clients.brevo.requests.get", return_value=response): + result = BrevoClient(api_key="brevo-key").get_contact("jane@example.com") + + assert result is None + + +@pytest.mark.parametrize("email", ["", "not-an-email", "a@b", "jane @example.com"]) +def test_get_contact_rejects_invalid_email(email: str) -> None: + with pytest.raises(ValueError, match="full email address"): + BrevoClient(api_key="brevo-key").get_contact(email) + + +@pytest.mark.parametrize("email", ["", "not-an-email", "a@b", "jane @example.com"]) +def test_add_contact_to_list_rejects_invalid_email(email: str) -> None: + with pytest.raises(ValueError, match="full email address"): + BrevoClient(api_key="brevo-key").add_contact_to_list(email=email, list_id=4) + + +def test_get_contact_truncates_error_response_body() -> None: + response = Mock() + response.status_code = 500 + response.text = f"email=jane@example.com {'x' * 600}" + + with patch("five08.clients.brevo.requests.get", return_value=response): + with pytest.raises(BrevoAPIError) as exc_info: + BrevoClient(api_key="brevo-key").get_contact("jane@example.com") + + message = str(exc_info.value) + assert "jane@example.com" not in message + assert "[redacted-email]" in message + assert len(message) < 600 + + +def test_find_list_id_by_name_gets_matching_list() -> None: + response = Mock() + response.status_code = 200 + response.json.return_value = { + "count": 2, + "lists": [ + {"id": 3, "name": "Other"}, + {"id": 4, "name": "508 members"}, + ], + } + + with patch("five08.clients.brevo.requests.get", return_value=response) as get: + list_id = BrevoClient(api_key="brevo-key").find_list_id_by_name("508 Members") + + get.assert_called_once_with( + "https://api.brevo.com/v3/contacts/lists", + headers={"Accept": "application/json", "api-key": "brevo-key"}, + params={"limit": 50, "offset": 0, "sort": "asc"}, + timeout=20.0, + ) + assert list_id == 4 + + +def test_find_list_id_by_name_returns_none_when_missing() -> None: + response = Mock() + response.status_code = 200 + response.json.return_value = {"count": 1, "lists": [{"id": 3, "name": "Other"}]} + + with patch("five08.clients.brevo.requests.get", return_value=response): + list_id = BrevoClient(api_key="brevo-key").find_list_id_by_name("508 members") + + assert list_id is None + + +def test_find_list_id_by_name_truncates_error_response_body() -> None: + response = Mock() + response.status_code = 503 + response.text = f"email=jane@example.com {'x' * 600}" + + with patch("five08.clients.brevo.requests.get", return_value=response): + with pytest.raises(BrevoAPIError) as exc_info: + BrevoClient(api_key="brevo-key").find_list_id_by_name("508 members") + + message = str(exc_info.value) + assert "jane@example.com" not in message + assert "[redacted-email]" in message + assert len(message) < 600 diff --git a/tests/unit/test_crm_create_sso_user.py b/tests/unit/test_crm_create_sso_user.py index 7923f676..b91610d3 100644 --- a/tests/unit/test_crm_create_sso_user.py +++ b/tests/unit/test_crm_create_sso_user.py @@ -44,6 +44,21 @@ def cog(mock_espo_api: Mock) -> CRMCog: return CRMCog(Mock()) +@pytest.mark.asyncio +async def test_add_emails_to_newsletter_returns_warning_on_unexpected_error( + cog: CRMCog, +) -> None: + with patch( + "five08.discord_bot.cogs.crm.sync_newsletter_contacts", + side_effect=RuntimeError("provider `exploded` for jane@example.com"), + ): + warning = await cog._add_emails_to_newsletter( + ["jane@508.dev", "jane@example.com"] + ) + + assert warning == "Newsletter sync failed: provider 'exploded' for [redacted-email]" + + @pytest.mark.asyncio async def test_create_sso_user_creates_links_and_sends_recovery_email( cog: CRMCog, mock_interaction: AsyncMock, mock_espo_api: Mock @@ -521,6 +536,11 @@ async def test_create_user_accounts_creates_mailbox_sso_and_outline_invite( patch.object(cog, "_migadu_client", return_value=migadu_client), patch.object(cog, "_authentik_client", return_value=authentik_client), patch.object(cog, "_outline_client", return_value=outline_client), + patch.object( + cog, + "_add_emails_to_newsletter", + new=AsyncMock(return_value=None), + ) as mock_newsletter, patch.object(cog, "_audit_command_safe") as mock_audit, ): mock_espo_api.request.return_value = {"id": "crm-123"} @@ -546,6 +566,9 @@ async def test_create_user_accounts_creates_mailbox_sso_and_outline_invite( name="Jane Doe", role="member", ) + mock_newsletter.assert_awaited_once_with( + ["jane@508.dev", "jane.personal@example.com"] + ) assert mock_espo_api.request.call_args_list[0].args == ( "PUT", "Contact/crm-123", @@ -560,6 +583,7 @@ async def test_create_user_accounts_creates_mailbox_sso_and_outline_invite( assert "User accounts are ready" in message assert "Email: `jane@508.dev`" in message assert "Outline invite: sent." in message + assert "Newsletter: added mailbox and backup email." in message assert mock_interaction.followup.send.call_args.kwargs["ephemeral"] is True assert mock_audit.call_args.kwargs["metadata"]["outline_invited"] is True diff --git a/tests/unit/test_keila_client.py b/tests/unit/test_keila_client.py new file mode 100644 index 00000000..077fd26c --- /dev/null +++ b/tests/unit/test_keila_client.py @@ -0,0 +1,263 @@ +"""Unit tests for the shared Keila API client.""" + +from unittest.mock import Mock, patch + +import pytest +import requests + +from five08.clients.keila import KeilaAPIError, KeilaClient + + +def test_get_contact_by_email_fetches_contact() -> None: + response = Mock() + response.status_code = 200 + response.content = b'{"data":{"id":"contact-1","email":"jane@example.com"}}' + response.json.return_value = { + "data": {"id": "contact-1", "email": "jane@example.com"} + } + + with patch( + "five08.clients.keila.requests.request", return_value=response + ) as request: + result = KeilaClient(api_key="keila-key").get_contact_by_email( + "Jane@Example.com" + ) + + request.assert_called_once_with( + "GET", + "https://app.keila.io/api/v1/contacts/jane%40example.com", + headers={ + "Accept": "application/json", + "Authorization": "Bearer keila-key", + }, + params={"id_type": "email"}, + json=None, + timeout=20.0, + ) + assert result == {"id": "contact-1", "email": "jane@example.com"} + + +def test_get_contact_by_email_url_encodes_plus_in_email() -> None: + response = Mock() + response.status_code = 200 + response.content = b'{"data":{"id":"contact-1","email":"jane+tag@example.com"}}' + response.json.return_value = { + "data": {"id": "contact-1", "email": "jane+tag@example.com"} + } + + with patch( + "five08.clients.keila.requests.request", return_value=response + ) as request: + result = KeilaClient(api_key="keila-key").get_contact_by_email( + "Jane+Tag@Example.com" + ) + + request.assert_called_once_with( + "GET", + "https://app.keila.io/api/v1/contacts/jane%2Btag%40example.com", + headers={ + "Accept": "application/json", + "Authorization": "Bearer keila-key", + }, + params={"id_type": "email"}, + json=None, + timeout=20.0, + ) + assert result == {"id": "contact-1", "email": "jane+tag@example.com"} + + +def test_get_contact_by_email_returns_none_for_missing_contact() -> None: + response = Mock() + response.status_code = 404 + + with patch("five08.clients.keila.requests.request", return_value=response): + result = KeilaClient(api_key="keila-key").get_contact_by_email( + "jane@example.com" + ) + + assert result is None + + +@pytest.mark.parametrize("email", ["", "not-an-email", "a@b", "jane @example.com"]) +def test_get_contact_by_email_rejects_invalid_email(email: str) -> None: + with pytest.raises(ValueError, match="full email address"): + KeilaClient(api_key="keila-key").get_contact_by_email(email) + + +@pytest.mark.parametrize("email", ["", "not-an-email", "a@b", "jane @example.com"]) +def test_upsert_active_contact_rejects_invalid_email(email: str) -> None: + with pytest.raises(ValueError, match="full email address"): + KeilaClient(api_key="keila-key").upsert_active_contact( + email=email, + data={"audiences": ["508_members"]}, + existing_contact=None, + ) + + +def test_upsert_active_contact_creates_missing_contact() -> None: + missing = Mock() + missing.status_code = 404 + created = Mock() + created.status_code = 201 + created.content = b'{"data":{"id":"contact-1"}}' + created.json.return_value = {"data": {"id": "contact-1"}} + + with patch( + "five08.clients.keila.requests.request", + side_effect=[missing, created], + ) as request: + result = KeilaClient(api_key="keila-key").upsert_active_contact( + email="jane@example.com", + first_name="Jane", + last_name="Doe", + data={"audiences": ["508_members"]}, + ) + + assert request.call_args_list[1].kwargs["json"] == { + "data": { + "email": "jane@example.com", + "status": "active", + "data": {"audiences": ["508_members"]}, + "first_name": "Jane", + "last_name": "Doe", + } + } + assert result == {"id": "contact-1"} + + +def test_upsert_active_contact_updates_existing_contact_without_status() -> None: + existing = Mock() + existing.status_code = 200 + existing.content = b'{"data":{"id":"contact-1","status":"active"}}' + existing.json.return_value = {"data": {"id": "contact-1", "status": "active"}} + updated = Mock() + updated.status_code = 200 + updated.content = b'{"data":{"id":"contact-1"}}' + updated.json.return_value = {"data": {"id": "contact-1"}} + + with patch( + "five08.clients.keila.requests.request", + side_effect=[existing, updated], + ) as request: + result = KeilaClient(api_key="keila-key").upsert_active_contact( + email="jane@example.com", + data={"audiences": ["508_members"]}, + ) + + assert request.call_args_list[1].args == ( + "PATCH", + "https://app.keila.io/api/v1/contacts/contact-1", + ) + assert request.call_args_list[1].kwargs["json"] == { + "data": { + "email": "jane@example.com", + "data": {"audiences": ["508_members"]}, + } + } + assert result == {"id": "contact-1"} + + +def test_upsert_active_contact_preserves_existing_contact_data() -> None: + existing = Mock() + existing.status_code = 200 + existing.content = ( + b'{"data":{"id":"contact-1","status":"active",' + b'"data":{"form":"member-intake","audiences":["old"]}}}' + ) + existing.json.return_value = { + "data": { + "id": "contact-1", + "status": "active", + "data": {"form": "member-intake", "audiences": ["old"]}, + } + } + updated = Mock() + updated.status_code = 200 + updated.content = b'{"data":{"id":"contact-1"}}' + updated.json.return_value = {"data": {"id": "contact-1"}} + + with patch( + "five08.clients.keila.requests.request", + side_effect=[existing, updated], + ) as request: + result = KeilaClient(api_key="keila-key").upsert_active_contact( + email="jane@example.com", + data={"audiences": ["508_members"], "mailbox_email": "jane@example.com"}, + ) + + assert request.call_args_list[1].kwargs["json"] == { + "data": { + "email": "jane@example.com", + "data": { + "form": "member-intake", + "audiences": ["old", "508_members"], + "mailbox_email": "jane@example.com", + }, + } + } + assert result == {"id": "contact-1"} + + +def test_upsert_active_contact_uses_supplied_existing_contact_without_lookup() -> None: + updated = Mock() + updated.status_code = 200 + updated.content = b'{"data":{"id":"contact-1"}}' + updated.json.return_value = {"data": {"id": "contact-1"}} + + with patch( + "five08.clients.keila.requests.request", + return_value=updated, + ) as request: + result = KeilaClient(api_key="keila-key").upsert_active_contact( + email="jane@example.com", + data={"audiences": ["508_members"]}, + existing_contact={"id": "contact-1", "email": "jane@example.com"}, + ) + + request.assert_called_once() + assert request.call_args.args == ( + "PATCH", + "https://app.keila.io/api/v1/contacts/contact-1", + ) + assert result == {"id": "contact-1"} + + +def test_upsert_active_contact_requires_existing_contact_id() -> None: + existing = Mock() + existing.status_code = 200 + existing.content = b'{"data":{"email":"jane@example.com","status":"active"}}' + existing.json.return_value = { + "data": {"email": "jane@example.com", "status": "active"} + } + + with patch("five08.clients.keila.requests.request", return_value=existing): + with pytest.raises(KeilaAPIError, match="missing id"): + KeilaClient(api_key="keila-key").upsert_active_contact( + email="jane@example.com", + data={"audiences": ["508_members"]}, + ) + + +def test_keila_client_raises_on_request_error() -> None: + with patch( + "five08.clients.keila.requests.request", + side_effect=requests.Timeout("timed out"), + ): + with pytest.raises(KeilaAPIError, match="request failed"): + KeilaClient(api_key="keila-key").get_contact_by_email("jane@example.com") + + +def test_keila_client_truncates_error_response_body() -> None: + response = Mock() + response.status_code = 500 + response.text = f"email=jane@example.com {'x' * 600}" + response.content = response.text.encode() + + with patch("five08.clients.keila.requests.request", return_value=response): + with pytest.raises(KeilaAPIError) as exc_info: + KeilaClient(api_key="keila-key").get_contact_by_email("jane@example.com") + + message = str(exc_info.value) + assert "jane@example.com" not in message + assert "[redacted-email]" in message + assert len(message) < 600 diff --git a/tests/unit/test_migadu_client.py b/tests/unit/test_migadu_client.py new file mode 100644 index 00000000..c2faccc3 --- /dev/null +++ b/tests/unit/test_migadu_client.py @@ -0,0 +1,75 @@ +"""Unit tests for the shared Migadu API client.""" + +from unittest.mock import Mock, patch + +import pytest + +from five08.clients.migadu import MigaduAPIError, MigaduClient + + +def test_list_mailboxes_accepts_top_level_array_response() -> None: + response = Mock() + response.status_code = 200 + response.json.return_value = [ + { + "address": "Jane@508.dev", + "name": "Jane Doe", + "password_recovery_email": "Jane@Example.com", + } + ] + + with patch("five08.clients.migadu.requests.get", return_value=response): + mailboxes = MigaduClient( + username="migadu-user", + api_key="migadu-key", + domain="508.dev", + ).list_mailboxes() + + assert len(mailboxes) == 1 + assert mailboxes[0].address == "jane@508.dev" + assert mailboxes[0].name == "Jane Doe" + assert mailboxes[0].password_recovery_email == "jane@example.com" + + +def test_list_mailboxes_accepts_wrapped_mailboxes_response() -> None: + response = Mock() + response.status_code = 200 + response.json.return_value = { + "mailboxes": [ + { + "address": "jane@508.dev", + "name": "Jane Doe", + "password_recovery_email": None, + } + ] + } + + with patch("five08.clients.migadu.requests.get", return_value=response): + mailboxes = MigaduClient( + username="migadu-user", + api_key="migadu-key", + domain="508.dev", + ).list_mailboxes() + + assert len(mailboxes) == 1 + assert mailboxes[0].address == "jane@508.dev" + assert mailboxes[0].password_recovery_email is None + + +def test_list_mailboxes_truncates_error_response_body() -> None: + response = Mock() + response.status_code = 500 + response.text = f"email=jane@508.dev {'x' * 600}" + + with patch("five08.clients.migadu.requests.get", return_value=response): + with pytest.raises(MigaduAPIError) as exc_info: + MigaduClient( + username="migadu-user", + api_key="migadu-key", + domain="508.dev", + ).list_mailboxes() + + message = str(exc_info.value) + assert "jane@508.dev" not in message + assert "[redacted-email]" in message + assert len(message) < 600 diff --git a/tests/unit/test_migadu_create_mailbox.py b/tests/unit/test_migadu_create_mailbox.py index c84c4b1d..08dceb70 100644 --- a/tests/unit/test_migadu_create_mailbox.py +++ b/tests/unit/test_migadu_create_mailbox.py @@ -32,6 +32,11 @@ def migadu_cog(mock_bot: Mock) -> MigaduCog: mock_settings.migadu_api_user = "migadu-user" mock_settings.migadu_api_key = "migadu-key" mock_settings.migadu_mailbox_domain = "508.dev" + mock_settings.brevo_api_key = None + mock_settings.brevo_api_base_url = "https://api.brevo.com/v3" + mock_settings.brevo_api_timeout_seconds = 20.0 + mock_settings.brevo_508_members_newsletter_list_id = None + mock_settings.brevo_508_members_newsletter_list_name = "508 members" cog = MigaduCog(mock_bot) cog.espo_api = Mock() return cog @@ -70,6 +75,21 @@ def test_normalize_mailbox_request_rejects_non_508_domain( migadu_cog._normalize_mailbox_request("alice@gmail.com") +@pytest.mark.asyncio +async def test_add_emails_to_newsletter_returns_warning_on_unexpected_error( + migadu_cog: MigaduCog, +) -> None: + with patch( + "five08.discord_bot.cogs.migadu.sync_newsletter_contacts", + side_effect=RuntimeError("provider exploded for alice@example.com"), + ): + warning = await migadu_cog._add_emails_to_newsletter( + ["alice@508.dev", "alice@gmail.com"] + ) + + assert warning == "Newsletter sync failed: provider exploded for [redacted-email]" + + @pytest.mark.asyncio async def test_create_mailbox_command_success_with_crm_defaults_and_sync( migadu_cog: MigaduCog, @@ -90,6 +110,7 @@ async def test_create_mailbox_command_success_with_crm_defaults_and_sync( migadu_cog._create_migadu_mailbox = AsyncMock( return_value={"address": "alice@508.dev"} ) + migadu_cog._add_emails_to_newsletter = AsyncMock(return_value=None) await migadu_cog.create_mailbox.callback( migadu_cog, @@ -107,6 +128,9 @@ async def test_create_mailbox_command_success_with_crm_defaults_and_sync( "contact-1", {"c508Email": "alice@508.dev"}, ) + migadu_cog._add_emails_to_newsletter.assert_awaited_once_with( + ["alice@508.dev", "alice@gmail.com"] + ) _args, kwargs = mock_interaction.followup.send.call_args assert kwargs["embed"].title == "✅ Mailbox Created" diff --git a/tests/unit/test_newsletter_sync.py b/tests/unit/test_newsletter_sync.py new file mode 100644 index 00000000..98ed95b5 --- /dev/null +++ b/tests/unit/test_newsletter_sync.py @@ -0,0 +1,781 @@ +"""Unit tests for Migadu-backed newsletter audience sync.""" + +from __future__ import annotations + +from datetime import datetime, timezone +from types import SimpleNamespace +from typing import Any + +import pytest + +from five08.clients.migadu import MigaduMailbox +from five08.clients.espo import EspoAPIError +from five08.newsletter_sync import ( + NewsletterSyncProcessor, + build_newsletter_providers, + format_newsletter_sync_warning, + sync_newsletter_contacts, +) +from five08.newsletter_suppressions import NewsletterSuppressionRecord + + +class FakeMigaduClient: + mailboxes: list[MigaduMailbox] = [] + + def __init__( + self, + *, + username: str, + api_key: str, + domain: str, + ) -> None: + self.username = username + self.api_key = api_key + self.domain = domain + + def list_mailboxes(self) -> list[MigaduMailbox]: + return list(self.mailboxes) + + +class FakeBrevoClient: + contacts: dict[str, dict[str, Any]] = {} + subscriptions: list[dict[str, Any]] = [] + list_lookup_names: list[str] = [] + contact_lookup_emails: list[str] = [] + lookup_errors: set[str] = set() + + def __init__( + self, + *, + api_key: str, + base_url: str = "https://api.brevo.com/v3", + timeout_seconds: float = 20.0, + ) -> None: + self.api_key = api_key + self.base_url = base_url + self.timeout_seconds = timeout_seconds + + def get_contact(self, email: str) -> dict[str, Any] | None: + self.contact_lookup_emails.append(email) + if email in self.lookup_errors: + raise RuntimeError(f"Brevo lookup failed for {email}") + return self.contacts.get(email) + + def add_contact_to_list(self, *, email: str, list_id: int) -> dict[str, Any]: + self.subscriptions.append({"email": email, "list_id": list_id}) + return {"id": len(self.subscriptions)} + + def find_list_id_by_name(self, name: str) -> int | None: + self.list_lookup_names.append(name) + return 4 if name == "508 members" else None + + +class FakeKeilaClient: + contacts: dict[str, dict[str, Any]] = {} + upserts: list[dict[str, Any]] = [] + lookups: list[str] = [] + lookup_errors: set[str] = set() + + def __init__( + self, + *, + api_key: str, + base_url: str = "https://app.keila.io", + timeout_seconds: float = 20.0, + ) -> None: + self.api_key = api_key + self.base_url = base_url + self.timeout_seconds = timeout_seconds + + def get_contact_by_email(self, email: str) -> dict[str, Any] | None: + self.lookups.append(email) + if email in self.lookup_errors: + raise RuntimeError(f"Keila lookup failed for {email}") + return self.contacts.get(email) + + def upsert_active_contact( + self, + *, + email: str, + first_name: str | None = None, + last_name: str | None = None, + data: dict[str, Any] | None = None, + existing_contact: dict[str, Any] | None | object = None, + ) -> dict[str, Any]: + self.upserts.append( + { + "email": email, + "first_name": first_name, + "last_name": last_name, + "data": data, + "existing_contact": existing_contact, + } + ) + return {"id": str(len(self.upserts))} + + +class FakeEspoClient: + contacts: list[dict[str, Any]] = [] + raise_error = False + calls: list[dict[str, Any]] = [] + + def __init__( + self, + base_url: str, + api_key: str, + timeout_seconds: float = 20.0, + ) -> None: + self.base_url = base_url + self.api_key = api_key + self.timeout_seconds = timeout_seconds + + def list_contacts(self, params: dict[str, Any]) -> dict[str, Any]: + self.calls.append(params) + if self.raise_error: + raise EspoAPIError("CRM unavailable") + filter_values = { + str(item.get("value") or "").strip().lower() + for clause in params.get("where", []) + if isinstance(clause, dict) + for item in clause.get("value", []) + if isinstance(item, dict) + } + if not filter_values: + return {"list": [dict(item) for item in self.contacts]} + matches: list[dict[str, Any]] = [] + for contact in self.contacts: + contact_values = { + str(contact.get("c508Email") or "").strip().lower(), + str(contact.get("emailAddress") or "").strip().lower(), + } + if filter_values.intersection(value for value in contact_values if value): + matches.append(dict(contact)) + return {"list": matches} + + +@pytest.fixture(autouse=True) +def reset_fakes(monkeypatch: pytest.MonkeyPatch) -> None: + FakeMigaduClient.mailboxes = [] + FakeBrevoClient.contacts = {} + FakeBrevoClient.subscriptions = [] + FakeBrevoClient.list_lookup_names = [] + FakeBrevoClient.contact_lookup_emails = [] + FakeBrevoClient.lookup_errors = set() + FakeKeilaClient.contacts = {} + FakeKeilaClient.upserts = [] + FakeKeilaClient.lookups = [] + FakeKeilaClient.lookup_errors = set() + FakeEspoClient.contacts = [] + FakeEspoClient.raise_error = False + FakeEspoClient.calls = [] + monkeypatch.setattr("five08.newsletter_sync.MigaduClient", FakeMigaduClient) + monkeypatch.setattr("five08.newsletter_sync.BrevoClient", FakeBrevoClient) + monkeypatch.setattr("five08.newsletter_sync.KeilaClient", FakeKeilaClient) + monkeypatch.setattr("five08.newsletter_sync.EspoClient", FakeEspoClient) + + +def _settings(**overrides: Any) -> SimpleNamespace: + values: dict[str, Any] = { + "migadu_api_user": "migadu-user", + "migadu_api_key": "migadu-key", + "migadu_mailbox_domain": "508.dev", + "brevo_api_key": "brevo-key", + "brevo_508_members_newsletter_list_id": 4, + "keila_api_key": "keila-key", + "newsletter_sync_excluded_mailboxes": "system", + } + values.update(overrides) + return SimpleNamespace(**values) + + +def test_sync_508_members_adds_mailbox_and_backup_email_to_configured_providers() -> ( + None +): + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ), + MigaduMailbox( + address="system@508.dev", + name="System", + password_recovery_email="ops@example.com", + ), + ] + + result = NewsletterSyncProcessor(_settings()).sync_508_members() + + assert result["mailboxes_scanned"] == 2 + assert result["system_mailboxes_skipped"] == 1 + assert result["contacts_considered"] == 2 + assert FakeBrevoClient.subscriptions == [ + {"email": "jane@508.dev", "list_id": 4}, + {"email": "jane@example.com", "list_id": 4}, + ] + assert [item["email"] for item in FakeKeilaClient.upserts] == [ + "jane@508.dev", + "jane@example.com", + ] + assert result["providers"]["brevo"]["synced"] == 2 + assert result["providers"]["keila"]["synced"] == 2 + + +def test_sync_508_members_dry_run_reports_provider_actions_without_writes() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + + result = NewsletterSyncProcessor(_settings()).sync_508_members(dry_run=True) + + assert result["dry_run"] is True + assert result["mailboxes_scanned"] == 1 + assert result["contacts_considered"] == 2 + assert sorted(FakeBrevoClient.contact_lookup_emails) == [ + "jane@508.dev", + "jane@example.com", + ] + assert sorted(FakeKeilaClient.lookups) == ["jane@508.dev", "jane@example.com"] + assert FakeBrevoClient.subscriptions == [] + assert FakeKeilaClient.upserts == [] + assert result["providers"]["brevo"]["would_sync"] == 2 + assert result["providers"]["keila"]["would_sync"] == 2 + + +def test_sync_508_members_skips_provider_suppressed_contacts() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + FakeBrevoClient.contacts = {"jane@example.com": {"emailBlacklisted": True}} + FakeKeilaClient.contacts = {"jane@example.com": {"status": "unsubscribed"}} + + result = NewsletterSyncProcessor(_settings()).sync_508_members() + + assert FakeBrevoClient.subscriptions == [{"email": "jane@508.dev", "list_id": 4}] + assert [item["email"] for item in FakeKeilaClient.upserts] == ["jane@508.dev"] + assert result["providers"]["brevo"]["statuses"] == { + "synced": 1, + "skipped_provider_suppressed": 1, + } + assert result["providers"]["keila"]["statuses"] == { + "synced": 1, + "skipped_provider_suppressed": 1, + } + + +def test_sync_508_members_applies_brevo_suppression_to_all_providers() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + FakeBrevoClient.contacts = {"jane@example.com": {"emailBlacklisted": True}} + + result = NewsletterSyncProcessor(_settings()).sync_508_members() + + assert FakeBrevoClient.subscriptions == [{"email": "jane@508.dev", "list_id": 4}] + assert [item["email"] for item in FakeKeilaClient.upserts] == ["jane@508.dev"] + assert result["suppressed_contacts_skipped"] == 1 + assert result["providers"]["brevo"]["statuses"] == { + "synced": 1, + "skipped_provider_suppressed": 1, + } + assert result["providers"]["keila"]["statuses"] == { + "synced": 1, + "skipped_provider_suppressed": 1, + } + + +def test_sync_508_members_does_not_treat_unreachable_as_suppressed() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email=None, + ) + ] + FakeKeilaClient.contacts = { + "jane@508.dev": {"id": "contact-1", "status": "unreachable"} + } + + result = NewsletterSyncProcessor(_settings(brevo_api_key=None)).sync_508_members() + + assert result["suppressed_contacts_skipped"] == 0 + assert result["providers"]["keila"]["statuses"] == {"synced": 1} + assert [item["email"] for item in FakeKeilaClient.upserts] == ["jane@508.dev"] + + +def test_sync_508_members_allows_sms_only_brevo_blacklist() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email=None, + ) + ] + FakeBrevoClient.contacts = { + "jane@508.dev": {"smsBlacklisted": True, "emailBlacklisted": False} + } + + result = NewsletterSyncProcessor(_settings(keila_api_key=None)).sync_508_members() + + assert FakeBrevoClient.subscriptions == [{"email": "jane@508.dev", "list_id": 4}] + assert result["providers"]["brevo"]["statuses"] == {"synced": 1} + + +def test_sync_508_members_skips_brevo_list_unsubscribed_contacts() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + FakeBrevoClient.contacts = {"jane@example.com": {"listUnsubscribed": ["4"]}} + + result = NewsletterSyncProcessor(_settings()).sync_508_members() + + assert FakeBrevoClient.subscriptions == [{"email": "jane@508.dev", "list_id": 4}] + assert result["providers"]["brevo"]["statuses"] == { + "synced": 1, + "skipped_provider_suppressed": 1, + } + + +def test_sync_508_members_caches_brevo_list_lookup_by_name() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + + result = NewsletterSyncProcessor( + _settings( + brevo_508_members_newsletter_list_id=None, + brevo_508_members_newsletter_list_name="508 members", + keila_api_key=None, + ) + ).sync_508_members() + + assert result["providers"]["brevo"]["synced"] == 2 + assert FakeBrevoClient.list_lookup_names == ["508 members"] + + +def test_sync_508_members_skips_missing_brevo_list_before_contact_lookup() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + + result = NewsletterSyncProcessor( + _settings( + brevo_508_members_newsletter_list_id=None, + brevo_508_members_newsletter_list_name="Missing list", + keila_api_key=None, + ) + ).sync_508_members() + + assert result["providers"]["brevo"]["synced"] == 0 + assert result["providers"]["brevo"]["statuses"] == {"skipped_list_missing": 2} + assert FakeBrevoClient.list_lookup_names == ["Missing list"] + assert FakeBrevoClient.contact_lookup_emails == [] + + +def test_sync_508_members_avoids_duplicate_keila_contact_lookups() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + + result = NewsletterSyncProcessor(_settings(brevo_api_key=None)).sync_508_members() + + assert result["providers"]["keila"]["synced"] == 2 + assert FakeKeilaClient.lookups == ["jane@508.dev", "jane@example.com"] + assert [item["existing_contact"] for item in FakeKeilaClient.upserts] == [ + None, + None, + ] + + +def test_sync_508_members_skips_internal_suppression_registry( + monkeypatch: pytest.MonkeyPatch, +) -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + now = datetime(2026, 6, 26, tzinfo=timezone.utc) + suppression = NewsletterSuppressionRecord( + email="jane@example.com", + source_provider="manual", + reason="manual", + active=True, + metadata={}, + first_seen_at=now, + last_seen_at=now, + created_at=now, + updated_at=now, + ) + + def fake_load_suppressions( + settings: Any, emails: list[str] + ) -> dict[str, list[Any]]: + assert sorted(emails) == ["jane@508.dev", "jane@example.com"] + return {"jane@example.com": [suppression]} + + monkeypatch.setattr( + "five08.newsletter_sync.load_active_newsletter_suppressions_by_email", + fake_load_suppressions, + ) + + result = NewsletterSyncProcessor( + _settings(postgres_url="postgresql://example/db") + ).sync_508_members() + + assert result["suppressed_contacts_skipped"] == 1 + assert FakeBrevoClient.subscriptions == [{"email": "jane@508.dev", "list_id": 4}] + assert [item["email"] for item in FakeKeilaClient.upserts] == ["jane@508.dev"] + assert result["providers"]["brevo"]["statuses"] == { + "synced": 1, + "skipped_internal_suppression": 1, + } + + +def test_sync_508_members_fails_closed_when_suppression_lookup_fails() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + FakeBrevoClient.lookup_errors = {"jane@example.com"} + + result = NewsletterSyncProcessor(_settings()).sync_508_members() + + assert result["suppression_lookup_failed_skipped"] == 1 + assert FakeBrevoClient.subscriptions == [{"email": "jane@508.dev", "list_id": 4}] + assert [item["email"] for item in FakeKeilaClient.upserts] == ["jane@508.dev"] + assert result["providers"]["brevo"]["failed"] == 1 + assert result["providers"]["keila"]["statuses"] == { + "synced": 1, + "skipped_suppression_lookup_failed": 1, + } + + +def test_sync_508_members_deactivates_stale_provider_suppression( + monkeypatch: pytest.MonkeyPatch, +) -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email=None, + ) + ] + now = datetime(2026, 6, 26, tzinfo=timezone.utc) + stale_record = NewsletterSuppressionRecord( + email="jane@508.dev", + source_provider="brevo", + reason="email_blacklisted", + active=True, + metadata={}, + first_seen_at=now, + last_seen_at=now, + created_at=now, + updated_at=now, + ) + deactivated: list[tuple[str, str]] = [] + + monkeypatch.setattr( + "five08.newsletter_sync.load_active_newsletter_suppressions_by_email", + lambda settings, emails: {"jane@508.dev": [stale_record]}, + ) + + def fake_deactivate( + settings: Any, + *, + email: str, + source_provider: str, + ) -> bool: + deactivated.append((email, source_provider)) + return True + + monkeypatch.setattr( + "five08.newsletter_sync.deactivate_newsletter_suppression", + fake_deactivate, + ) + + result = NewsletterSyncProcessor( + _settings(postgres_url="postgresql://example/db") + ).sync_508_members() + + assert result["suppression_registry_deactivated"] == 1 + assert deactivated == [("jane@508.dev", "brevo")] + assert result["suppressed_contacts_skipped"] == 0 + assert FakeBrevoClient.subscriptions == [{"email": "jane@508.dev", "list_id": 4}] + assert [item["email"] for item in FakeKeilaClient.upserts] == ["jane@508.dev"] + + +def test_sync_508_members_skips_crm_blocked_mailboxes() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + FakeEspoClient.contacts = [ + {"id": "contact-1", "type": "Inactive Member", "c508Email": "jane@508.dev"} + ] + + result = NewsletterSyncProcessor( + _settings(espo_base_url="https://crm.example", espo_api_key="espo-key") + ).sync_508_members() + + assert result["crm_blocked_skipped"] == 1 + assert result["contacts_considered"] == 0 + assert FakeBrevoClient.subscriptions == [] + assert FakeKeilaClient.upserts == [] + + +def test_sync_508_members_skips_crm_unmatched_mailboxes_when_crm_configured() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="service@508.dev", + name="Service Account", + password_recovery_email="ops@example.com", + ) + ] + + result = NewsletterSyncProcessor( + _settings(espo_base_url="https://crm.example", espo_api_key="espo-key") + ).sync_508_members() + + assert result["crm_unmatched_skipped"] == 1 + assert result["contacts_considered"] == 0 + assert FakeBrevoClient.subscriptions == [] + assert FakeKeilaClient.upserts == [] + + +def test_sync_508_members_syncs_crm_matched_mailboxes_when_crm_configured() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + FakeEspoClient.contacts = [ + {"id": "contact-1", "type": "Member", "c508Email": "jane@508.dev"} + ] + + result = NewsletterSyncProcessor( + _settings(espo_base_url="https://crm.example", espo_api_key="espo-key") + ).sync_508_members() + + assert result["crm_unmatched_skipped"] == 0 + assert result["contacts_considered"] == 2 + assert FakeBrevoClient.subscriptions == [ + {"email": "jane@508.dev", "list_id": 4}, + {"email": "jane@example.com", "list_id": 4}, + ] + + +def test_sync_508_members_batches_crm_lookup_for_multiple_mailboxes() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email=None, + ), + MigaduMailbox( + address="john@508.dev", + name="John Doe", + password_recovery_email="john@example.com", + ), + ] + FakeEspoClient.contacts = [ + {"id": "contact-1", "type": "Member", "c508Email": "jane@508.dev"}, + {"id": "contact-2", "type": "Member", "emailAddress": "john@example.com"}, + ] + + result = NewsletterSyncProcessor( + _settings(espo_base_url="https://crm.example", espo_api_key="espo-key") + ).sync_508_members() + + assert result["crm_unmatched_skipped"] == 0 + assert result["contacts_considered"] == 3 + assert len(FakeEspoClient.calls) == 1 + crm_filters = FakeEspoClient.calls[0]["where"][0]["value"] + assert {"type": "equals", "attribute": "c508Email", "value": "jane@508.dev"} in ( + crm_filters + ) + assert { + "type": "equals", + "attribute": "emailAddress", + "value": "john@example.com", + } in crm_filters + + +def test_sync_508_members_skips_mailbox_when_any_crm_match_is_blocked() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + FakeEspoClient.contacts = [ + {"id": "contact-1", "type": "Member", "c508Email": "jane@508.dev"}, + {"id": "contact-2", "type": "Inactive Member", "c508Email": "jane@508.dev"}, + ] + + result = NewsletterSyncProcessor( + _settings(espo_base_url="https://crm.example", espo_api_key="espo-key") + ).sync_508_members() + + assert result["crm_blocked_skipped"] == 1 + assert result["contacts_considered"] == 0 + assert FakeBrevoClient.subscriptions == [] + assert FakeKeilaClient.upserts == [] + + +def test_build_newsletter_providers_uses_default_list_name_when_blank() -> None: + providers = build_newsletter_providers( + _settings( + brevo_508_members_newsletter_list_id=None, + brevo_508_members_newsletter_list_name=" ", + ) + ) + + assert len(providers) == 2 + assert providers[0].list_name == "508 members" + + +def test_format_newsletter_sync_warning_reports_suppressed_skips() -> None: + warning = format_newsletter_sync_warning( + { + "providers": { + "brevo": { + "synced": 1, + "skipped": 1, + "failed": 0, + "statuses": {"skipped_provider_suppressed": 1}, + } + } + } + ) + + assert warning == "brevo skipped 1 suppressed contact(s)" + + +def test_format_newsletter_sync_warning_redacts_failure_emails() -> None: + warning = format_newsletter_sync_warning( + { + "providers": { + "keila": { + "failed": 1, + "failures": [ + { + "email": "jane@example.com", + "error": ( + "lookup failed for jane@example.com at " + "/contacts/jane%40example.com" + ), + } + ], + } + } + } + ) + + assert warning == ( + "keila failed for 1 contact(s): lookup failed for [redacted-email] " + "at /contacts/[redacted-email]" + ) + assert "jane@example.com" not in warning + assert "jane%40example.com" not in warning + + +def test_sync_newsletter_contacts_uses_first_email_as_default_mailbox_pointer() -> None: + result = sync_newsletter_contacts( + _settings(brevo_api_key=None), + ["jane@508.dev", "jane@example.com"], + source="test", + ) + + assert result["providers"]["keila"]["synced"] == 2 + assert [item["email"] for item in FakeKeilaClient.upserts] == [ + "jane@508.dev", + "jane@example.com", + ] + assert [item["data"]["mailbox_email"] for item in FakeKeilaClient.upserts] == [ + "jane@508.dev", + "jane@508.dev", + ] + + +def test_sync_newsletter_contacts_fails_closed_when_suppression_lookup_fails() -> None: + FakeBrevoClient.lookup_errors = {"jane@example.com"} + + result = sync_newsletter_contacts( + _settings(), + ["jane@example.com"], + source="test", + ) + + assert result["suppression_lookup_failed_skipped"] == 1 + assert FakeBrevoClient.subscriptions == [] + assert FakeKeilaClient.upserts == [] + assert result["providers"]["brevo"]["failed"] == 1 + assert result["providers"]["keila"]["statuses"] == { + "skipped_suppression_lookup_failed": 1 + } + + +def test_sync_508_members_skips_mailbox_when_crm_lookup_fails() -> None: + FakeMigaduClient.mailboxes = [ + MigaduMailbox( + address="jane@508.dev", + name="Jane Doe", + password_recovery_email="jane@example.com", + ) + ] + FakeEspoClient.raise_error = True + + result = NewsletterSyncProcessor( + _settings(espo_base_url="https://crm.example", espo_api_key="espo-key") + ).sync_508_members() + + assert result["crm_lookup_failed_skipped"] == 1 + assert result["contacts_considered"] == 0 + assert result["crm_lookup_failures"] == [ + { + "mailbox": "jane@508.dev", + "error": "CRM contact lookup failed for jane@508.dev: CRM unavailable", + } + ] + assert FakeBrevoClient.subscriptions == [] + assert FakeKeilaClient.upserts == [] diff --git a/tests/unit/test_runtime_config.py b/tests/unit/test_runtime_config.py index 0dd46eab..a1c38885 100644 --- a/tests/unit/test_runtime_config.py +++ b/tests/unit/test_runtime_config.py @@ -297,6 +297,8 @@ def test_runtime_config_numeric_bounds_are_preserved( "key", [ "DISCORD_LOGS_WEBHOOK_URL", + "NEWSLETTER_SYNC_ENABLED", + "NEWSLETTER_SYNC_INTERVAL_SECONDS", ], ) def test_startup_bound_runtime_config_is_restart_required(key: str) -> None: @@ -305,17 +307,54 @@ def test_startup_bound_runtime_config_is_restart_required(key: str) -> None: assert definition.restart_required is True +@pytest.mark.parametrize( + "key", + [ + "BREVO_API_KEY", + "BREVO_API_BASE_URL", + "BREVO_API_TIMEOUT_SECONDS", + "BREVO_508_MEMBERS_NEWSLETTER_LIST_ID", + "BREVO_508_MEMBERS_NEWSLETTER_LIST_NAME", + "MIGADU_API_USER", + "MIGADU_API_KEY", + "MIGADU_MAILBOX_DOMAIN", + "KEILA_API_KEY", + "KEILA_API_BASE_URL", + "KEILA_API_TIMEOUT_SECONDS", + "NEWSLETTER_SYNC_ENABLED", + "NEWSLETTER_SYNC_INTERVAL_SECONDS", + "NEWSLETTER_SYNC_EXCLUDED_MAILBOXES", + ], +) +def test_newsletter_settings_are_dashboard_configurable(key: str) -> None: + definition = runtime_config_definition_for_key(key) + + assert definition is not None + assert definition.category in {"Newsletter", "Mailbox"} + + def test_core_crm_auth_and_mailbox_settings_are_not_dashboard_configurable() -> None: assert runtime_config_definition_for_key("ESPO_BASE_URL") is None assert runtime_config_definition_for_key("ESPO_API_KEY") is None assert runtime_config_definition_for_key("AUTHENTIK_API_BASE_URL") is None assert runtime_config_definition_for_key("AUTHENTIK_API_TOKEN") is None - assert runtime_config_definition_for_key("MIGADU_API_USER") is None - assert runtime_config_definition_for_key("MIGADU_API_KEY") is None assert runtime_config_definition_for_key("CRM_SYNC_INTERVAL_SECONDS") is None assert runtime_config_definition_for_key("CRM_SYNC_PAGE_SIZE") is None +def test_migadu_runtime_config_accepts_short_env_aliases() -> None: + migadu_user = runtime_config_definition_for_key("MIGADU_API_USER") + migadu_domain = runtime_config_definition_for_key("MIGADU_MAILBOX_DOMAIN") + keila_base_url = runtime_config_definition_for_key("KEILA_API_BASE_URL") + + assert migadu_user is not None + assert "MIGADU_USER" in migadu_user.env_names + assert migadu_domain is not None + assert "MIGADU_DOMAIN" in migadu_domain.env_names + assert keila_base_url is not None + assert "KEILA_BASE_URL" in keila_base_url.env_names + + def test_onboarding_email_smtp_settings_are_dashboard_configurable() -> None: assert runtime_config_definition_for_key("ONBOARDING_EMAIL_SMTP_SERVER") is not None assert ( diff --git a/tests/unit/test_shared_settings.py b/tests/unit/test_shared_settings.py index db5f974c..048d1de1 100644 --- a/tests/unit/test_shared_settings.py +++ b/tests/unit/test_shared_settings.py @@ -99,6 +99,12 @@ def test_shared_settings_expose_agent_external_tool_credentials() -> None: migadu_api_user="migadu-user", migadu_api_key="migadu-key", migadu_mailbox_domain="mail.example.com", + brevo_api_key="brevo-key", + brevo_508_members_newsletter_list_id=4, + brevo_508_members_newsletter_list_name="508 members", + keila_api_key="keila-key", + keila_api_base_url="https://keila.example", + postgres_url="postgresql://postgres:postgres@db.example/workflows", ) runtime_config = ToolRuntimeConfig.from_settings(settings) @@ -107,6 +113,29 @@ def test_shared_settings_expose_agent_external_tool_credentials() -> None: assert runtime_config.migadu_api_user == "migadu-user" assert runtime_config.migadu_api_key == "migadu-key" assert runtime_config.migadu_mailbox_domain == "mail.example.com" + assert runtime_config.brevo_api_key == "brevo-key" + assert runtime_config.brevo_508_members_newsletter_list_id == 4 + assert runtime_config.brevo_508_members_newsletter_list_name == "508 members" + assert runtime_config.keila_api_key == "keila-key" + assert runtime_config.keila_api_base_url == "https://keila.example" + assert ( + runtime_config.postgres_url + == "postgresql://postgres:postgres@db.example/workflows" + ) + + +def test_shared_settings_accept_newsletter_sync_env_aliases() -> None: + settings = SharedSettings( + **{ + "MIGADU_USER": "michael@508.dev", + "MIGADU_DOMAIN": "508.dev", + "KEILA_BASE_URL": "https://keila.508.dev/", + } + ) + + assert settings.migadu_api_user == "michael@508.dev" + assert settings.migadu_mailbox_domain == "508.dev" + assert settings.keila_api_base_url == "https://keila.508.dev/" def test_shared_settings_docuseal_template_id_accepts_numeric_string() -> None: @@ -125,6 +154,20 @@ def test_shared_settings_docuseal_template_id_rejects_non_numeric_string() -> No SharedSettings(docuseal_member_agreement_template_id="abc") +def test_shared_settings_brevo_members_list_id_accepts_blank_string_as_none() -> None: + """Blank Brevo list IDs from env should leave list-name lookup enabled.""" + settings = SharedSettings(brevo_508_members_newsletter_list_id=" ") + + assert settings.brevo_508_members_newsletter_list_id is None + + +def test_shared_settings_brevo_members_list_id_accepts_numeric_string() -> None: + """Numeric Brevo list IDs from env should coerce to integers.""" + settings = SharedSettings(brevo_508_members_newsletter_list_id="4") + + assert settings.brevo_508_members_newsletter_list_id == 4 + + def test_local_service_defaults_target_host_runtime( monkeypatch: pytest.MonkeyPatch, ) -> None: diff --git a/tests/unit/test_worker_newsletter_sync.py b/tests/unit/test_worker_newsletter_sync.py new file mode 100644 index 00000000..1aa6324d --- /dev/null +++ b/tests/unit/test_worker_newsletter_sync.py @@ -0,0 +1,65 @@ +"""Unit tests for worker newsletter sync job result handling.""" + +import pytest + +from five08.worker import jobs + + +def test_sync_508_members_newsletters_job_masks_failure_emails( + monkeypatch: pytest.MonkeyPatch, +) -> None: + class FakeNewsletterSyncProcessor: + def __init__(self, settings: object) -> None: + self.settings = settings + + def sync_508_members(self) -> dict[str, object]: + return { + "crm_lookup_failures": [ + { + "mailbox": "jane@508.dev", + "error": "CRM lookup failed for jane@508.dev", + } + ], + "providers": { + "brevo": { + "failures": [ + { + "email": "jane@example.com", + "error": ( + "provider unavailable for /contacts/" + "jane%40example.com" + ), + } + ] + }, + "keila": {"failures": "not-a-list"}, + }, + } + + monkeypatch.setattr( + jobs, + "NewsletterSyncProcessor", + FakeNewsletterSyncProcessor, + ) + + result = jobs.sync_508_members_newsletters_job() + + assert result == { + "crm_lookup_failures": [ + { + "mailbox": "j***@5****...", + "error": "CRM lookup failed for j***@5****...", + } + ], + "providers": { + "brevo": { + "failures": [ + { + "email": "j***@e****...", + "error": ("provider unavailable for /contacts/j***@e****..."), + } + ] + }, + "keila": {"failures": "not-a-list"}, + }, + }