@burdenoff/microfe-timecampus 2026.728.1 → 2026.731.2
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PersonalCorporatePage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/lifetime/PersonalCorporatePage.tsx"],"names":[],"mappings":"AA8HA,wBAAgB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"PersonalCorporatePage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/lifetime/PersonalCorporatePage.tsx"],"names":[],"mappings":"AA8HA,wBAAgB,qBAAqB,gCAijBpC"}
|
|
@@ -2,15 +2,15 @@ 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 { useGrantSourceConsentMutation as i, useMyContextSharingPolicyQuery as
|
|
6
|
-
import { PageLayout as
|
|
7
|
-
import { StatusBadge as
|
|
8
|
-
import { EmptyState as
|
|
9
|
-
import { ToastContainer as
|
|
5
|
+
import { useGrantSourceConsentMutation as i, useMyContextSharingPolicyQuery as a, useMyPersonalDataSummaryQuery as ee, useMySourceConsentsQuery as o, useRevokeSourceConsentMutation as s, useUpdateContextSharingPolicyMutation as c } from "../../../generated/wspace-operations.js";
|
|
6
|
+
import { PageLayout as l } from "../../components/PageLayout.js";
|
|
7
|
+
import { StatusBadge as u } from "../../components/StatusBadge.js";
|
|
8
|
+
import { EmptyState as d } from "../../components/EmptyState.js";
|
|
9
|
+
import { ToastContainer as te, useToast as ne } from "../../components/Toast.js";
|
|
10
10
|
import "../../components/index.js";
|
|
11
|
-
import { formatSecondsHuman as
|
|
12
|
-
import { useMemo as
|
|
13
|
-
import { ArrowUpRight as
|
|
11
|
+
import { formatSecondsHuman as re, isoDaysAgo as ie, isoNow as ae, lifeDomainLabel as oe } from "./domains.js";
|
|
12
|
+
import { useMemo as f, useState as p } from "react";
|
|
13
|
+
import { ArrowUpRight as m, Building2 as h, Info as g, Lock as _, Plug as v, Share2 as se, ShieldCheck as ce } from "lucide-react";
|
|
14
14
|
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
15
15
|
import { GlassCard as x } from "@burdenoff/fe-libs/ui";
|
|
16
16
|
//#region src/timecampus/pages/lifetime/PersonalCorporatePage.tsx
|
|
@@ -48,21 +48,21 @@ function w({ checked: e, onChange: n, disabled: r = !1, ariaLabel: i }) {
|
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
function T() {
|
|
51
|
-
let { workspaceId: T = "" } = e(), { toasts:
|
|
52
|
-
from:
|
|
53
|
-
to:
|
|
54
|
-
}), []), { data:
|
|
51
|
+
let { workspaceId: T = "" } = e(), { toasts: le, addToast: E, dismissToast: ue } = ne(), D = f(() => ({
|
|
52
|
+
from: ie(30),
|
|
53
|
+
to: ae()
|
|
54
|
+
}), []), { data: O, loading: k, error: A } = a({ skip: !T }), { data: j, loading: M, error: N, refetch: P } = ee({
|
|
55
55
|
variables: {
|
|
56
|
-
from:
|
|
57
|
-
to:
|
|
56
|
+
from: D.from,
|
|
57
|
+
to: D.to
|
|
58
58
|
},
|
|
59
59
|
skip: !T
|
|
60
|
-
}), { data:
|
|
60
|
+
}), { data: F, loading: I, error: L, refetch: R } = o({ skip: !T }), [z] = c(), [B] = i(), [V] = s(), [H, U] = p(null), [de, W] = p(null), [G, K] = p(null), q = O?.myContextSharingPolicy, J = j?.myPersonalDataSummary, Y = F?.mySourceConsents ?? [], X = f(() => [...J?.byDomain ?? []].sort((e, t) => t.totalSeconds - e.totalSeconds), [J]), Z = X[0]?.totalSeconds ?? 0, Q = J?.sharedAggregates ?? [], $ = {
|
|
61
61
|
title: "Personal & Corporate",
|
|
62
62
|
description: "Two contexts, one boundary you control",
|
|
63
63
|
purpose: "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."
|
|
64
64
|
};
|
|
65
|
-
if (!T ||
|
|
65
|
+
if (!T || k && !O || M && !j) return /* @__PURE__ */ y(l, {
|
|
66
66
|
...$,
|
|
67
67
|
children: /* @__PURE__ */ b("div", {
|
|
68
68
|
className: "grid grid-cols-1 gap-6 lg:grid-cols-3",
|
|
@@ -82,65 +82,89 @@ function T() {
|
|
|
82
82
|
]
|
|
83
83
|
})
|
|
84
84
|
});
|
|
85
|
-
if (
|
|
85
|
+
if (A && N && !q && !J) return /* @__PURE__ */ y(l, {
|
|
86
86
|
...$,
|
|
87
87
|
children: /* @__PURE__ */ y("div", {
|
|
88
88
|
className: "rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
|
|
89
89
|
children: "We couldn't load your privacy settings right now. Please try again in a moment."
|
|
90
90
|
})
|
|
91
91
|
});
|
|
92
|
-
let
|
|
92
|
+
let fe = async (e, t) => {
|
|
93
93
|
if (!q) return;
|
|
94
94
|
let n = S.find((t) => t.key === e)?.label ?? "signal";
|
|
95
|
-
|
|
95
|
+
U(e);
|
|
96
96
|
try {
|
|
97
|
-
let r = await
|
|
97
|
+
let r = await z({ variables: { input: {
|
|
98
98
|
sustainableHours: q.sustainableHours,
|
|
99
99
|
recoveryBand: q.recoveryBand,
|
|
100
100
|
workLifeBoundary: q.workLifeBoundary,
|
|
101
101
|
[e]: t
|
|
102
102
|
} } });
|
|
103
103
|
if (r.error) throw Error(r.error.message);
|
|
104
|
-
await
|
|
104
|
+
await P(), E({
|
|
105
105
|
title: t ? `Now sharing "${n}"` : `Stopped sharing "${n}"`,
|
|
106
106
|
description: "Your workspace only ever sees this as an aggregate signal.",
|
|
107
107
|
variant: "success"
|
|
108
108
|
});
|
|
109
109
|
} catch {
|
|
110
|
-
|
|
110
|
+
E({
|
|
111
111
|
title: "Couldn't update what you share",
|
|
112
112
|
description: "Nothing was changed. Please try again.",
|
|
113
113
|
variant: "error"
|
|
114
114
|
});
|
|
115
115
|
} finally {
|
|
116
|
-
|
|
116
|
+
U(null);
|
|
117
117
|
}
|
|
118
|
-
},
|
|
118
|
+
}, pe = async (e, t) => {
|
|
119
119
|
let n = C(e.source);
|
|
120
120
|
K(e.source);
|
|
121
|
+
try {
|
|
122
|
+
let r = await B({ variables: {
|
|
123
|
+
source: e.source,
|
|
124
|
+
mode: e.mode,
|
|
125
|
+
onDeviceOnly: t
|
|
126
|
+
} });
|
|
127
|
+
if (r.error) throw Error(r.error.message);
|
|
128
|
+
await R(), E({
|
|
129
|
+
title: t ? `${n} now on-device only` : `${n} on-device-only turned off`,
|
|
130
|
+
description: t ? "Only aggregated app/category/timing data reaches your workspace — no titles, URLs, or other details." : void 0,
|
|
131
|
+
variant: "success"
|
|
132
|
+
});
|
|
133
|
+
} catch {
|
|
134
|
+
E({
|
|
135
|
+
title: "Couldn't update on-device-only",
|
|
136
|
+
description: "Nothing was changed. Please try again.",
|
|
137
|
+
variant: "error"
|
|
138
|
+
});
|
|
139
|
+
} finally {
|
|
140
|
+
K(null);
|
|
141
|
+
}
|
|
142
|
+
}, me = async (e, t) => {
|
|
143
|
+
let n = C(e.source);
|
|
144
|
+
W(e.source);
|
|
121
145
|
try {
|
|
122
146
|
if (t) {
|
|
123
|
-
let t = await
|
|
147
|
+
let t = await B({ variables: { source: e.source } });
|
|
124
148
|
if (t.error) throw Error(t.error.message);
|
|
125
149
|
} else {
|
|
126
|
-
let t = await
|
|
150
|
+
let t = await V({ variables: { source: e.source } });
|
|
127
151
|
if (t.error) throw Error(t.error.message);
|
|
128
152
|
}
|
|
129
|
-
await
|
|
153
|
+
await R(), E({
|
|
130
154
|
title: t ? `${n} connected` : `${n} disconnected`,
|
|
131
155
|
variant: "success"
|
|
132
156
|
});
|
|
133
157
|
} catch {
|
|
134
|
-
|
|
158
|
+
E({
|
|
135
159
|
title: "Couldn't update this source",
|
|
136
160
|
description: "Nothing was changed. Please try again.",
|
|
137
161
|
variant: "error"
|
|
138
162
|
});
|
|
139
163
|
} finally {
|
|
140
|
-
|
|
164
|
+
W(null);
|
|
141
165
|
}
|
|
142
166
|
};
|
|
143
|
-
return /* @__PURE__ */ b(
|
|
167
|
+
return /* @__PURE__ */ b(l, {
|
|
144
168
|
...$,
|
|
145
169
|
children: [
|
|
146
170
|
/* @__PURE__ */ b("div", {
|
|
@@ -153,7 +177,7 @@ function T() {
|
|
|
153
177
|
className: "mb-4 flex items-start gap-3",
|
|
154
178
|
children: [/* @__PURE__ */ y("div", {
|
|
155
179
|
className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary",
|
|
156
|
-
children: /* @__PURE__ */ y(
|
|
180
|
+
children: /* @__PURE__ */ y(_, { className: "h-5 w-5" })
|
|
157
181
|
}), /* @__PURE__ */ b("div", {
|
|
158
182
|
className: "min-w-0",
|
|
159
183
|
children: [/* @__PURE__ */ b("h2", {
|
|
@@ -171,10 +195,10 @@ function T() {
|
|
|
171
195
|
children: "Everything you capture about your life. Locked to your account — it never leaves this side."
|
|
172
196
|
})]
|
|
173
197
|
})]
|
|
174
|
-
}),
|
|
198
|
+
}), N && !J ? /* @__PURE__ */ y("div", {
|
|
175
199
|
className: "rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-3 text-xs text-status-error-text",
|
|
176
200
|
children: "Couldn't load your private data summary right now."
|
|
177
|
-
}) : !J || J.totalPersonalEvents === 0 ? /* @__PURE__ */ y(
|
|
201
|
+
}) : !J || J.totalPersonalEvents === 0 ? /* @__PURE__ */ y(d, {
|
|
178
202
|
illustration: "empty-data",
|
|
179
203
|
title: "Nothing private captured yet",
|
|
180
204
|
description: "Log your first life entry and it will live here — always private to you."
|
|
@@ -183,7 +207,7 @@ function T() {
|
|
|
183
207
|
children: [/* @__PURE__ */ y(n, {
|
|
184
208
|
title: "Private life entries",
|
|
185
209
|
value: J.totalPersonalEvents.toLocaleString(),
|
|
186
|
-
icon: /* @__PURE__ */ y(
|
|
210
|
+
icon: /* @__PURE__ */ y(_, { className: "h-5 w-5" }),
|
|
187
211
|
description: "captured in the last 30 days"
|
|
188
212
|
}), X.length === 0 ? /* @__PURE__ */ y("p", {
|
|
189
213
|
className: "text-xs text-text-secondary",
|
|
@@ -200,11 +224,11 @@ function T() {
|
|
|
200
224
|
className: "min-w-0",
|
|
201
225
|
children: [/* @__PURE__ */ y("p", {
|
|
202
226
|
className: "truncate text-sm font-medium text-text-primary",
|
|
203
|
-
children:
|
|
227
|
+
children: oe(e.domain)
|
|
204
228
|
}), /* @__PURE__ */ b("p", {
|
|
205
229
|
className: "mt-0.5 text-xs text-text-secondary",
|
|
206
230
|
children: [
|
|
207
|
-
|
|
231
|
+
re(e.totalSeconds),
|
|
208
232
|
" ·",
|
|
209
233
|
" ",
|
|
210
234
|
e.eventCount.toLocaleString(),
|
|
@@ -212,7 +236,7 @@ function T() {
|
|
|
212
236
|
e.eventCount === 1 ? "entry" : "entries"
|
|
213
237
|
]
|
|
214
238
|
})]
|
|
215
|
-
}), /* @__PURE__ */ y(
|
|
239
|
+
}), /* @__PURE__ */ y(_, {
|
|
216
240
|
className: "h-3.5 w-3.5 shrink-0 text-text-muted",
|
|
217
241
|
"aria-label": "Private to you"
|
|
218
242
|
})]
|
|
@@ -238,7 +262,7 @@ function T() {
|
|
|
238
262
|
className: "mb-4 flex items-start gap-3",
|
|
239
263
|
children: [/* @__PURE__ */ y("div", {
|
|
240
264
|
className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-status-info-bg/40 text-status-info-text",
|
|
241
|
-
children: /* @__PURE__ */ y(
|
|
265
|
+
children: /* @__PURE__ */ y(se, { className: "h-5 w-5" })
|
|
242
266
|
}), /* @__PURE__ */ b("div", {
|
|
243
267
|
className: "min-w-0",
|
|
244
268
|
children: [/* @__PURE__ */ y("h2", {
|
|
@@ -249,13 +273,13 @@ function T() {
|
|
|
249
273
|
children: "Aggregate signals only. Every switch is off until you turn it on."
|
|
250
274
|
})]
|
|
251
275
|
})]
|
|
252
|
-
}),
|
|
276
|
+
}), A && !q ? /* @__PURE__ */ y("div", {
|
|
253
277
|
className: "rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-3 text-xs text-status-error-text",
|
|
254
278
|
children: "Couldn't load your sharing settings right now."
|
|
255
279
|
}) : /* @__PURE__ */ y("div", {
|
|
256
280
|
className: "space-y-3",
|
|
257
281
|
children: S.map((e) => {
|
|
258
|
-
let n = q ? q[e.key] : !1, r =
|
|
282
|
+
let n = q ? q[e.key] : !1, r = H === e.key;
|
|
259
283
|
return /* @__PURE__ */ y("div", {
|
|
260
284
|
className: "rounded-lg border border-border-subtle bg-bg-sunken/40 p-3",
|
|
261
285
|
children: /* @__PURE__ */ b("div", {
|
|
@@ -275,7 +299,7 @@ function T() {
|
|
|
275
299
|
checked: n,
|
|
276
300
|
disabled: r || !q,
|
|
277
301
|
ariaLabel: `Share "${e.label}" with your workspace`,
|
|
278
|
-
onChange: (t) =>
|
|
302
|
+
onChange: (t) => fe(e.key, t)
|
|
279
303
|
}), /* @__PURE__ */ y("span", {
|
|
280
304
|
className: t("text-[0.65rem] font-medium uppercase tracking-wide", n ? "text-status-success-text" : "text-text-muted"),
|
|
281
305
|
children: r ? "Saving…" : n ? "On" : "Off"
|
|
@@ -307,7 +331,7 @@ function T() {
|
|
|
307
331
|
className: "mb-4 flex items-start gap-3",
|
|
308
332
|
children: [/* @__PURE__ */ y("div", {
|
|
309
333
|
className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary",
|
|
310
|
-
children: /* @__PURE__ */ y(
|
|
334
|
+
children: /* @__PURE__ */ y(h, { className: "h-5 w-5" })
|
|
311
335
|
}), /* @__PURE__ */ b("div", {
|
|
312
336
|
className: "min-w-0",
|
|
313
337
|
children: [/* @__PURE__ */ b("h2", {
|
|
@@ -330,7 +354,7 @@ function T() {
|
|
|
330
354
|
children: [
|
|
331
355
|
/* @__PURE__ */ b("div", {
|
|
332
356
|
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",
|
|
333
|
-
children: [/* @__PURE__ */ y(
|
|
357
|
+
children: [/* @__PURE__ */ y(g, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), /* @__PURE__ */ y("span", { children: "Your workspace only ever receives aggregate work signals — coarse yes/no bands, never entries, locations, health or timestamps." })]
|
|
334
358
|
}),
|
|
335
359
|
/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("p", {
|
|
336
360
|
className: "mb-2 text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
@@ -350,11 +374,11 @@ function T() {
|
|
|
350
374
|
className: "flex items-center justify-between gap-3 rounded-lg border border-border-subtle bg-bg-sunken/40 px-3 py-2.5",
|
|
351
375
|
children: [/* @__PURE__ */ b("span", {
|
|
352
376
|
className: "flex min-w-0 items-center gap-2 text-sm text-text-primary",
|
|
353
|
-
children: [/* @__PURE__ */ y(
|
|
377
|
+
children: [/* @__PURE__ */ y(m, { className: "h-4 w-4 shrink-0 text-status-success-text" }), /* @__PURE__ */ y("span", {
|
|
354
378
|
className: "truncate",
|
|
355
379
|
children: C(e)
|
|
356
380
|
})]
|
|
357
|
-
}), /* @__PURE__ */ y(
|
|
381
|
+
}), /* @__PURE__ */ y(u, {
|
|
358
382
|
variant: "success",
|
|
359
383
|
label: "Shared",
|
|
360
384
|
dot: !0
|
|
@@ -377,7 +401,7 @@ function T() {
|
|
|
377
401
|
className: "mb-4 flex items-start gap-3",
|
|
378
402
|
children: [/* @__PURE__ */ y("div", {
|
|
379
403
|
className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary",
|
|
380
|
-
children: /* @__PURE__ */ y(
|
|
404
|
+
children: /* @__PURE__ */ y(v, { className: "h-5 w-5" })
|
|
381
405
|
}), /* @__PURE__ */ b("div", {
|
|
382
406
|
className: "min-w-0",
|
|
383
407
|
children: [/* @__PURE__ */ y("h3", {
|
|
@@ -388,20 +412,20 @@ function T() {
|
|
|
388
412
|
children: "Each capture source only feeds your private record once you consent — and you can withdraw at any moment."
|
|
389
413
|
})]
|
|
390
414
|
})]
|
|
391
|
-
}),
|
|
415
|
+
}), L && Y.length === 0 ? /* @__PURE__ */ y("div", {
|
|
392
416
|
className: "rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-3 text-xs text-status-error-text",
|
|
393
417
|
children: "Couldn't load your connected sources right now."
|
|
394
|
-
}) :
|
|
418
|
+
}) : I && Y.length === 0 ? /* @__PURE__ */ y(r, {
|
|
395
419
|
variant: "list",
|
|
396
420
|
count: 3
|
|
397
|
-
}) : Y.length === 0 ? /* @__PURE__ */ y(
|
|
421
|
+
}) : Y.length === 0 ? /* @__PURE__ */ y(d, {
|
|
398
422
|
illustration: "empty-data",
|
|
399
423
|
title: "No sources connected yet",
|
|
400
424
|
description: "When you connect a capture source — a calendar, location or health signal — you'll grant and revoke exactly what it may feed here."
|
|
401
425
|
}) : /* @__PURE__ */ y("div", {
|
|
402
426
|
className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3",
|
|
403
427
|
children: Y.map((e) => {
|
|
404
|
-
let n =
|
|
428
|
+
let n = de === e.source;
|
|
405
429
|
return /* @__PURE__ */ y(x, {
|
|
406
430
|
treatment: "metric",
|
|
407
431
|
className: "p-4",
|
|
@@ -415,15 +439,20 @@ function T() {
|
|
|
415
439
|
}), /* @__PURE__ */ b("div", {
|
|
416
440
|
className: "mt-1.5 flex flex-wrap items-center gap-1.5",
|
|
417
441
|
children: [
|
|
418
|
-
/* @__PURE__ */ y(
|
|
442
|
+
/* @__PURE__ */ y(u, {
|
|
419
443
|
variant: "muted",
|
|
420
444
|
label: C(e.mode)
|
|
421
445
|
}),
|
|
422
|
-
e.
|
|
423
|
-
|
|
424
|
-
|
|
446
|
+
e.granted && /* @__PURE__ */ y("button", {
|
|
447
|
+
type: "button",
|
|
448
|
+
role: "switch",
|
|
449
|
+
"aria-checked": e.onDeviceOnly,
|
|
450
|
+
disabled: G === e.source,
|
|
451
|
+
onClick: () => pe(e, !e.onDeviceOnly),
|
|
452
|
+
className: t("rounded-full border px-2 py-0.5 text-[0.65rem] font-medium uppercase tracking-wide transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50", e.onDeviceOnly ? "border-status-info-border/60 bg-status-info-bg/40 text-status-info-text" : "border-border-subtle bg-bg-sunken/40 text-text-muted hover:text-text-secondary", G === e.source && "cursor-not-allowed opacity-50"),
|
|
453
|
+
children: G === e.source ? "Saving…" : e.onDeviceOnly ? "On-device only" : "Turn on on-device only"
|
|
425
454
|
}),
|
|
426
|
-
e.retentionDays != null && /* @__PURE__ */ y(
|
|
455
|
+
e.retentionDays != null && /* @__PURE__ */ y(u, {
|
|
427
456
|
variant: "muted",
|
|
428
457
|
label: `Kept ${e.retentionDays}d`
|
|
429
458
|
})
|
|
@@ -435,7 +464,7 @@ function T() {
|
|
|
435
464
|
checked: e.granted,
|
|
436
465
|
disabled: n,
|
|
437
466
|
ariaLabel: `Consent for ${C(e.source)}`,
|
|
438
|
-
onChange: (t) =>
|
|
467
|
+
onChange: (t) => me(e, t)
|
|
439
468
|
}), /* @__PURE__ */ y("span", {
|
|
440
469
|
className: t("text-[0.65rem] font-medium uppercase tracking-wide", e.granted ? "text-status-success-text" : "text-text-muted"),
|
|
441
470
|
children: n ? "Saving…" : e.granted ? "Granted" : "Revoked"
|
|
@@ -446,9 +475,9 @@ function T() {
|
|
|
446
475
|
})
|
|
447
476
|
})]
|
|
448
477
|
}),
|
|
449
|
-
/* @__PURE__ */ y(
|
|
450
|
-
toasts:
|
|
451
|
-
onDismiss:
|
|
478
|
+
/* @__PURE__ */ y(te, {
|
|
479
|
+
toasts: le,
|
|
480
|
+
onDismiss: ue
|
|
452
481
|
})
|
|
453
482
|
]
|
|
454
483
|
});
|
|
@@ -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\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 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.onDeviceOnly && (\n <StatusBadge variant=\"info\" label=\"On-device only\" />\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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"}
|
|
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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-timecampus",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.731.2",
|
|
4
4
|
"description": "Burdenoff TimeCampus Microfrontend - AI-powered time management and productivity platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -26,15 +26,15 @@
|
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
27
|
"scripts": {
|
|
28
28
|
"dev": "vite",
|
|
29
|
-
"build": "tsc && vite build",
|
|
29
|
+
"build": "tsc && bash scripts/build-limits.sh --gate vite build",
|
|
30
30
|
"lint": "eslint .",
|
|
31
31
|
"lint:sanity": "if [ \"${CI:-}\" = \"true\" ]; then bun run lint; else bun run lint -- --cache --cache-location .eslintcache; fi",
|
|
32
32
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
33
|
-
"type:check": "tsc --noEmit",
|
|
34
|
-
"test": "vitest run",
|
|
33
|
+
"type:check": "bash scripts/build-limits.sh tsc --noEmit",
|
|
34
|
+
"test": "bash scripts/build-limits.sh vitest run",
|
|
35
35
|
"test:watch": "vitest",
|
|
36
|
-
"test:coverage": "vitest run --coverage",
|
|
37
|
-
"sanity": "bun run codegen && bun run lint:sanity && bun run format && bun run type:check && bun run test && bun run build && bun run build-storybook",
|
|
36
|
+
"test:coverage": "bash scripts/build-limits.sh vitest run --coverage",
|
|
37
|
+
"sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run lint:sanity && bun run format && bun run type:check && bun run test && bun run build && bun run build-storybook'",
|
|
38
38
|
"storybook": "storybook dev -p 6006",
|
|
39
39
|
"build-storybook": "storybook build",
|
|
40
40
|
"test:storybook": "test-storybook",
|