@burdenoff/microfe-timecampus 2026.830.2 → 2026.904.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/timecampus/components/index.js +4 -4
- package/dist/timecampus/pages/HomePage.d.ts.map +1 -1
- package/dist/timecampus/pages/HomePage.js +149 -133
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +7 -7
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +7 -7
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js +13 -13
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +7 -7
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +7 -7
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/focus-score/FocusScorePage.js +13 -13
- package/dist/timecampus/pages/geofencing/GeofencingPage.js +135 -135
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/invoicing/InvoicingPage.js +7 -7
- package/dist/timecampus/pages/invoicing/InvoicingPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/DeadTimeRadarPage.js +8 -8
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js +4 -4
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js +104 -104
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js +7 -7
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js +33 -33
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js +80 -80
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/TimeWellSpentPage.js +16 -16
- package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js +11 -11
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +11 -11
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +14 -14
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingsPage.js +12 -12
- package/dist/timecampus/pages/projects/ProjectsPage.js +5 -5
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js +8 -8
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +8 -8
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js +8 -8
- package/dist/timecampus/pages/team-activity/TeamActivityPage.js +13 -13
- package/dist/timecampus/pages/time/TimeEntriesPage.js +5 -5
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +138 -138
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +14 -14
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +26 -26
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js +8 -8
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +79 -79
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PersonalCorporatePage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/PersonalCorporatePage.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport {\n Lock,\n Building2,\n Share2,\n ShieldCheck,\n Plug,\n Info,\n ArrowUpRight,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n StatusBadge,\n EmptyState,\n LoadingSkeleton,\n ToastContainer,\n useToast,\n} from \"../../components\";\nimport { cn } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useMyContextSharingPolicyQuery,\n useMySourceConsentsQuery,\n useMyPersonalDataSummaryQuery,\n useUpdateContextSharingPolicyMutation,\n useGrantSourceConsentMutation,\n useRevokeSourceConsentMutation,\n type MyPersonalDataSummaryQuery,\n type MySourceConsentsQuery,\n} from \"@/generated/wspace-operations\";\nimport {\n lifeDomainLabel,\n formatSecondsHuman,\n isoDaysAgo,\n isoNow,\n} from \"./domains\";\n\ntype PersonalDomainCount =\n MyPersonalDataSummaryQuery[\"myPersonalDataSummary\"][\"byDomain\"][number];\ntype SourceConsent = MySourceConsentsQuery[\"mySourceConsents\"][number];\n\n/** The three aggregate work signals the user can share up to their workspace. */\ntype PolicyKey = \"sustainableHours\" | \"recoveryBand\" | \"workLifeBoundary\";\n\nconst POLICY_TOGGLES: {\n key: PolicyKey;\n label: string;\n description: string;\n}[] = [\n {\n key: \"sustainableHours\",\n label: \"Sustainable hours\",\n description:\n \"A yes/no signal that your working hours sit within a healthy range — never the hours themselves.\",\n },\n {\n key: \"recoveryBand\",\n label: \"Recovery band\",\n description:\n \"Whether you're getting enough recovery between work blocks, as a single band — not what you did to recover.\",\n },\n {\n key: \"workLifeBoundary\",\n label: \"Work-life boundary\",\n description:\n \"Whether work is staying inside the hours you've set aside for it — a boundary flag, nothing more.\",\n },\n];\n\n/** Turn a raw enum/source token like \"SUSTAINABLE_HOURS\" or \"onDevice\" into prose. */\nfunction humanizeToken(raw: string): string {\n const spaced = raw\n .replace(/[_-]+/g, \" \")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .trim()\n .toLowerCase();\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n\n/**\n * A small, token-styled toggle switch. Kept local to this page (per brief) —\n * a headless button with `role=\"switch\"` so it stays accessible and themable.\n * No hover movement: the track only changes colour.\n */\nfunction ToggleSwitch({\n checked,\n onChange,\n disabled = false,\n ariaLabel,\n}: {\n checked: boolean;\n onChange: (next: boolean) => void;\n disabled?: boolean;\n ariaLabel: string;\n}) {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n aria-label={ariaLabel}\n disabled={disabled}\n onClick={() => onChange(!checked)}\n className={cn(\n \"relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border transition-colors\",\n \"focus:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50 focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface\",\n checked\n ? \"border-transparent bg-action-primary-bg\"\n : \"border-border-subtle bg-bg-sunken\",\n disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <span\n className={cn(\n \"inline-block h-4 w-4 transform rounded-full shadow-sm transition-transform motion-reduce:transition-none\",\n checked\n ? \"translate-x-6 bg-action-primary-text\"\n : \"translate-x-1 bg-bg-surface\",\n )}\n />\n </button>\n );\n}\n\nexport function PersonalCorporatePage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n\n // Keep the range identity stable so `isoNow()` doesn't trigger refetch loops.\n const range = useMemo(() => ({ from: isoDaysAgo(30), to: isoNow() }), []);\n\n const {\n data: policyData,\n loading: policyLoading,\n error: policyError,\n } = useMyContextSharingPolicyQuery({ skip: !workspaceId });\n\n const {\n data: summaryData,\n loading: summaryLoading,\n error: summaryError,\n refetch: refetchSummary,\n } = useMyPersonalDataSummaryQuery({\n variables: { from: range.from, to: range.to },\n skip: !workspaceId,\n });\n\n const {\n data: consentsData,\n loading: consentsLoading,\n error: consentsError,\n refetch: refetchConsents,\n } = useMySourceConsentsQuery({ skip: !workspaceId });\n\n const [updatePolicy] = useUpdateContextSharingPolicyMutation();\n const [grantConsent] = useGrantSourceConsentMutation();\n const [revokeConsent] = useRevokeSourceConsentMutation();\n\n const [savingPolicyKey, setSavingPolicyKey] = useState<PolicyKey | null>(\n null,\n );\n const [savingSource, setSavingSource] = useState<string | null>(null);\n const [savingOnDeviceSource, setSavingOnDeviceSource] = useState<\n string | null\n >(null);\n\n const policy = policyData?.myContextSharingPolicy;\n const summary = summaryData?.myPersonalDataSummary;\n const consents: SourceConsent[] = consentsData?.mySourceConsents ?? [];\n\n // Biggest slices of private time first — most meaningful at a glance.\n const personalDomains = useMemo<PersonalDomainCount[]>(() => {\n const list = summary?.byDomain ?? [];\n return [...list].sort((a, b) => b.totalSeconds - a.totalSeconds);\n }, [summary]);\n\n const maxDomainSeconds = personalDomains[0]?.totalSeconds ?? 0;\n const sharedAggregates: string[] = summary?.sharedAggregates ?? [];\n\n const pageProps = {\n title: \"Personal & Corporate\",\n description: \"Two contexts, one boundary you control\",\n purpose:\n \"TimeCampus keeps two worlds apart: your private life record, which stays yours, and the workspace you work in. This is where you decide exactly which aggregate work signals — never your personal context — travel up to your employer.\",\n } as const;\n\n const initialLoading =\n !workspaceId ||\n (policyLoading && !policyData) ||\n (summaryLoading && !summaryData);\n\n if (initialLoading) {\n return (\n <PageLayout {...pageProps}>\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <LoadingSkeleton variant=\"card\" count={1} />\n <LoadingSkeleton variant=\"card\" count={1} />\n <LoadingSkeleton variant=\"card\" count={1} />\n </div>\n </PageLayout>\n );\n }\n\n // Both core surfaces failed and we have nothing to show — calm, honest error.\n if (policyError && summaryError && !policy && !summary) {\n return (\n <PageLayout {...pageProps}>\n <div className=\"rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n We couldn't load your privacy settings right now. Please try again in\n a moment.\n </div>\n </PageLayout>\n );\n }\n\n const handlePolicyToggle = async (key: PolicyKey, next: boolean) => {\n if (!policy) return;\n const label = POLICY_TOGGLES.find((t) => t.key === key)?.label ?? \"signal\";\n setSavingPolicyKey(key);\n try {\n const res = await updatePolicy({\n variables: {\n input: {\n sustainableHours: policy.sustainableHours,\n recoveryBand: policy.recoveryBand,\n workLifeBoundary: policy.workLifeBoundary,\n [key]: next,\n },\n },\n });\n if (res.error) throw new Error(res.error.message);\n // Shared-aggregate list can change with the policy — keep Corporate honest.\n await refetchSummary();\n addToast({\n title: next ? `Now sharing \"${label}\"` : `Stopped sharing \"${label}\"`,\n description:\n \"Your workspace only ever sees this as an aggregate signal.\",\n variant: \"success\",\n });\n } catch {\n addToast({\n title: \"Couldn't update what you share\",\n description: \"Nothing was changed. Please try again.\",\n variant: \"error\",\n });\n } finally {\n setSavingPolicyKey(null);\n }\n };\n\n // BOFF-4202: let the user actually flip onDeviceOnly, not just see it.\n // Re-grants with the same mode + the flipped flag; grantSourceConsent's\n // upsert preserves `granted:true` on this path (only meaningful to toggle\n // on an already-connected source), so this never re-enables a revoked one.\n const handleOnDeviceOnlyToggle = async (\n consent: SourceConsent,\n next: boolean,\n ) => {\n const label = humanizeToken(consent.source);\n setSavingOnDeviceSource(consent.source);\n try {\n const res = await grantConsent({\n variables: {\n source: consent.source,\n mode: consent.mode,\n onDeviceOnly: next,\n },\n });\n if (res.error) throw new Error(res.error.message);\n await refetchConsents();\n addToast({\n title: next\n ? `${label} now on-device only`\n : `${label} on-device-only turned off`,\n description: next\n ? \"Only aggregated app/category/timing data reaches your workspace — no titles, URLs, or other details.\"\n : undefined,\n variant: \"success\",\n });\n } catch {\n addToast({\n title: \"Couldn't update on-device-only\",\n description: \"Nothing was changed. Please try again.\",\n variant: \"error\",\n });\n } finally {\n setSavingOnDeviceSource(null);\n }\n };\n\n const handleConsentToggle = async (consent: SourceConsent, next: boolean) => {\n const label = humanizeToken(consent.source);\n setSavingSource(consent.source);\n try {\n if (next) {\n const res = await grantConsent({\n variables: { source: consent.source },\n });\n if (res.error) throw new Error(res.error.message);\n } else {\n const res = await revokeConsent({\n variables: { source: consent.source },\n });\n if (res.error) throw new Error(res.error.message);\n }\n await refetchConsents();\n addToast({\n title: next ? `${label} connected` : `${label} disconnected`,\n variant: \"success\",\n });\n } catch {\n addToast({\n title: \"Couldn't update this source\",\n description: \"Nothing was changed. Please try again.\",\n variant: \"error\",\n });\n } finally {\n setSavingSource(null);\n }\n };\n\n return (\n <PageLayout {...pageProps}>\n {/* Three contexts: your private record · what you share · the workspace. */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* ── Personal (private to you) ─────────────────────────────────── */}\n <GlassCard treatment=\"glass\" className=\"flex flex-col p-4 sm:p-6\">\n <div className=\"mb-4 flex items-start gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Lock className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n Personal{\" \"}\n <span className=\"font-normal text-text-secondary\">\n (private to you)\n </span>\n </h2>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Everything you capture about your life. Locked to your account —\n it never leaves this side.\n </p>\n </div>\n </div>\n\n {summaryError && !summary ? (\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-3 text-xs text-status-error-text\">\n Couldn't load your private data summary right now.\n </div>\n ) : !summary || summary.totalPersonalEvents === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"Nothing private captured yet\"\n description=\"Log your first life entry and it will live here — always private to you.\"\n />\n ) : (\n <div className=\"flex flex-1 flex-col gap-4\">\n <StatCard\n title=\"Private life entries\"\n value={summary.totalPersonalEvents.toLocaleString()}\n icon={<Lock className=\"h-5 w-5\" />}\n description=\"captured in the last 30 days\"\n />\n\n {personalDomains.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n No life domains logged in this window yet.\n </p>\n ) : (\n <ul className=\"space-y-2.5\">\n {personalDomains.map((d) => {\n const pct =\n maxDomainSeconds > 0\n ? Math.round((d.totalSeconds / maxDomainSeconds) * 100)\n : 0;\n return (\n <li\n key={d.domain}\n className=\"rounded-lg border border-border-subtle bg-bg-sunken/40 p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {lifeDomainLabel(d.domain)}\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {formatSecondsHuman(d.totalSeconds)} ·{\" \"}\n {d.eventCount.toLocaleString()}{\" \"}\n {d.eventCount === 1 ? \"entry\" : \"entries\"}\n </p>\n </div>\n <Lock\n className=\"h-3.5 w-3.5 shrink-0 text-text-muted\"\n aria-label=\"Private to you\"\n />\n </div>\n {/* Data-driven share bar of this window's private time. */}\n <div className=\"mt-2 h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg/60\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n )}\n </GlassCard>\n\n {/* ── What you share up to work ─────────────────────────────────── */}\n <div className=\"flex flex-col gap-4\">\n <GlassCard\n treatment=\"insight\"\n glow\n className=\"flex flex-col p-4 sm:p-6\"\n >\n <div className=\"mb-4 flex items-start gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-status-info-bg/40 text-status-info-text\">\n <Share2 className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n What you share up to work\n </h2>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Aggregate signals only. Every switch is off until you turn it\n on.\n </p>\n </div>\n </div>\n\n {policyError && !policy ? (\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-3 text-xs text-status-error-text\">\n Couldn't load your sharing settings right now.\n </div>\n ) : (\n <div className=\"space-y-3\">\n {POLICY_TOGGLES.map((toggle) => {\n const checked = policy ? policy[toggle.key] : false;\n const saving = savingPolicyKey === toggle.key;\n return (\n <div\n key={toggle.key}\n className=\"rounded-lg border border-border-subtle bg-bg-sunken/40 p-3\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n {toggle.label}\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {toggle.description}\n </p>\n </div>\n <div className=\"flex shrink-0 flex-col items-end gap-1\">\n <ToggleSwitch\n checked={checked}\n disabled={saving || !policy}\n ariaLabel={`Share \"${toggle.label}\" with your workspace`}\n onChange={(next) =>\n handlePolicyToggle(toggle.key, next)\n }\n />\n <span\n className={cn(\n \"text-[0.65rem] font-medium uppercase tracking-wide\",\n checked\n ? \"text-status-success-text\"\n : \"text-text-muted\",\n )}\n >\n {saving ? \"Saving…\" : checked ? \"On\" : \"Off\"}\n </span>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </GlassCard>\n\n {/* Strong, unmissable reassurance. */}\n <GlassCard\n treatment=\"metric\"\n className=\"border-status-success-border/40 p-4\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-status-success-bg/40 text-status-success-text\">\n <ShieldCheck className=\"h-5 w-5\" />\n </div>\n <p className=\"text-sm font-semibold text-text-primary\">\n Your employer never sees your personal context.\n </p>\n </div>\n </GlassCard>\n </div>\n\n {/* ── Corporate (your workspace) ────────────────────────────────── */}\n <GlassCard treatment=\"glass\" className=\"flex flex-col p-4 sm:p-6\">\n <div className=\"mb-4 flex items-start gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Building2 className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n Corporate{\" \"}\n <span className=\"font-normal text-text-secondary\">\n (your workspace)\n </span>\n </h2>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n What the workspace can actually see about you.\n </p>\n </div>\n </div>\n\n <div className=\"flex flex-1 flex-col gap-4\">\n <div className=\"flex items-start gap-2 rounded-lg border border-status-info-border/40 bg-status-info-bg/10 px-3 py-2.5 text-xs text-status-info-text\">\n <Info className=\"mt-0.5 h-3.5 w-3.5 shrink-0\" />\n <span>\n Your workspace only ever receives aggregate work signals —\n coarse yes/no bands, never entries, locations, health or\n timestamps.\n </span>\n </div>\n\n <div>\n <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Aggregate signals visible to work\n </p>\n {sharedAggregates.length === 0 ? (\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/40 p-4 text-center\">\n <p className=\"text-sm font-medium text-text-primary\">\n Nothing is shared right now\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Turn on a signal in the middle column and it will appear\n here.\n </p>\n </div>\n ) : (\n <ul className=\"space-y-2\">\n {sharedAggregates.map((agg) => (\n <li\n key={agg}\n className=\"flex items-center justify-between gap-3 rounded-lg border border-border-subtle bg-bg-sunken/40 px-3 py-2.5\"\n >\n <span className=\"flex min-w-0 items-center gap-2 text-sm text-text-primary\">\n <ArrowUpRight className=\"h-4 w-4 shrink-0 text-status-success-text\" />\n <span className=\"truncate\">{humanizeToken(agg)}</span>\n </span>\n <StatusBadge variant=\"success\" label=\"Shared\" dot />\n </li>\n ))}\n </ul>\n )}\n </div>\n\n <p className=\"mt-auto text-xs text-text-muted\">\n This mirrors exactly what you've allowed — change it any time from\n the switches on the left.\n </p>\n </div>\n </GlassCard>\n </div>\n\n {/* ── Connected sources & consent ─────────────────────────────────── */}\n <GlassCard treatment=\"glass\" className=\"mt-6 p-4 sm:p-6\">\n <div className=\"mb-4 flex items-start gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Plug className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Connected sources\n </h3>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Each capture source only feeds your private record once you\n consent — and you can withdraw at any moment.\n </p>\n </div>\n </div>\n\n {consentsError && consents.length === 0 ? (\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-3 text-xs text-status-error-text\">\n Couldn't load your connected sources right now.\n </div>\n ) : consentsLoading && consents.length === 0 ? (\n <LoadingSkeleton variant=\"list\" count={3} />\n ) : consents.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No sources connected yet\"\n description=\"When you connect a capture source — a calendar, location or health signal — you'll grant and revoke exactly what it may feed here.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {consents.map((consent) => {\n const saving = savingSource === consent.source;\n return (\n <GlassCard key={consent.id} treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {humanizeToken(consent.source)}\n </p>\n <div className=\"mt-1.5 flex flex-wrap items-center gap-1.5\">\n <StatusBadge\n variant=\"muted\"\n label={humanizeToken(consent.mode)}\n />\n {consent.granted && (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={consent.onDeviceOnly}\n disabled={savingOnDeviceSource === consent.source}\n onClick={() =>\n handleOnDeviceOnlyToggle(\n consent,\n !consent.onDeviceOnly,\n )\n }\n className={cn(\n \"rounded-full border px-2 py-0.5 text-[0.65rem] font-medium uppercase tracking-wide transition-colors\",\n \"focus:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50\",\n consent.onDeviceOnly\n ? \"border-status-info-border/60 bg-status-info-bg/40 text-status-info-text\"\n : \"border-border-subtle bg-bg-sunken/40 text-text-muted hover:text-text-secondary\",\n savingOnDeviceSource === consent.source &&\n \"cursor-not-allowed opacity-50\",\n )}\n >\n {savingOnDeviceSource === consent.source\n ? \"Saving…\"\n : consent.onDeviceOnly\n ? \"On-device only\"\n : \"Turn on on-device only\"}\n </button>\n )}\n {consent.retentionDays != null && (\n <StatusBadge\n variant=\"muted\"\n label={`Kept ${consent.retentionDays}d`}\n />\n )}\n </div>\n </div>\n <div className=\"flex shrink-0 flex-col items-end gap-1\">\n <ToggleSwitch\n checked={consent.granted}\n disabled={saving}\n ariaLabel={`Consent for ${humanizeToken(consent.source)}`}\n onChange={(next) => handleConsentToggle(consent, next)}\n />\n <span\n className={cn(\n \"text-[0.65rem] font-medium uppercase tracking-wide\",\n consent.granted\n ? \"text-status-success-text\"\n : \"text-text-muted\",\n )}\n >\n {saving\n ? \"Saving…\"\n : consent.granted\n ? \"Granted\"\n : \"Revoked\"}\n </span>\n </div>\n </div>\n </GlassCard>\n );\n })}\n </div>\n )}\n </GlassCard>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA8CA,IAAM,IAIA;CACJ;EACE,KAAK;EACL,OAAO;EACP,aACE;CACJ;CACA;EACE,KAAK;EACL,OAAO;EACP,aACE;CACJ;CACA;EACE,KAAK;EACL,OAAO;EACP,aACE;CACJ;AACF;AAGA,SAAS,EAAc,GAAqB;CAC1C,IAAM,IAAS,EACZ,QAAQ,UAAU,GAAG,CAAC,CACtB,QAAQ,sBAAsB,OAAO,CAAC,CACtC,KAAK,CAAC,CACN,YAAY;CACf,OAAO,EAAO,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,EAAO,MAAM,CAAC;AACxD;AAOA,SAAS,EAAa,EACpB,YACA,aACA,cAAW,IACX,gBAMC;CACD,OACE,kBAAC,UAAD;EACE,MAAK;EACL,MAAK;EACL,gBAAc;EACd,cAAY;EACF;EACV,eAAe,EAAS,CAAC,CAAO;EAChC,WAAW,EACT,6FACA,oJACA,IACI,4CACA,qCACJ,KAAY,+BACd;EAEA,UAAA,kBAAC,QAAD,EACE,WAAW,EACT,4GACA,IACI,yCACA,6BACN,EACD,CAAA;CACK,CAAA;AAEZ;AAEA,SAAgB,IAAwB;CACtC,IAAM,EAAE,iBAAc,OAAO,EAAc,GACrC,EAAE,YAAQ,aAAU,qBAAiB,GAAS,GAG9C,IAAQ,SAAe;EAAE,MAAM,GAAW,EAAE;EAAG,IAAI,GAAO;CAAE,IAAI,CAAC,CAAC,GAElE,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,EAA+B,EAAE,MAAM,CAAC,EAAY,CAAC,GAEnD,EACJ,MAAM,GACN,SAAS,GACT,OAAO,GACP,SAAS,MACP,GAA8B;EAChC,WAAW;GAAE,MAAM,EAAM;GAAM,IAAI,EAAM;EAAG;EAC5C,MAAM,CAAC;CACT,CAAC,GAEK,EACJ,MAAM,GACN,SAAS,GACT,OAAO,GACP,SAAS,MACP,EAAyB,EAAE,MAAM,CAAC,EAAY,CAAC,GAE7C,CAAC,KAAgB,EAAsC,GACvD,CAAC,KAAgB,EAA8B,GAC/C,CAAC,KAAiB,EAA+B,GAEjD,CAAC,GAAiB,KAAsB,EAC5C,IACF,GACM,CAAC,IAAc,KAAmB,EAAwB,IAAI,GAC9D,CAAC,GAAsB,KAA2B,EAEtD,IAAI,GAEA,IAAS,GAAY,wBACrB,IAAU,GAAa,uBACvB,IAA4B,GAAc,oBAAoB,CAAC,GAG/D,IAAkB,QAEf,CAAC,GADK,GAAS,YAAY,CAAC,CACpB,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,eAAe,EAAE,YAAY,GAC9D,CAAC,CAAO,CAAC,GAEN,IAAmB,EAAgB,EAAE,EAAE,gBAAgB,GACvD,IAA6B,GAAS,oBAAoB,CAAC,GAE3D,IAAY;EAChB,OAAO;EACP,aAAa;EACb,SACE;CACJ;CAOA,IAJE,CAAC,KACA,KAAiB,CAAC,KAClB,KAAkB,CAAC,GAGpB,OACE,kBAAC,GAAD;EAAY,GAAI;EACd,UAAA,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,kBAAC,GAAD;KAAiB,SAAQ;KAAO,OAAO;IAAI,CAAA;IAC3C,kBAAC,GAAD;KAAiB,SAAQ;KAAO,OAAO;IAAI,CAAA;IAC3C,kBAAC,GAAD;KAAiB,SAAQ;KAAO,OAAO;IAAI,CAAA;GACxC;;CACK,CAAA;CAKhB,IAAI,KAAe,KAAgB,CAAC,KAAU,CAAC,GAC7C,OACE,kBAAC,GAAD;EAAY,GAAI;EACd,UAAA,kBAAC,OAAD;GAAK,WAAU;GAA2G,UAAA;EAGrH,CAAA;CACK,CAAA;CAIhB,IAAM,KAAqB,OAAO,GAAgB,MAAkB;EAClE,IAAI,CAAC,GAAQ;EACb,IAAM,IAAQ,EAAe,MAAM,MAAM,EAAE,QAAQ,CAAG,CAAC,EAAE,SAAS;EAClE,EAAmB,CAAG;EACtB,IAAI;GACF,IAAM,IAAM,MAAM,EAAa,EAC7B,WAAW,EACT,OAAO;IACL,kBAAkB,EAAO;IACzB,cAAc,EAAO;IACrB,kBAAkB,EAAO;KACxB,IAAM;GACT,EACF,EACF,CAAC;GACD,IAAI,EAAI,OAAO,MAAU,MAAM,EAAI,MAAM,OAAO;GAGhD,AADA,MAAM,EAAe,GACrB,EAAS;IACP,OAAO,IAAO,gBAAgB,EAAM,KAAK,oBAAoB,EAAM;IACnE,aACE;IACF,SAAS;GACX,CAAC;EACH,QAAQ;GACN,EAAS;IACP,OAAO;IACP,aAAa;IACb,SAAS;GACX,CAAC;EACH,UAAU;GACR,EAAmB,IAAI;EACzB;CACF,GAMM,KAA2B,OAC/B,GACA,MACG;EACH,IAAM,IAAQ,EAAc,EAAQ,MAAM;EAC1C,EAAwB,EAAQ,MAAM;EACtC,IAAI;GACF,IAAM,IAAM,MAAM,EAAa,EAC7B,WAAW;IACT,QAAQ,EAAQ;IAChB,MAAM,EAAQ;IACd,cAAc;GAChB,EACF,CAAC;GACD,IAAI,EAAI,OAAO,MAAU,MAAM,EAAI,MAAM,OAAO;GAEhD,AADA,MAAM,EAAgB,GACtB,EAAS;IACP,OAAO,IACH,GAAG,EAAM,uBACT,GAAG,EAAM;IACb,aAAa,IACT,yGACA,KAAA;IACJ,SAAS;GACX,CAAC;EACH,QAAQ;GACN,EAAS;IACP,OAAO;IACP,aAAa;IACb,SAAS;GACX,CAAC;EACH,UAAU;GACR,EAAwB,IAAI;EAC9B;CACF,GAEM,KAAsB,OAAO,GAAwB,MAAkB;EAC3E,IAAM,IAAQ,EAAc,EAAQ,MAAM;EAC1C,EAAgB,EAAQ,MAAM;EAC9B,IAAI;GACF,IAAI,GAAM;IACR,IAAM,IAAM,MAAM,EAAa,EAC7B,WAAW,EAAE,QAAQ,EAAQ,OAAO,EACtC,CAAC;IACD,IAAI,EAAI,OAAO,MAAU,MAAM,EAAI,MAAM,OAAO;GAClD,OAAO;IACL,IAAM,IAAM,MAAM,EAAc,EAC9B,WAAW,EAAE,QAAQ,EAAQ,OAAO,EACtC,CAAC;IACD,IAAI,EAAI,OAAO,MAAU,MAAM,EAAI,MAAM,OAAO;GAClD;GAEA,AADA,MAAM,EAAgB,GACtB,EAAS;IACP,OAAO,IAAO,GAAG,EAAM,cAAc,GAAG,EAAM;IAC9C,SAAS;GACX,CAAC;EACH,QAAQ;GACN,EAAS;IACP,OAAO;IACP,aAAa;IACb,SAAS;GACX,CAAC;EACH,UAAU;GACR,EAAgB,IAAI;EACtB;CACF;CAEA,OACE,kBAAC,GAAD;EAAY,GAAI;EAAhB,UAAA;GAEE,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KAEE,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;MAAvC,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;QACb,UAAA,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;OACxB,CAAA,GACL,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;SAAd,UAAA;UAAwD;UAC7C;UACT,kBAAC,QAAD;WAAM,WAAU;WAAkC,UAAA;UAE5C,CAAA;SACJ;QACJ,CAAA,GAAA,kBAAC,KAAD;SAAG,WAAU;SAAqC,UAAA;QAG/C,CAAA,CACA;OACF,CAAA,CAAA;MAEJ,CAAA,GAAA,KAAgB,CAAC,IAChB,kBAAC,OAAD;OAAK,WAAU;OAA2G,UAAA;MAErH,CAAA,IACH,CAAC,KAAW,EAAQ,wBAAwB,IAC9C,kBAAC,GAAD;OACE,cAAa;OACb,OAAM;OACN,aAAY;MACb,CAAA,IAED,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,GAAD;QACE,OAAM;QACN,OAAO,EAAQ,oBAAoB,eAAe;QAClD,MAAM,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;QACjC,aAAY;OACb,CAAA,GAEA,EAAgB,WAAW,IAC1B,kBAAC,KAAD;QAAG,WAAU;QAA8B,UAAA;OAExC,CAAA,IAEH,kBAAC,MAAD;QAAI,WAAU;QACX,UAAA,EAAgB,KAAK,MAAM;SAC1B,IAAM,IACJ,IAAmB,IACf,KAAK,MAAO,EAAE,eAAe,IAAoB,GAAG,IACpD;SACN,OACE,kBAAC,MAAD;UAEE,WAAU;UAFZ,UAAA,CAIE,kBAAC,OAAD;WAAK,WAAU;WAAf,UAAA,CACE,kBAAC,OAAD;YAAK,WAAU;YAAf,UAAA,CACE,kBAAC,KAAD;aAAG,WAAU;aACV,UAAA,GAAgB,EAAE,MAAM;YACxB,CAAA,GACH,kBAAC,KAAD;aAAG,WAAU;aAAb,UAAA;cACG,GAAmB,EAAE,YAAY;cAAE;cAAG;cACtC,EAAE,WAAW,eAAe;cAAG;cAC/B,EAAE,eAAe,IAAI,UAAU;aAC/B;YACA,CAAA,CAAA;WACL,CAAA,GAAA,kBAAC,GAAD;YACE,WAAU;YACV,cAAW;WACZ,CAAA,CACE;UAEL,CAAA,GAAA,kBAAC,OAAD;WAAK,WAAU;WACb,UAAA,kBAAC,OAAD;YACE,WAAU;YACV,OAAO,EAAE,OAAO,GAAG,EAAI,GAAG;WAC3B,CAAA;UACE,CAAA,CACH;SA1BG,GAAA,EAAE,MA0BL;QAER,CAAC;OACC,CAAA,CAEH;MAEE,CAAA,CAAA;;KAGX,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,GAAD;OACE,WAAU;OACV,MAAA;OACA,WAAU;OAHZ,UAAA,CAKE,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC,OAAD;SAAK,WAAU;SACb,UAAA,kBAAC,IAAD,EAAQ,WAAU,UAAW,CAAA;QAC1B,CAAA,GACL,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;UAA0C,UAAA;SAEpD,CAAA,GACJ,kBAAC,KAAD;UAAG,WAAU;UAAqC,UAAA;SAG/C,CAAA,CACA;QACF,CAAA,CAAA;OAEJ,CAAA,GAAA,KAAe,CAAC,IACf,kBAAC,OAAD;QAAK,WAAU;QAA2G,UAAA;OAErH,CAAA,IAEL,kBAAC,OAAD;QAAK,WAAU;QACZ,UAAA,EAAe,KAAK,MAAW;SAC9B,IAAM,IAAU,IAAS,EAAO,EAAO,OAAO,IACxC,IAAS,MAAoB,EAAO;SAC1C,OACE,kBAAC,OAAD;UAEE,WAAU;UAEV,UAAA,kBAAC,OAAD;WAAK,WAAU;WAAf,UAAA,CACE,kBAAC,OAAD;YAAK,WAAU;YAAf,UAAA,CACE,kBAAC,KAAD;aAAG,WAAU;aACV,UAAA,EAAO;YACP,CAAA,GACH,kBAAC,KAAD;aAAG,WAAU;aACV,UAAA,EAAO;YACP,CAAA,CACA;WACL,CAAA,GAAA,kBAAC,OAAD;YAAK,WAAU;YAAf,UAAA,CACE,kBAAC,GAAD;aACW;aACT,UAAU,KAAU,CAAC;aACrB,WAAW,UAAU,EAAO,MAAM;aAClC,WAAW,MACT,GAAmB,EAAO,KAAK,CAAI;YAEtC,CAAA,GACD,kBAAC,QAAD;aACE,WAAW,EACT,sDACA,IACI,6BACA,iBACN;aAEC,UAAA,IAAS,YAAY,IAAU,OAAO;YACnC,CAAA,CACH;WACF,CAAA,CAAA;;SACF,GAjCE,EAAO,GAiCT;QAET,CAAC;OACE,CAAA,CAEE;MAGX,CAAA,GAAA,kBAAC,GAAD;OACE,WAAU;OACV,WAAU;OAEV,UAAA,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC,OAAD;SAAK,WAAU;SACb,UAAA,kBAAC,IAAD,EAAa,WAAU,UAAW,CAAA;QAC/B,CAAA,GACL,kBAAC,KAAD;SAAG,WAAU;SAA0C,UAAA;QAEpD,CAAA,CACA;;MACI,CAAA,CACR;;KAGL,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;MAAvC,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;QACb,UAAA,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;OAC7B,CAAA,GACL,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;SAAd,UAAA;UAAwD;UAC5C;UACV,kBAAC,QAAD;WAAM,WAAU;WAAkC,UAAA;UAE5C,CAAA;SACJ;QACJ,CAAA,GAAA,kBAAC,KAAD;SAAG,WAAU;SAAqC,UAAA;QAE/C,CAAA,CACA;OACF,CAAA,CAAA;MAEL,CAAA,GAAA,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA;QACE,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,8BAA+B,CAAA,GAC/C,kBAAC,QAAD,EAAA,UAAM,kIAIA,CAAA,CACH;;QAEL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;SAAmE,UAAA;QAE7E,CAAA,GACF,EAAiB,WAAW,IAC3B,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,kBAAC,KAAD;UAAG,WAAU;UAAwC,UAAA;SAElD,CAAA,GACH,kBAAC,KAAD;UAAG,WAAU;UAAmC,UAAA;SAG7C,CAAA,CACA;QAEL,CAAA,IAAA,kBAAC,MAAD;SAAI,WAAU;SACX,UAAA,EAAiB,KAAK,MACrB,kBAAC,MAAD;UAEE,WAAU;UAFZ,UAAA,CAIE,kBAAC,QAAD;WAAM,WAAU;WAAhB,UAAA,CACE,kBAAC,GAAD,EAAc,WAAU,4CAA6C,CAAA,GACrE,kBAAC,QAAD;YAAM,WAAU;YAAY,UAAA,EAAc,CAAG;WAAQ,CAAA,CACjD;UACN,CAAA,GAAA,kBAAC,GAAD;WAAa,SAAQ;WAAU,OAAM;WAAS,KAAA;UAAK,CAAA,CACjD;SARG,GAAA,CAQH,CACL;QACC,CAAA,CAEH,EAAA,CAAA;QAEL,kBAAC,KAAD;SAAG,WAAU;SAAkC,UAAA;QAG5C,CAAA;OACA;MACI,CAAA,CAAA;;IACR;;GAGL,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;IAAvC,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;MACb,UAAA,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;KACxB,CAAA,GACL,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;OAA0C,UAAA;MAEpD,CAAA,GACJ,kBAAC,KAAD;OAAG,WAAU;OAAqC,UAAA;MAG/C,CAAA,CACA;KACF,CAAA,CAAA;IAEJ,CAAA,GAAA,KAAiB,EAAS,WAAW,IACpC,kBAAC,OAAD;KAAK,WAAU;KAA2G,UAAA;IAErH,CAAA,IACH,KAAmB,EAAS,WAAW,IACzC,kBAAC,GAAD;KAAiB,SAAQ;KAAO,OAAO;IAAI,CAAA,IACzC,EAAS,WAAW,IACtB,kBAAC,GAAD;KACE,cAAa;KACb,OAAM;KACN,aAAY;IACb,CAAA,IAED,kBAAC,OAAD;KAAK,WAAU;KACZ,UAAA,EAAS,KAAK,MAAY;MACzB,IAAM,IAAS,OAAiB,EAAQ;MACxC,OACE,kBAAC,GAAD;OAA4B,WAAU;OAAS,WAAU;OACvD,UAAA,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,kBAAC,KAAD;UAAG,WAAU;UACV,UAAA,EAAc,EAAQ,MAAM;SAC5B,CAAA,GACH,kBAAC,OAAD;UAAK,WAAU;UAAf,UAAA;WACE,kBAAC,GAAD;YACE,SAAQ;YACR,OAAO,EAAc,EAAQ,IAAI;WAClC,CAAA;WACA,EAAQ,WACP,kBAAC,UAAD;YACE,MAAK;YACL,MAAK;YACL,gBAAc,EAAQ;YACtB,UAAU,MAAyB,EAAQ;YAC3C,eACE,GACE,GACA,CAAC,EAAQ,YACX;YAEF,WAAW,EACT,wGACA,mFACA,EAAQ,eACJ,4EACA,kFACJ,MAAyB,EAAQ,UAC/B,+BACJ;YAEC,UAAA,MAAyB,EAAQ,SAC9B,YACA,EAAQ,eACN,mBACA;WACA,CAAA;WAET,EAAQ,iBAAiB,QACxB,kBAAC,GAAD;YACE,SAAQ;YACR,OAAO,QAAQ,EAAQ,cAAc;WACtC,CAAA;UAEA;SACF,CAAA,CAAA;QACL,CAAA,GAAA,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,kBAAC,GAAD;UACE,SAAS,EAAQ;UACjB,UAAU;UACV,WAAW,eAAe,EAAc,EAAQ,MAAM;UACtD,WAAW,MAAS,GAAoB,GAAS,CAAI;SACtD,CAAA,GACD,kBAAC,QAAD;UACE,WAAW,EACT,sDACA,EAAQ,UACJ,6BACA,iBACN;UAEC,UAAA,IACG,YACA,EAAQ,UACN,YACA;SACF,CAAA,CACH;QACF,CAAA,CAAA;;MACI,GAvEK,EAAQ,EAuEb;KAEf,CAAC;IACE,CAAA,CAEE;;GAEX,kBAAC,IAAD;IAAwB;IAAQ,WAAW;GAAe,CAAA;EAChD;;AAEhB"}
|
|
1
|
+
{"version":3,"file":"PersonalCorporatePage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/PersonalCorporatePage.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport {\n Lock,\n Building2,\n Share2,\n ShieldCheck,\n Plug,\n Info,\n ArrowUpRight,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n StatusBadge,\n EmptyState,\n LoadingSkeleton,\n ToastContainer,\n useToast,\n} from \"../../components\";\nimport { cn } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useMyContextSharingPolicyQuery,\n useMySourceConsentsQuery,\n useMyPersonalDataSummaryQuery,\n useUpdateContextSharingPolicyMutation,\n useGrantSourceConsentMutation,\n useRevokeSourceConsentMutation,\n type MyPersonalDataSummaryQuery,\n type MySourceConsentsQuery,\n} from \"@/generated/wspace-operations\";\nimport {\n lifeDomainLabel,\n formatSecondsHuman,\n isoDaysAgo,\n isoNow,\n} from \"./domains\";\n\ntype PersonalDomainCount =\n MyPersonalDataSummaryQuery[\"myPersonalDataSummary\"][\"byDomain\"][number];\ntype SourceConsent = MySourceConsentsQuery[\"mySourceConsents\"][number];\n\n/** The three aggregate work signals the user can share up to their workspace. */\ntype PolicyKey = \"sustainableHours\" | \"recoveryBand\" | \"workLifeBoundary\";\n\nconst POLICY_TOGGLES: {\n key: PolicyKey;\n label: string;\n description: string;\n}[] = [\n {\n key: \"sustainableHours\",\n label: \"Sustainable hours\",\n description:\n \"A yes/no signal that your working hours sit within a healthy range — never the hours themselves.\",\n },\n {\n key: \"recoveryBand\",\n label: \"Recovery band\",\n description:\n \"Whether you're getting enough recovery between work blocks, as a single band — not what you did to recover.\",\n },\n {\n key: \"workLifeBoundary\",\n label: \"Work-life boundary\",\n description:\n \"Whether work is staying inside the hours you've set aside for it — a boundary flag, nothing more.\",\n },\n];\n\n/** Turn a raw enum/source token like \"SUSTAINABLE_HOURS\" or \"onDevice\" into prose. */\nfunction humanizeToken(raw: string): string {\n const spaced = raw\n .replace(/[_-]+/g, \" \")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .trim()\n .toLowerCase();\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n\n/**\n * A small, token-styled toggle switch. Kept local to this page (per brief) —\n * a headless button with `role=\"switch\"` so it stays accessible and themable.\n * No hover movement: the track only changes colour.\n */\nfunction ToggleSwitch({\n checked,\n onChange,\n disabled = false,\n ariaLabel,\n}: {\n checked: boolean;\n onChange: (next: boolean) => void;\n disabled?: boolean;\n ariaLabel: string;\n}) {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n aria-label={ariaLabel}\n disabled={disabled}\n onClick={() => onChange(!checked)}\n className={cn(\n \"relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border transition-colors\",\n \"focus:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50 focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface\",\n checked\n ? \"border-transparent bg-action-primary-bg\"\n : \"border-border-subtle bg-bg-sunken\",\n disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <span\n className={cn(\n \"inline-block h-4 w-4 transform rounded-full shadow-sm transition-transform motion-reduce:transition-none\",\n checked\n ? \"translate-x-6 bg-action-primary-text\"\n : \"translate-x-1 bg-bg-surface\",\n )}\n />\n </button>\n );\n}\n\nexport function PersonalCorporatePage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n\n // Keep the range identity stable so `isoNow()` doesn't trigger refetch loops.\n const range = useMemo(() => ({ from: isoDaysAgo(30), to: isoNow() }), []);\n\n const {\n data: policyData,\n loading: policyLoading,\n error: policyError,\n } = useMyContextSharingPolicyQuery({ skip: !workspaceId });\n\n const {\n data: summaryData,\n loading: summaryLoading,\n error: summaryError,\n refetch: refetchSummary,\n } = useMyPersonalDataSummaryQuery({\n variables: { from: range.from, to: range.to },\n skip: !workspaceId,\n });\n\n const {\n data: consentsData,\n loading: consentsLoading,\n error: consentsError,\n refetch: refetchConsents,\n } = useMySourceConsentsQuery({ skip: !workspaceId });\n\n const [updatePolicy] = useUpdateContextSharingPolicyMutation();\n const [grantConsent] = useGrantSourceConsentMutation();\n const [revokeConsent] = useRevokeSourceConsentMutation();\n\n const [savingPolicyKey, setSavingPolicyKey] = useState<PolicyKey | null>(\n null,\n );\n const [savingSource, setSavingSource] = useState<string | null>(null);\n const [savingOnDeviceSource, setSavingOnDeviceSource] = useState<\n string | null\n >(null);\n\n const policy = policyData?.myContextSharingPolicy;\n const summary = summaryData?.myPersonalDataSummary;\n const consents: SourceConsent[] = consentsData?.mySourceConsents ?? [];\n\n // Biggest slices of private time first — most meaningful at a glance.\n const personalDomains = useMemo<PersonalDomainCount[]>(() => {\n const list = summary?.byDomain ?? [];\n return [...list].sort((a, b) => b.totalSeconds - a.totalSeconds);\n }, [summary]);\n\n const maxDomainSeconds = personalDomains[0]?.totalSeconds ?? 0;\n const sharedAggregates: string[] = summary?.sharedAggregates ?? [];\n\n const pageProps = {\n title: \"Personal & Corporate\",\n description: \"Two contexts, one boundary you control\",\n purpose:\n \"TimeCampus keeps two worlds apart: your private life record, which stays yours, and the workspace you work in. This is where you decide exactly which aggregate work signals — never your personal context — travel up to your employer.\",\n } as const;\n\n const initialLoading =\n !workspaceId ||\n (policyLoading && !policyData) ||\n (summaryLoading && !summaryData);\n\n if (initialLoading) {\n return (\n <PageLayout {...pageProps}>\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <LoadingSkeleton variant=\"card\" count={1} />\n <LoadingSkeleton variant=\"card\" count={1} />\n <LoadingSkeleton variant=\"card\" count={1} />\n </div>\n </PageLayout>\n );\n }\n\n // Both core surfaces failed and we have nothing to show — calm, honest error.\n if (policyError && summaryError && !policy && !summary) {\n return (\n <PageLayout {...pageProps}>\n <div className=\"rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n We couldn't load your privacy settings right now. Please try again in\n a moment.\n </div>\n </PageLayout>\n );\n }\n\n const handlePolicyToggle = async (key: PolicyKey, next: boolean) => {\n if (!policy) return;\n const label = POLICY_TOGGLES.find((t) => t.key === key)?.label ?? \"signal\";\n setSavingPolicyKey(key);\n try {\n const res = await updatePolicy({\n variables: {\n input: {\n sustainableHours: policy.sustainableHours,\n recoveryBand: policy.recoveryBand,\n workLifeBoundary: policy.workLifeBoundary,\n [key]: next,\n },\n },\n });\n if (res.error) throw new Error(res.error.message);\n // Shared-aggregate list can change with the policy — keep Corporate honest.\n await refetchSummary();\n addToast({\n title: next ? `Now sharing \"${label}\"` : `Stopped sharing \"${label}\"`,\n description:\n \"Your workspace only ever sees this as an aggregate signal.\",\n variant: \"success\",\n });\n } catch {\n addToast({\n title: \"Couldn't update what you share\",\n description: \"Nothing was changed. Please try again.\",\n variant: \"error\",\n });\n } finally {\n setSavingPolicyKey(null);\n }\n };\n\n // BOFF-4202: let the user actually flip onDeviceOnly, not just see it.\n // Re-grants with the same mode + the flipped flag; grantSourceConsent's\n // upsert preserves `granted:true` on this path (only meaningful to toggle\n // on an already-connected source), so this never re-enables a revoked one.\n const handleOnDeviceOnlyToggle = async (\n consent: SourceConsent,\n next: boolean,\n ) => {\n const label = humanizeToken(consent.source);\n setSavingOnDeviceSource(consent.source);\n try {\n const res = await grantConsent({\n variables: {\n source: consent.source,\n mode: consent.mode,\n onDeviceOnly: next,\n },\n });\n if (res.error) throw new Error(res.error.message);\n await refetchConsents();\n addToast({\n title: next\n ? `${label} now on-device only`\n : `${label} on-device-only turned off`,\n description: next\n ? \"Only aggregated app/category/timing data reaches your workspace — no titles, URLs, or other details.\"\n : undefined,\n variant: \"success\",\n });\n } catch {\n addToast({\n title: \"Couldn't update on-device-only\",\n description: \"Nothing was changed. Please try again.\",\n variant: \"error\",\n });\n } finally {\n setSavingOnDeviceSource(null);\n }\n };\n\n const handleConsentToggle = async (consent: SourceConsent, next: boolean) => {\n const label = humanizeToken(consent.source);\n setSavingSource(consent.source);\n try {\n if (next) {\n const res = await grantConsent({\n variables: { source: consent.source },\n });\n if (res.error) throw new Error(res.error.message);\n } else {\n const res = await revokeConsent({\n variables: { source: consent.source },\n });\n if (res.error) throw new Error(res.error.message);\n }\n await refetchConsents();\n addToast({\n title: next ? `${label} connected` : `${label} disconnected`,\n variant: \"success\",\n });\n } catch {\n addToast({\n title: \"Couldn't update this source\",\n description: \"Nothing was changed. Please try again.\",\n variant: \"error\",\n });\n } finally {\n setSavingSource(null);\n }\n };\n\n return (\n <PageLayout {...pageProps}>\n {/* Three contexts: your private record · what you share · the workspace. */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* ── Personal (private to you) ─────────────────────────────────── */}\n <GlassCard treatment=\"glass\" className=\"flex flex-col p-4 sm:p-6\">\n <div className=\"mb-4 flex items-start gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Lock className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n Personal{\" \"}\n <span className=\"font-normal text-text-secondary\">\n (private to you)\n </span>\n </h2>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Everything you capture about your life. Locked to your account —\n it never leaves this side.\n </p>\n </div>\n </div>\n\n {summaryError && !summary ? (\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-3 text-xs text-status-error-text\">\n Couldn't load your private data summary right now.\n </div>\n ) : !summary || summary.totalPersonalEvents === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"Nothing private captured yet\"\n description=\"Log your first life entry and it will live here — always private to you.\"\n />\n ) : (\n <div className=\"flex flex-1 flex-col gap-4\">\n <StatCard\n title=\"Private life entries\"\n value={summary.totalPersonalEvents.toLocaleString()}\n icon={<Lock className=\"h-5 w-5\" />}\n description=\"captured in the last 30 days\"\n />\n\n {personalDomains.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n No life domains logged in this window yet.\n </p>\n ) : (\n <ul className=\"space-y-2.5\">\n {personalDomains.map((d) => {\n const pct =\n maxDomainSeconds > 0\n ? Math.round((d.totalSeconds / maxDomainSeconds) * 100)\n : 0;\n return (\n <li\n key={d.domain}\n className=\"rounded-lg border border-border-subtle bg-bg-sunken/40 p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {lifeDomainLabel(d.domain)}\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {formatSecondsHuman(d.totalSeconds)} ·{\" \"}\n {d.eventCount.toLocaleString()}{\" \"}\n {d.eventCount === 1 ? \"entry\" : \"entries\"}\n </p>\n </div>\n <Lock\n className=\"h-3.5 w-3.5 shrink-0 text-text-muted\"\n aria-label=\"Private to you\"\n />\n </div>\n {/* Data-driven share bar of this window's private time. */}\n <div className=\"mt-2 h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg/60\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n )}\n </GlassCard>\n\n {/* ── What you share up to work ─────────────────────────────────── */}\n <div className=\"flex flex-col gap-4\">\n <GlassCard\n treatment=\"insight\"\n glow\n className=\"flex flex-col p-4 sm:p-6\"\n >\n <div className=\"mb-4 flex items-start gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-status-info-bg/40 text-status-info-text\">\n <Share2 className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n What you share up to work\n </h2>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Aggregate signals only. Every switch is off until you turn it\n on.\n </p>\n </div>\n </div>\n\n {policyError && !policy ? (\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-3 text-xs text-status-error-text\">\n Couldn't load your sharing settings right now.\n </div>\n ) : (\n <div className=\"space-y-3\">\n {POLICY_TOGGLES.map((toggle) => {\n const checked = policy ? policy[toggle.key] : false;\n const saving = savingPolicyKey === toggle.key;\n return (\n <div\n key={toggle.key}\n className=\"rounded-lg border border-border-subtle bg-bg-sunken/40 p-3\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n {toggle.label}\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {toggle.description}\n </p>\n </div>\n <div className=\"flex shrink-0 flex-col items-end gap-1\">\n <ToggleSwitch\n checked={checked}\n disabled={saving || !policy}\n ariaLabel={`Share \"${toggle.label}\" with your workspace`}\n onChange={(next) =>\n handlePolicyToggle(toggle.key, next)\n }\n />\n <span\n className={cn(\n \"text-[0.65rem] font-medium uppercase tracking-wide\",\n checked\n ? \"text-status-success-text\"\n : \"text-text-muted\",\n )}\n >\n {saving ? \"Saving…\" : checked ? \"On\" : \"Off\"}\n </span>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </GlassCard>\n\n {/* Strong, unmissable reassurance. */}\n <GlassCard\n treatment=\"metric\"\n className=\"border-status-success-border/40 p-4\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-status-success-bg/40 text-status-success-text\">\n <ShieldCheck className=\"h-5 w-5\" />\n </div>\n <p className=\"text-sm font-semibold text-text-primary\">\n Your employer never sees your personal context.\n </p>\n </div>\n </GlassCard>\n </div>\n\n {/* ── Corporate (your workspace) ────────────────────────────────── */}\n <GlassCard treatment=\"glass\" className=\"flex flex-col p-4 sm:p-6\">\n <div className=\"mb-4 flex items-start gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Building2 className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n Corporate{\" \"}\n <span className=\"font-normal text-text-secondary\">\n (your workspace)\n </span>\n </h2>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n What the workspace can actually see about you.\n </p>\n </div>\n </div>\n\n <div className=\"flex flex-1 flex-col gap-4\">\n <div className=\"flex items-start gap-2 rounded-lg border border-status-info-border/40 bg-status-info-bg/10 px-3 py-2.5 text-xs text-status-info-text\">\n <Info className=\"mt-0.5 h-3.5 w-3.5 shrink-0\" />\n <span>\n Your workspace only ever receives aggregate work signals —\n coarse yes/no bands, never entries, locations, health or\n timestamps.\n </span>\n </div>\n\n <div>\n <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Aggregate signals visible to work\n </p>\n {sharedAggregates.length === 0 ? (\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/40 p-4 text-center\">\n <p className=\"text-sm font-medium text-text-primary\">\n Nothing is shared right now\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Turn on a signal in the middle column and it will appear\n here.\n </p>\n </div>\n ) : (\n <ul className=\"space-y-2\">\n {sharedAggregates.map((agg) => (\n <li\n key={agg}\n className=\"flex items-center justify-between gap-3 rounded-lg border border-border-subtle bg-bg-sunken/40 px-3 py-2.5\"\n >\n <span className=\"flex min-w-0 items-center gap-2 text-sm text-text-primary\">\n <ArrowUpRight className=\"h-4 w-4 shrink-0 text-status-success-text\" />\n <span className=\"truncate\">{humanizeToken(agg)}</span>\n </span>\n <StatusBadge variant=\"success\" label=\"Shared\" dot />\n </li>\n ))}\n </ul>\n )}\n </div>\n\n <p className=\"mt-auto text-xs text-text-muted\">\n This mirrors exactly what you've allowed — change it any time from\n the switches on the left.\n </p>\n </div>\n </GlassCard>\n </div>\n\n {/* ── Connected sources & consent ─────────────────────────────────── */}\n <GlassCard treatment=\"glass\" className=\"mt-6 p-4 sm:p-6\">\n <div className=\"mb-4 flex items-start gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Plug className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Connected sources\n </h3>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Each capture source only feeds your private record once you\n consent — and you can withdraw at any moment.\n </p>\n </div>\n </div>\n\n {consentsError && consents.length === 0 ? (\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-3 text-xs text-status-error-text\">\n Couldn't load your connected sources right now.\n </div>\n ) : consentsLoading && consents.length === 0 ? (\n <LoadingSkeleton variant=\"list\" count={3} />\n ) : consents.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No sources connected yet\"\n description=\"When you connect a capture source — a calendar, location or health signal — you'll grant and revoke exactly what it may feed here.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {consents.map((consent) => {\n const saving = savingSource === consent.source;\n return (\n <GlassCard key={consent.id} treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {humanizeToken(consent.source)}\n </p>\n <div className=\"mt-1.5 flex flex-wrap items-center gap-1.5\">\n <StatusBadge\n variant=\"muted\"\n label={humanizeToken(consent.mode)}\n />\n {consent.granted && (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={consent.onDeviceOnly}\n disabled={savingOnDeviceSource === consent.source}\n onClick={() =>\n handleOnDeviceOnlyToggle(\n consent,\n !consent.onDeviceOnly,\n )\n }\n className={cn(\n \"rounded-full border px-2 py-0.5 text-[0.65rem] font-medium uppercase tracking-wide transition-colors\",\n \"focus:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50\",\n consent.onDeviceOnly\n ? \"border-status-info-border/60 bg-status-info-bg/40 text-status-info-text\"\n : \"border-border-subtle bg-bg-sunken/40 text-text-muted hover:text-text-secondary\",\n savingOnDeviceSource === consent.source &&\n \"cursor-not-allowed opacity-50\",\n )}\n >\n {savingOnDeviceSource === consent.source\n ? \"Saving…\"\n : consent.onDeviceOnly\n ? \"On-device only\"\n : \"Turn on on-device only\"}\n </button>\n )}\n {consent.retentionDays != null && (\n <StatusBadge\n variant=\"muted\"\n label={`Kept ${consent.retentionDays}d`}\n />\n )}\n </div>\n </div>\n <div className=\"flex shrink-0 flex-col items-end gap-1\">\n <ToggleSwitch\n checked={consent.granted}\n disabled={saving}\n ariaLabel={`Consent for ${humanizeToken(consent.source)}`}\n onChange={(next) => handleConsentToggle(consent, next)}\n />\n <span\n className={cn(\n \"text-[0.65rem] font-medium uppercase tracking-wide\",\n consent.granted\n ? \"text-status-success-text\"\n : \"text-text-muted\",\n )}\n >\n {saving\n ? \"Saving…\"\n : consent.granted\n ? \"Granted\"\n : \"Revoked\"}\n </span>\n </div>\n </div>\n </GlassCard>\n );\n })}\n </div>\n )}\n </GlassCard>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -2,16 +2,16 @@ import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
|
|
|
2
2
|
import { cn as t } from "../../utils/helpers.js";
|
|
3
3
|
import { StatCard as n } from "../../components/StatCard.js";
|
|
4
4
|
import { LoadingSkeleton as r } from "../../components/LoadingSkeleton.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { Modal as
|
|
10
|
-
import { ToastContainer as
|
|
5
|
+
import { EmptyState as i } from "../../components/EmptyState.js";
|
|
6
|
+
import { useAcceptRecommendationMutation as a, useDismissRecommendationMutation as o, useGenerateMyRecommendationsMutation as s, useMyRecommendationPreferencesQuery as c, useMyRecommendationsQuery as l, useSnoozeRecommendationMutation as u, useUpdateMyRecommendationPreferencesMutation as d } from "../../../generated/wspace-operations.js";
|
|
7
|
+
import { PageLayout as f } from "../../components/PageLayout.js";
|
|
8
|
+
import { StatusBadge as ee } from "../../components/StatusBadge.js";
|
|
9
|
+
import { Modal as p } from "../../components/Modal.js";
|
|
10
|
+
import { ToastContainer as m, useToast as h } from "../../components/Toast.js";
|
|
11
11
|
import "../../components/index.js";
|
|
12
|
-
import { TimeCampusLifeDomain as
|
|
13
|
-
import { lifeDomainLabel as
|
|
14
|
-
import { formatEvidenceRef as
|
|
12
|
+
import { TimeCampusLifeDomain as g } from "../../../generated/wspace-types.js";
|
|
13
|
+
import { lifeDomainLabel as _ } from "./domains.js";
|
|
14
|
+
import { formatEvidenceRef as v, formatHourOfDay as y, humaniseToken as b, parseEvidenceRefs as x, parseSuggestedAction as S } from "./recommendationEvidence.js";
|
|
15
15
|
import { useMemo as C, useState as w } from "react";
|
|
16
16
|
import { BellOff as T, Briefcase as E, Bus as D, CalendarClock as O, Check as k, CheckCircle2 as te, ChevronDown as ne, Circle as re, Clock as A, Coffee as j, GraduationCap as ie, Handshake as ae, Heart as oe, HeartPulse as se, Home as M, Hourglass as ce, Lightbulb as N, Microscope as le, Moon as ue, ShieldCheck as P, Sparkles as F, Users as I, Wallet as L, X as R } from "lucide-react";
|
|
17
17
|
import { Fragment as de, jsx as z, jsxs as B } from "react/jsx-runtime";
|
|
@@ -56,20 +56,20 @@ function K(e) {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
var q = Array.from({ length: 24 }, (e, t) => t), me = {
|
|
59
|
-
[
|
|
60
|
-
[
|
|
61
|
-
[
|
|
62
|
-
[
|
|
63
|
-
[
|
|
64
|
-
[
|
|
65
|
-
[
|
|
66
|
-
[
|
|
67
|
-
[
|
|
68
|
-
[
|
|
69
|
-
[
|
|
70
|
-
[
|
|
71
|
-
[
|
|
72
|
-
[
|
|
59
|
+
[g.Work]: E,
|
|
60
|
+
[g.Sleep]: ue,
|
|
61
|
+
[g.Health]: se,
|
|
62
|
+
[g.Family]: I,
|
|
63
|
+
[g.Learning]: ie,
|
|
64
|
+
[g.Leisure]: j,
|
|
65
|
+
[g.Commute]: D,
|
|
66
|
+
[g.Waiting]: ce,
|
|
67
|
+
[g.Chores]: M,
|
|
68
|
+
[g.SelfCare]: oe,
|
|
69
|
+
[g.Social]: ae,
|
|
70
|
+
[g.FinanceAdmin]: L,
|
|
71
|
+
[g.Reflection]: F,
|
|
72
|
+
[g.Other]: re
|
|
73
73
|
};
|
|
74
74
|
function he(e) {
|
|
75
75
|
return e ? me[e] ?? N : N;
|
|
@@ -112,7 +112,7 @@ function _e({ checked: e, onChange: n, label: r, disabled: i }) {
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
function ve({ rec: e, busy: n, muted: r, onAccept: i, onSnooze: a, onDismiss: o, onMuteKind: s }) {
|
|
115
|
-
let c = he(e.domain), l = U(e.state), u = ge(e.state), [d,
|
|
115
|
+
let c = he(e.domain), l = U(e.state), u = ge(e.state), [d, f] = w(!1), p = x(e.evidenceRefs), m = S(e.suggestedAction), h = `rec-evidence-${e.id}`;
|
|
116
116
|
return /* @__PURE__ */ B(V, {
|
|
117
117
|
treatment: "glass",
|
|
118
118
|
className: "flex flex-col gap-3 p-4 sm:p-5",
|
|
@@ -128,13 +128,13 @@ function ve({ rec: e, busy: n, muted: r, onAccept: i, onSnooze: a, onDismiss: o,
|
|
|
128
128
|
className: "min-w-0",
|
|
129
129
|
children: [/* @__PURE__ */ z("p", {
|
|
130
130
|
className: "truncate text-xs font-medium uppercase tracking-wide text-text-secondary",
|
|
131
|
-
children:
|
|
131
|
+
children: b(e.kind)
|
|
132
132
|
}), /* @__PURE__ */ z("h3", {
|
|
133
133
|
className: "mt-0.5 text-base font-semibold text-text-primary",
|
|
134
134
|
children: e.title
|
|
135
135
|
})]
|
|
136
136
|
})]
|
|
137
|
-
}), e.confidence != null && /* @__PURE__ */ z(
|
|
137
|
+
}), e.confidence != null && /* @__PURE__ */ z(ee, {
|
|
138
138
|
variant: "info",
|
|
139
139
|
label: `${G(e.confidence)} confident`,
|
|
140
140
|
className: "shrink-0"
|
|
@@ -149,7 +149,7 @@ function ve({ rec: e, busy: n, muted: r, onAccept: i, onSnooze: a, onDismiss: o,
|
|
|
149
149
|
children: [
|
|
150
150
|
e.domain && /* @__PURE__ */ z("span", {
|
|
151
151
|
className: X,
|
|
152
|
-
children:
|
|
152
|
+
children: _(e.domain)
|
|
153
153
|
}),
|
|
154
154
|
e.valueAlignment != null && /* @__PURE__ */ B("span", {
|
|
155
155
|
className: X,
|
|
@@ -177,7 +177,7 @@ function ve({ rec: e, busy: n, muted: r, onAccept: i, onSnooze: a, onDismiss: o,
|
|
|
177
177
|
})
|
|
178
178
|
]
|
|
179
179
|
}),
|
|
180
|
-
|
|
180
|
+
m && /* @__PURE__ */ B("div", {
|
|
181
181
|
className: "rounded-lg border border-border-seam bg-bg-sunken/40 p-3",
|
|
182
182
|
children: [
|
|
183
183
|
/* @__PURE__ */ z("p", {
|
|
@@ -186,11 +186,11 @@ function ve({ rec: e, busy: n, muted: r, onAccept: i, onSnooze: a, onDismiss: o,
|
|
|
186
186
|
}),
|
|
187
187
|
/* @__PURE__ */ z("p", {
|
|
188
188
|
className: "mt-1 text-sm font-medium text-text-primary",
|
|
189
|
-
children:
|
|
189
|
+
children: b(m.type)
|
|
190
190
|
}),
|
|
191
|
-
|
|
191
|
+
m.details.length > 0 && /* @__PURE__ */ z("dl", {
|
|
192
192
|
className: "mt-1.5 flex flex-wrap gap-x-4 gap-y-1",
|
|
193
|
-
children:
|
|
193
|
+
children: m.details.map((e) => /* @__PURE__ */ B("div", {
|
|
194
194
|
className: "flex items-center gap-1.5",
|
|
195
195
|
children: [/* @__PURE__ */ z("dt", {
|
|
196
196
|
className: "text-xs text-text-secondary",
|
|
@@ -203,13 +203,13 @@ function ve({ rec: e, busy: n, muted: r, onAccept: i, onSnooze: a, onDismiss: o,
|
|
|
203
203
|
})
|
|
204
204
|
]
|
|
205
205
|
}),
|
|
206
|
-
|
|
206
|
+
p.length > 0 && /* @__PURE__ */ B("div", {
|
|
207
207
|
className: "rounded-lg border border-border-seam bg-bg-sunken/40",
|
|
208
208
|
children: [/* @__PURE__ */ B("button", {
|
|
209
209
|
type: "button",
|
|
210
|
-
onClick: () =>
|
|
210
|
+
onClick: () => f((e) => !e),
|
|
211
211
|
"aria-expanded": d,
|
|
212
|
-
"aria-controls":
|
|
212
|
+
"aria-controls": h,
|
|
213
213
|
className: "flex w-full items-center justify-between gap-2 px-3 py-2 text-left",
|
|
214
214
|
children: [/* @__PURE__ */ B("span", {
|
|
215
215
|
className: "flex min-w-0 items-center gap-2",
|
|
@@ -217,22 +217,22 @@ function ve({ rec: e, busy: n, muted: r, onAccept: i, onSnooze: a, onDismiss: o,
|
|
|
217
217
|
className: "truncate text-xs text-text-secondary",
|
|
218
218
|
children: [
|
|
219
219
|
"Evidence: ",
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
v(p[0]),
|
|
221
|
+
p.length > 1 ? ` · +${p.length - 1} more` : ""
|
|
222
222
|
]
|
|
223
223
|
})]
|
|
224
224
|
}), /* @__PURE__ */ z(ne, { className: t("h-4 w-4 shrink-0 text-text-secondary transition-transform", d && "rotate-180") })]
|
|
225
225
|
}), d && /* @__PURE__ */ B("dl", {
|
|
226
|
-
id:
|
|
226
|
+
id: h,
|
|
227
227
|
className: "space-y-1.5 border-t border-border-seam px-3 py-2.5",
|
|
228
|
-
children: [
|
|
228
|
+
children: [p.map((e, t) => /* @__PURE__ */ B("div", {
|
|
229
229
|
className: "flex items-baseline justify-between gap-3",
|
|
230
230
|
children: [/* @__PURE__ */ z("dt", {
|
|
231
231
|
className: "text-xs text-text-secondary",
|
|
232
|
-
children:
|
|
232
|
+
children: v(e).split(": ")[0]
|
|
233
233
|
}), /* @__PURE__ */ z("dd", {
|
|
234
234
|
className: "text-xs font-medium tabular-nums text-text-primary",
|
|
235
|
-
children:
|
|
235
|
+
children: v(e).split(": ").slice(1).join(": ")
|
|
236
236
|
})]
|
|
237
237
|
}, `${e.metric}-${e.period ?? e.domain ?? t}`)), /* @__PURE__ */ z("p", {
|
|
238
238
|
className: "pt-1 text-xs text-text-tertiary",
|
|
@@ -269,7 +269,7 @@ function ve({ rec: e, busy: n, muted: r, onAccept: i, onSnooze: a, onDismiss: o,
|
|
|
269
269
|
onClick: () => s(e.kind),
|
|
270
270
|
disabled: n || r,
|
|
271
271
|
className: Y,
|
|
272
|
-
title: r ? `${
|
|
272
|
+
title: r ? `${b(e.kind)} suggestions are already muted` : `Stop generating ${b(e.kind)} suggestions`,
|
|
273
273
|
children: [/* @__PURE__ */ z(T, { className: "h-4 w-4" }), r ? "Muted" : "Mute this kind"]
|
|
274
274
|
})
|
|
275
275
|
]
|
|
@@ -277,7 +277,7 @@ function ve({ rec: e, busy: n, muted: r, onAccept: i, onSnooze: a, onDismiss: o,
|
|
|
277
277
|
className: "flex flex-col gap-2 pt-1",
|
|
278
278
|
children: [/* @__PURE__ */ B("div", {
|
|
279
279
|
className: "flex items-center justify-between gap-2",
|
|
280
|
-
children: [/* @__PURE__ */ z(
|
|
280
|
+
children: [/* @__PURE__ */ z(ee, {
|
|
281
281
|
variant: u.variant,
|
|
282
282
|
label: u.label
|
|
283
283
|
}), /* @__PURE__ */ z("span", {
|
|
@@ -298,7 +298,7 @@ function ye({ prefs: e, loading: n, saving: i, onToggleOutputGuard: a, onQuietHo
|
|
|
298
298
|
count: 1
|
|
299
299
|
});
|
|
300
300
|
if (!e) return null;
|
|
301
|
-
let c =
|
|
301
|
+
let c = y(e.quietHoursStart), l = y(e.quietHoursEnd), u = c !== null && l !== null, d = "rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40 disabled:opacity-50";
|
|
302
302
|
return /* @__PURE__ */ B(V, {
|
|
303
303
|
treatment: "glass",
|
|
304
304
|
className: "p-4 sm:p-5",
|
|
@@ -367,7 +367,7 @@ function ye({ prefs: e, loading: n, saving: i, onToggleOutputGuard: a, onQuietHo
|
|
|
367
367
|
children: "—"
|
|
368
368
|
}), q.map((e) => /* @__PURE__ */ z("option", {
|
|
369
369
|
value: e,
|
|
370
|
-
children:
|
|
370
|
+
children: y(e)
|
|
371
371
|
}, e))]
|
|
372
372
|
})]
|
|
373
373
|
}),
|
|
@@ -383,7 +383,7 @@ function ye({ prefs: e, loading: n, saving: i, onToggleOutputGuard: a, onQuietHo
|
|
|
383
383
|
children: "—"
|
|
384
384
|
}), q.map((e) => /* @__PURE__ */ z("option", {
|
|
385
385
|
value: e,
|
|
386
|
-
children:
|
|
386
|
+
children: y(e)
|
|
387
387
|
}, e))]
|
|
388
388
|
})]
|
|
389
389
|
}),
|
|
@@ -419,8 +419,8 @@ function ye({ prefs: e, loading: n, saving: i, onToggleOutputGuard: a, onQuietHo
|
|
|
419
419
|
disabled: i,
|
|
420
420
|
onClick: () => s(e),
|
|
421
421
|
className: t(X, "hover:text-text-primary disabled:opacity-50"),
|
|
422
|
-
title: `Unmute ${
|
|
423
|
-
children: [
|
|
422
|
+
title: `Unmute ${b(e)} suggestions`,
|
|
423
|
+
children: [b(e), /* @__PURE__ */ z(R, { className: "h-3 w-3" })]
|
|
424
424
|
}, e))
|
|
425
425
|
})]
|
|
426
426
|
}),
|
|
@@ -433,7 +433,7 @@ function ye({ prefs: e, loading: n, saving: i, onToggleOutputGuard: a, onQuietHo
|
|
|
433
433
|
children: "Cadence"
|
|
434
434
|
}), /* @__PURE__ */ z("span", {
|
|
435
435
|
className: "text-xs text-text-secondary",
|
|
436
|
-
children:
|
|
436
|
+
children: b(e.cadence)
|
|
437
437
|
})]
|
|
438
438
|
}), e.optedInDomains.length > 0 && /* @__PURE__ */ B(de, { children: [/* @__PURE__ */ z("p", {
|
|
439
439
|
className: "mt-3 text-sm font-medium text-text-primary",
|
|
@@ -442,7 +442,7 @@ function ye({ prefs: e, loading: n, saving: i, onToggleOutputGuard: a, onQuietHo
|
|
|
442
442
|
className: "mt-1.5 flex flex-wrap gap-1.5",
|
|
443
443
|
children: e.optedInDomains.map((e) => /* @__PURE__ */ z("span", {
|
|
444
444
|
className: X,
|
|
445
|
-
children:
|
|
445
|
+
children: _(e)
|
|
446
446
|
}, e))
|
|
447
447
|
})] })]
|
|
448
448
|
})
|
|
@@ -472,23 +472,23 @@ function be(e) {
|
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
474
|
function xe() {
|
|
475
|
-
let { workspaceId: t } = e(), { toasts:
|
|
475
|
+
let { workspaceId: t } = e(), { toasts: ee, addToast: g, dismissToast: _ } = h(), [v, y] = w(H.New), [x, S] = w(null), [T, E] = w(null), [D, O] = w(""), { data: k, loading: ne, error: re, refetch: j } = l({
|
|
476
476
|
variables: { state: null },
|
|
477
477
|
skip: !t
|
|
478
|
-
}), { data: ie, loading: ae, refetch: oe } =
|
|
478
|
+
}), { data: ie, loading: ae, refetch: oe } = c({ skip: !t }), [se, { loading: M }] = s(), [ce] = a(), [N] = o(), [le] = u(), [ue, { loading: I }] = d(), L = ie?.myRecommendationPreferences, U = C(() => new Set(L?.mutedKinds ?? []), [L?.mutedKinds]), W = C(() => k?.myRecommendations ?? [], [k?.myRecommendations]), G = C(() => {
|
|
479
479
|
let e = {};
|
|
480
480
|
for (let t of W) e[t.state] = (e[t.state] ?? 0) + 1;
|
|
481
481
|
return e;
|
|
482
|
-
}, [W]), K = C(() => W.filter((e) => e.state ===
|
|
482
|
+
}, [W]), K = C(() => W.filter((e) => e.state === v), [W, v]), q = G[H.New] ?? 0, me = G[H.Accepted] ?? 0, he = G[H.Snoozed] ?? 0, ge = async () => {
|
|
483
483
|
try {
|
|
484
484
|
let e = (await se()).data?.generateMyRecommendations ?? [];
|
|
485
|
-
|
|
485
|
+
g({
|
|
486
486
|
title: e.length > 0 ? `${e.length} suggestion${e.length === 1 ? "" : "s"} ready` : "No new suggestions",
|
|
487
487
|
description: e.length > 0 ? "Fresh, values-aware ideas are in your New feed." : "You're all caught up — nothing new to suggest right now.",
|
|
488
488
|
variant: e.length > 0 ? "success" : "info"
|
|
489
|
-
}),
|
|
489
|
+
}), y(H.New), await j();
|
|
490
490
|
} catch {
|
|
491
|
-
|
|
491
|
+
g({
|
|
492
492
|
title: "Couldn't generate recommendations",
|
|
493
493
|
description: "Please try again in a moment.",
|
|
494
494
|
variant: "error"
|
|
@@ -497,12 +497,12 @@ function xe() {
|
|
|
497
497
|
}, X = async (e) => {
|
|
498
498
|
S(e);
|
|
499
499
|
try {
|
|
500
|
-
await ce({ variables: { id: e } }),
|
|
500
|
+
await ce({ variables: { id: e } }), g({
|
|
501
501
|
title: "Accepted — nice",
|
|
502
502
|
variant: "success"
|
|
503
503
|
}), await j();
|
|
504
504
|
} catch {
|
|
505
|
-
|
|
505
|
+
g({
|
|
506
506
|
title: "Couldn't accept that one",
|
|
507
507
|
variant: "error"
|
|
508
508
|
});
|
|
@@ -515,13 +515,13 @@ function xe() {
|
|
|
515
515
|
await le({ variables: {
|
|
516
516
|
id: e,
|
|
517
517
|
until: pe()
|
|
518
|
-
} }),
|
|
518
|
+
} }), g({
|
|
519
519
|
title: "Snoozed for a week",
|
|
520
520
|
description: "It'll resurface later — no nagging in between.",
|
|
521
521
|
variant: "info"
|
|
522
522
|
}), await j();
|
|
523
523
|
} catch {
|
|
524
|
-
|
|
524
|
+
g({
|
|
525
525
|
title: "Couldn't snooze that one",
|
|
526
526
|
variant: "error"
|
|
527
527
|
});
|
|
@@ -539,12 +539,12 @@ function xe() {
|
|
|
539
539
|
await N({ variables: {
|
|
540
540
|
id: e.id,
|
|
541
541
|
reason: t.length > 0 ? t : null
|
|
542
|
-
} }),
|
|
542
|
+
} }), g({
|
|
543
543
|
title: "Dismissed",
|
|
544
544
|
variant: "success"
|
|
545
545
|
}), E(null), O(""), await j();
|
|
546
546
|
} catch {
|
|
547
|
-
|
|
547
|
+
g({
|
|
548
548
|
title: "Couldn't dismiss that one",
|
|
549
549
|
variant: "error"
|
|
550
550
|
});
|
|
@@ -553,12 +553,12 @@ function xe() {
|
|
|
553
553
|
}
|
|
554
554
|
}, Z = async (e, t) => {
|
|
555
555
|
try {
|
|
556
|
-
await ue({ variables: { input: e } }),
|
|
556
|
+
await ue({ variables: { input: e } }), g({
|
|
557
557
|
title: t,
|
|
558
558
|
variant: "success"
|
|
559
559
|
}), await oe();
|
|
560
560
|
} catch {
|
|
561
|
-
|
|
561
|
+
g({
|
|
562
562
|
title: "Couldn't save that guardrail",
|
|
563
563
|
description: "Please try again in a moment.",
|
|
564
564
|
variant: "error"
|
|
@@ -568,14 +568,14 @@ function xe() {
|
|
|
568
568
|
quietHoursStart: e,
|
|
569
569
|
quietHoursEnd: t
|
|
570
570
|
}, e === null && t === null ? "Quiet hours cleared" : "Quiet hours saved"), Te = (e) => {
|
|
571
|
-
U.has(e) || Z({ mutedKinds: [...U, e] }, `${
|
|
571
|
+
U.has(e) || Z({ mutedKinds: [...U, e] }, `${b(e)} suggestions muted`);
|
|
572
572
|
}, Ee = /* @__PURE__ */ z(ye, {
|
|
573
573
|
prefs: L,
|
|
574
574
|
loading: ae,
|
|
575
575
|
saving: I,
|
|
576
576
|
onToggleOutputGuard: Ce,
|
|
577
577
|
onQuietHoursChange: we,
|
|
578
|
-
onUnmuteKind: (e) => void Z({ mutedKinds: [...U].filter((t) => t !== e) }, `${
|
|
578
|
+
onUnmuteKind: (e) => void Z({ mutedKinds: [...U].filter((t) => t !== e) }, `${b(e)} suggestions unmuted`)
|
|
579
579
|
}), Q = /* @__PURE__ */ B("button", {
|
|
580
580
|
type: "button",
|
|
581
581
|
onClick: ge,
|
|
@@ -602,7 +602,7 @@ function xe() {
|
|
|
602
602
|
})]
|
|
603
603
|
})
|
|
604
604
|
});
|
|
605
|
-
if (!t || ne) return /* @__PURE__ */ z(
|
|
605
|
+
if (!t || ne) return /* @__PURE__ */ z(f, {
|
|
606
606
|
title: "Recommendations",
|
|
607
607
|
description: "Values-aware suggestions from your real timeline",
|
|
608
608
|
purpose: $,
|
|
@@ -612,7 +612,7 @@ function xe() {
|
|
|
612
612
|
count: 4
|
|
613
613
|
})
|
|
614
614
|
});
|
|
615
|
-
if (re) return /* @__PURE__ */ z(
|
|
615
|
+
if (re) return /* @__PURE__ */ z(f, {
|
|
616
616
|
title: "Recommendations",
|
|
617
617
|
description: "Values-aware suggestions from your real timeline",
|
|
618
618
|
purpose: $,
|
|
@@ -622,8 +622,8 @@ function xe() {
|
|
|
622
622
|
children: "Couldn't load your recommendations right now. Please try again in a moment."
|
|
623
623
|
})
|
|
624
624
|
});
|
|
625
|
-
let Oe = W.length > 0, ke = be(
|
|
626
|
-
return /* @__PURE__ */ B(
|
|
625
|
+
let Oe = W.length > 0, ke = be(v);
|
|
626
|
+
return /* @__PURE__ */ B(f, {
|
|
627
627
|
title: "Recommendations",
|
|
628
628
|
description: "Values-aware suggestions from your real timeline",
|
|
629
629
|
purpose: $,
|
|
@@ -665,12 +665,12 @@ function xe() {
|
|
|
665
665
|
role: "tablist",
|
|
666
666
|
"aria-label": "Filter recommendations by state",
|
|
667
667
|
children: fe.map((e) => {
|
|
668
|
-
let t = e.key ===
|
|
668
|
+
let t = e.key === v, n = G[e.key] ?? 0;
|
|
669
669
|
return /* @__PURE__ */ B("button", {
|
|
670
670
|
type: "button",
|
|
671
671
|
role: "tab",
|
|
672
672
|
"aria-selected": t,
|
|
673
|
-
onClick: () =>
|
|
673
|
+
onClick: () => y(e.key),
|
|
674
674
|
className: t ? "flex items-center gap-1.5 rounded-md bg-bg-surface px-3 py-1.5 text-sm font-medium text-text-primary shadow-sm" : "flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary",
|
|
675
675
|
children: [e.label, /* @__PURE__ */ z("span", {
|
|
676
676
|
className: t ? "rounded-full bg-action-primary-bg/15 px-1.5 text-xs font-semibold text-primary" : "rounded-full bg-bg-sunken px-1.5 text-xs font-semibold text-text-secondary",
|
|
@@ -680,16 +680,16 @@ function xe() {
|
|
|
680
680
|
})
|
|
681
681
|
})
|
|
682
682
|
}),
|
|
683
|
-
K.length === 0 ? /* @__PURE__ */ z(
|
|
683
|
+
K.length === 0 ? /* @__PURE__ */ z(i, {
|
|
684
684
|
illustration: "empty-data",
|
|
685
685
|
title: ke.title,
|
|
686
686
|
description: ke.description,
|
|
687
|
-
action:
|
|
687
|
+
action: v === H.New ? Q : void 0
|
|
688
688
|
}) : /* @__PURE__ */ z("div", {
|
|
689
689
|
className: "space-y-4",
|
|
690
690
|
children: K.map((e) => /* @__PURE__ */ z(ve, {
|
|
691
691
|
rec: e,
|
|
692
|
-
busy:
|
|
692
|
+
busy: x === e.id || I,
|
|
693
693
|
muted: U.has(e.kind),
|
|
694
694
|
onAccept: X,
|
|
695
695
|
onSnooze: _e,
|
|
@@ -702,14 +702,14 @@ function xe() {
|
|
|
702
702
|
}), /* @__PURE__ */ z("div", { children: Ee })]
|
|
703
703
|
}) : /* @__PURE__ */ B("div", {
|
|
704
704
|
className: "grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]",
|
|
705
|
-
children: [/* @__PURE__ */ B("div", { children: [/* @__PURE__ */ z(
|
|
705
|
+
children: [/* @__PURE__ */ B("div", { children: [/* @__PURE__ */ z(i, {
|
|
706
706
|
illustration: "empty-data",
|
|
707
707
|
title: "No recommendations yet",
|
|
708
708
|
description: "TimeCampus turns your real timeline into gentle, values-aware suggestions — like protecting a focus block or reclaiming a commute. Generate your first set to see what it notices.",
|
|
709
709
|
action: Q
|
|
710
710
|
}), De] }), /* @__PURE__ */ z("div", { children: Ee })]
|
|
711
711
|
}),
|
|
712
|
-
/* @__PURE__ */ B(
|
|
712
|
+
/* @__PURE__ */ B(p, {
|
|
713
713
|
open: T !== null,
|
|
714
714
|
onClose: () => E(null),
|
|
715
715
|
title: "Dismiss this suggestion",
|
|
@@ -723,7 +723,7 @@ function xe() {
|
|
|
723
723
|
}), /* @__PURE__ */ B("button", {
|
|
724
724
|
type: "button",
|
|
725
725
|
onClick: () => void Se(),
|
|
726
|
-
disabled:
|
|
726
|
+
disabled: x !== null,
|
|
727
727
|
className: J,
|
|
728
728
|
children: [/* @__PURE__ */ z(R, { className: "h-4 w-4" }), "Dismiss"]
|
|
729
729
|
})] }),
|
|
@@ -754,9 +754,9 @@ function xe() {
|
|
|
754
754
|
})
|
|
755
755
|
]
|
|
756
756
|
}),
|
|
757
|
-
/* @__PURE__ */ z(
|
|
758
|
-
toasts:
|
|
759
|
-
onDismiss:
|
|
757
|
+
/* @__PURE__ */ z(m, {
|
|
758
|
+
toasts: ee,
|
|
759
|
+
onDismiss: _
|
|
760
760
|
})
|
|
761
761
|
]
|
|
762
762
|
});
|