@burdenoff/microfe-timecampus 2026.904.1 → 2026.1025.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.
@@ -0,0 +1,553 @@
1
+ import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
2
+ import { StatCard as t } from "../../components/StatCard.js";
3
+ import { LoadingSkeleton as ee } from "../../components/LoadingSkeleton.js";
4
+ import { useCancelFocusBlockMutation as te, useEndFocusSessionMutation as ne, useGetActiveFocusSessionQuery as re, useGetFocusBlockSuggestionsQuery as ie, useGetFocusPreferencesQuery as ae, useGetFocusProtectionSummaryQuery as oe, useListFocusBlocksQuery as se, useLogFocusInterruptionMutation as ce, useRespondToFocusBlockSuggestionMutation as le, useStartFocusSessionMutation as ue, useUpdateFocusPreferencesMutation as de } from "../../../generated/wspace-operations.js";
5
+ import { PageLayout as n } from "../../components/PageLayout.js";
6
+ import { StatusBadge as r } from "../../components/StatusBadge.js";
7
+ import { ToastContainer as fe, useToast as pe } from "../../components/Toast.js";
8
+ import { TimeCampusFocusBlockResponse as i, TimeCampusFocusProtectionLevel as a, TimeCampusFocusSessionStatus as o } from "../../../generated/wspace-types.js";
9
+ import { DAY_MS as me, canStartBlock as he, formatDuration as s, formatRange as c, localDayStart as ge, minutesLeft as _e, minutesToTime as l, parseSiteList as ve, reasonLabel as ye, timeToMinutes as be } from "./focusHelpers.js";
10
+ import { useEffect as u, useMemo as d, useState as f } from "react";
11
+ import { AlarmClock as xe, Ban as Se, CalendarClock as Ce, Check as we, Play as Te, Shield as p, ShieldCheck as Ee, Square as De, Target as m, X as Oe } from "lucide-react";
12
+ import { Fragment as ke, jsx as h, jsxs as g } from "react/jsx-runtime";
13
+ import { GlassCard as Ae } from "@burdenoff/fe-libs/ui";
14
+ //#region src/timecampus/pages/focus/FocusProtectionPage.tsx
15
+ var _ = "Focus Protection", v = "Adaptive focus blocks and distraction blocking during focus sessions", je = [
16
+ "GetActiveFocusSession",
17
+ "GetFocusBlockSuggestions",
18
+ "ListFocusBlocks",
19
+ "GetFocusProtectionSummary"
20
+ ], y = "rounded-md border border-border-subtle bg-bg-surface px-3 py-1.5 text-sm text-text-primary", b = "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50", x = "inline-flex items-center gap-1.5 rounded-md border border-border-subtle bg-bg-surface px-3 py-1.5 text-sm font-medium text-text-primary hover:bg-bg-surface-hover disabled:opacity-50";
21
+ function S(e) {
22
+ return e instanceof Error ? e.message : "Something went wrong";
23
+ }
24
+ function C() {
25
+ let { workspaceId: C = "" } = e(), { toasts: Me, addToast: w, dismissToast: Ne } = pe(), [T, Pe] = f(0), [E, Fe] = f(() => Date.now());
26
+ u(() => {
27
+ let e = window.setInterval(() => Fe(Date.now()), 6e4);
28
+ return () => window.clearInterval(e);
29
+ }, []);
30
+ let D = d(() => ge(/* @__PURE__ */ new Date(), T), [T]), Ie = d(() => new Date(D.getTime() + me), [D]), Le = d(() => {
31
+ let e = /* @__PURE__ */ new Date();
32
+ return {
33
+ from: (/* @__PURE__ */ new Date(e.getTime() - 7 * me)).toISOString(),
34
+ to: e.toISOString()
35
+ };
36
+ }, []), O = re({ skip: !C }), k = ie({
37
+ variables: { dayStart: D.toISOString() },
38
+ skip: !C
39
+ }), Re = se({
40
+ variables: {
41
+ from: D.toISOString(),
42
+ to: Ie.toISOString()
43
+ },
44
+ skip: !C
45
+ }), A = ae({ skip: !C }), ze = oe({
46
+ variables: Le,
47
+ skip: !C
48
+ }), j = { refetchQueries: je }, [Be, { loading: M }] = ue(j), [Ve, { loading: N }] = ne(j), [He] = ce(j), [Ue, { loading: P }] = le(j), [We] = te(j), [Ge, { loading: F }] = de({ refetchQueries: ["GetFocusPreferences", "GetFocusBlockSuggestions"] }), [I, Ke] = f(50), [L, qe] = f(!1), R = A.data?.timeCampusFocusPreferences, [z, B] = f("09:00"), [V, H] = f("18:00"), [U, W] = f(90), [Je, G] = f(2), [K, q] = f(10), [Ye, Xe] = f(!0), [Ze, J] = f("");
49
+ u(() => {
50
+ R && (B(l(R.workdayStartMinute)), H(l(R.workdayEndMinute)), W(R.preferredBlockMinutes), G(R.maxBlocksPerDay), q(R.meetingBufferMinutes), Xe(R.blockingEnabled), J(R.blockedDomains.join("\n")));
51
+ }, [R]);
52
+ let Y = O.data?.timeCampusActiveFocusSession ?? null, Qe = k.data?.timeCampusFocusBlockSuggestions ?? [], $e = Re.data?.timeCampusFocusBlocks ?? [], X = ze.data?.timeCampusFocusSummary, et = !O.data && O.loading || !A.data && A.loading, tt = O.error ?? A.error;
53
+ async function Z(e, t) {
54
+ try {
55
+ await e(), w({
56
+ title: t,
57
+ variant: "success"
58
+ });
59
+ } catch (e) {
60
+ w({
61
+ title: S(e),
62
+ variant: "error"
63
+ });
64
+ }
65
+ }
66
+ let nt = (e) => Z(() => Be({ variables: {
67
+ plannedDurationSeconds: e ? void 0 : I * 60,
68
+ protectionLevel: L ? a.Strict : a.Standard,
69
+ focusBlockId: e
70
+ } }), "Focus session started"), rt = (e) => Y ? Z(() => Ve({ variables: {
71
+ id: Y.id,
72
+ status: e
73
+ } }), e === o.Completed ? "Focus session completed" : "Focus session abandoned") : void 0, Q = (e, t) => Z(() => Ue({ variables: { input: {
74
+ startTime: e.startTime,
75
+ endTime: e.endTime,
76
+ response: t,
77
+ dayStart: D.toISOString(),
78
+ snoozeMinutes: t === i.Snooze ? 30 : null
79
+ } } }), t === i.Accept ? "Focus block scheduled" : t === i.Snooze ? "Snoozed for 30 minutes" : "Suggestion dismissed"), it = () => {
80
+ let e = be(z), t = be(V);
81
+ if (e === null || t === null) {
82
+ w({
83
+ title: "Use HH:MM for the workday",
84
+ variant: "error"
85
+ });
86
+ return;
87
+ }
88
+ Z(() => Ge({ variables: { input: {
89
+ workdayStartMinute: e,
90
+ workdayEndMinute: t,
91
+ preferredBlockMinutes: U,
92
+ minBlockMinutes: Math.min(45, U),
93
+ maxBlocksPerDay: Je,
94
+ meetingBufferMinutes: K,
95
+ blockingEnabled: Ye,
96
+ blockedDomains: ve(Ze)
97
+ } } }), "Focus settings saved");
98
+ };
99
+ if (et) return /* @__PURE__ */ h(n, {
100
+ title: _,
101
+ description: v,
102
+ children: /* @__PURE__ */ h(ee, {
103
+ variant: "card",
104
+ count: 4
105
+ })
106
+ });
107
+ if (tt && !O.data) return /* @__PURE__ */ h(n, {
108
+ title: _,
109
+ description: v,
110
+ children: /* @__PURE__ */ h("div", {
111
+ className: "rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
112
+ children: "Failed to load focus protection. Please try again later."
113
+ })
114
+ });
115
+ let $ = Y ? _e(Y.startTime, Y.plannedDurationSeconds, E) : null, at = X ? Math.round(X.averageProtectionRatio * 100) : null;
116
+ return /* @__PURE__ */ g(n, {
117
+ title: _,
118
+ description: v,
119
+ purpose: "Protect the hours that matter: TimeCampus suggests focus blocks around your meetings and the times you focus best, and blocks distracting sites in your browser while a session runs.",
120
+ children: [
121
+ /* @__PURE__ */ h(fe, {
122
+ toasts: Me,
123
+ onDismiss: Ne
124
+ }),
125
+ /* @__PURE__ */ g("div", {
126
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
127
+ children: [
128
+ /* @__PURE__ */ h(t, {
129
+ featured: !0,
130
+ title: "Protected Time",
131
+ value: X ? s(X.totalProtectedSeconds) : "--",
132
+ icon: /* @__PURE__ */ h(Ee, { className: "h-5 w-5" }),
133
+ description: "Last 7 days"
134
+ }),
135
+ /* @__PURE__ */ h(t, {
136
+ title: "Protection Ratio",
137
+ value: at === null ? "--" : `${at}%`,
138
+ icon: /* @__PURE__ */ h(p, { className: "h-5 w-5" }),
139
+ description: "Protected vs interrupted"
140
+ }),
141
+ /* @__PURE__ */ h(t, {
142
+ title: "Sessions",
143
+ value: X ? String(X.totalSessions) : "--",
144
+ icon: /* @__PURE__ */ h(m, { className: "h-5 w-5" }),
145
+ description: X ? `${X.completedSessions} completed` : "Last 7 days"
146
+ }),
147
+ /* @__PURE__ */ h(t, {
148
+ title: "Interruptions",
149
+ value: X ? String(X.totalInterruptionCount) : "--",
150
+ icon: /* @__PURE__ */ h(Se, { className: "h-5 w-5" }),
151
+ description: "Logged, detected or bypassed"
152
+ })
153
+ ]
154
+ }),
155
+ /* @__PURE__ */ h("div", {
156
+ className: "mt-6",
157
+ "data-testid": "focus-session-card",
158
+ children: /* @__PURE__ */ g(Ae, {
159
+ treatment: "glass",
160
+ glow: !0,
161
+ className: "p-6",
162
+ children: [/* @__PURE__ */ g("div", {
163
+ className: "flex flex-wrap items-start justify-between gap-4",
164
+ children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ g("h3", {
165
+ className: "flex items-center gap-2 text-sm font-semibold text-text-primary",
166
+ children: [/* @__PURE__ */ h(p, { className: "h-4 w-4" }), "Focus session"]
167
+ }), Y ? /* @__PURE__ */ g("p", {
168
+ className: "mt-1 text-sm text-text-secondary",
169
+ "data-testid": "focus-session-status",
170
+ children: [
171
+ $ === null ? "Open-ended session in progress." : $ === 0 ? "Planned time is up — finish when you're ready." : `${$} minute${$ === 1 ? "" : "s"} left.`,
172
+ " ",
173
+ Y.interruptionCount,
174
+ " interruption",
175
+ Y.interruptionCount === 1 ? "" : "s",
176
+ ",",
177
+ " ",
178
+ Y.blockedAttemptCount,
179
+ " site",
180
+ Y.blockedAttemptCount === 1 ? "" : "s",
181
+ " blocked."
182
+ ]
183
+ }) : /* @__PURE__ */ h("p", {
184
+ className: "mt-1 text-sm text-text-secondary",
185
+ children: "No session running. While one is active, the TimeCampus browser extension blocks the sites listed below."
186
+ })] }), Y && /* @__PURE__ */ h(r, {
187
+ variant: Y.protectionLevel === a.Strict ? "warning" : "success",
188
+ label: Y.protectionLevel === a.Strict ? "Strict" : "Standard"
189
+ })]
190
+ }), /* @__PURE__ */ h("div", {
191
+ className: "mt-4 flex flex-wrap items-center gap-3",
192
+ children: Y ? /* @__PURE__ */ g(ke, { children: [
193
+ /* @__PURE__ */ g("button", {
194
+ type: "button",
195
+ className: b,
196
+ disabled: N,
197
+ onClick: () => rt(o.Completed),
198
+ "data-testid": "focus-end",
199
+ children: [/* @__PURE__ */ h(De, { className: "h-4 w-4" }), " End session"]
200
+ }),
201
+ /* @__PURE__ */ h("button", {
202
+ type: "button",
203
+ className: x,
204
+ disabled: N,
205
+ onClick: () => rt(o.Abandoned),
206
+ children: "Abandon"
207
+ }),
208
+ /* @__PURE__ */ h("button", {
209
+ type: "button",
210
+ className: x,
211
+ onClick: () => Z(() => He({ variables: { focusSessionId: Y.id } }), "Interruption logged"),
212
+ "data-testid": "focus-log-interruption",
213
+ children: "Log an interruption"
214
+ })
215
+ ] }) : /* @__PURE__ */ g(ke, { children: [
216
+ /* @__PURE__ */ g("label", {
217
+ className: "flex items-center gap-2 text-sm text-text-secondary",
218
+ children: ["Length", /* @__PURE__ */ g("select", {
219
+ className: y,
220
+ value: I,
221
+ onChange: (e) => Ke(Number(e.target.value)),
222
+ "data-testid": "focus-length",
223
+ children: [
224
+ /* @__PURE__ */ h("option", {
225
+ value: 25,
226
+ children: "25 min"
227
+ }),
228
+ /* @__PURE__ */ h("option", {
229
+ value: 50,
230
+ children: "50 min"
231
+ }),
232
+ /* @__PURE__ */ h("option", {
233
+ value: 90,
234
+ children: "90 min"
235
+ }),
236
+ /* @__PURE__ */ h("option", {
237
+ value: 120,
238
+ children: "2 hours"
239
+ })
240
+ ]
241
+ })]
242
+ }),
243
+ /* @__PURE__ */ g("label", {
244
+ className: "flex items-center gap-2 text-sm text-text-secondary",
245
+ children: [/* @__PURE__ */ h("input", {
246
+ type: "checkbox",
247
+ checked: L,
248
+ onChange: (e) => qe(e.target.checked),
249
+ className: "h-4 w-4 rounded border-border-subtle"
250
+ }), "Strict (no “continue anyway”)"]
251
+ }),
252
+ /* @__PURE__ */ g("button", {
253
+ type: "button",
254
+ className: b,
255
+ disabled: M,
256
+ onClick: () => nt(),
257
+ "data-testid": "focus-start",
258
+ children: [/* @__PURE__ */ h(Te, { className: "h-4 w-4" }), " Start focus session"]
259
+ })
260
+ ] })
261
+ })]
262
+ })
263
+ }),
264
+ /* @__PURE__ */ g("div", {
265
+ className: "mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2",
266
+ children: [/* @__PURE__ */ g("div", {
267
+ className: "rounded-xl border border-border-seam bg-bg-surface p-6 shadow-elevation-1",
268
+ "data-testid": "focus-suggestions",
269
+ children: [
270
+ /* @__PURE__ */ g("div", {
271
+ className: "flex flex-wrap items-center justify-between gap-2",
272
+ children: [/* @__PURE__ */ g("h3", {
273
+ className: "flex items-center gap-2 text-sm font-semibold text-text-primary",
274
+ children: [/* @__PURE__ */ h(Ce, { className: "h-4 w-4" }), "Suggested focus blocks"]
275
+ }), /* @__PURE__ */ h("div", {
276
+ className: "flex gap-1",
277
+ role: "group",
278
+ "aria-label": "Day to plan",
279
+ children: ["Today", "Tomorrow"].map((e, t) => /* @__PURE__ */ h("button", {
280
+ type: "button",
281
+ "aria-pressed": T === t,
282
+ className: T === t ? b : x,
283
+ onClick: () => Pe(t),
284
+ children: e
285
+ }, e))
286
+ })]
287
+ }),
288
+ /* @__PURE__ */ h("p", {
289
+ className: "mt-1 text-xs text-text-secondary",
290
+ children: "Ranked from your peak hours, meetings and how well past sessions held up. Snoozed and dismissed slots are planned around."
291
+ }),
292
+ /* @__PURE__ */ g("div", {
293
+ className: "mt-4 space-y-3",
294
+ children: [
295
+ k.error && /* @__PURE__ */ h("p", {
296
+ className: "text-sm text-status-error-text",
297
+ children: S(k.error)
298
+ }),
299
+ !k.error && k.data && Qe.length === 0 && /* @__PURE__ */ g("p", {
300
+ className: "text-sm text-text-secondary",
301
+ "data-testid": "focus-suggestions-empty",
302
+ children: [
303
+ "No free window left for",
304
+ " ",
305
+ T === 0 ? "today" : "tomorrow",
306
+ " — your day is full or you have scheduled your maximum blocks."
307
+ ]
308
+ }),
309
+ Qe.map((e) => /* @__PURE__ */ g("div", {
310
+ className: "rounded-lg border border-border-subtle p-3",
311
+ "data-testid": "focus-suggestion",
312
+ children: [
313
+ /* @__PURE__ */ g("div", {
314
+ className: "flex flex-wrap items-center justify-between gap-2",
315
+ children: [/* @__PURE__ */ g("span", {
316
+ className: "text-sm font-medium text-text-primary",
317
+ children: [c(e.startTime, e.endTime), /* @__PURE__ */ h("span", {
318
+ className: "ml-2 text-xs text-text-secondary",
319
+ children: s(e.durationMinutes * 60)
320
+ })]
321
+ }), /* @__PURE__ */ g("span", {
322
+ className: "text-xs text-text-secondary",
323
+ children: [
324
+ "Fit ",
325
+ Math.round(e.score * 100),
326
+ "%"
327
+ ]
328
+ })]
329
+ }),
330
+ /* @__PURE__ */ h("div", {
331
+ className: "mt-2 flex flex-wrap gap-1.5",
332
+ children: e.reasons.map((e) => /* @__PURE__ */ h(r, {
333
+ variant: "info",
334
+ label: ye(e)
335
+ }, e))
336
+ }),
337
+ /* @__PURE__ */ g("div", {
338
+ className: "mt-3 flex flex-wrap gap-2",
339
+ children: [
340
+ /* @__PURE__ */ g("button", {
341
+ type: "button",
342
+ className: b,
343
+ disabled: P,
344
+ onClick: () => Q(e, i.Accept),
345
+ "data-testid": "focus-suggestion-accept",
346
+ children: [/* @__PURE__ */ h(we, { className: "h-4 w-4" }), " Accept"]
347
+ }),
348
+ /* @__PURE__ */ g("button", {
349
+ type: "button",
350
+ className: x,
351
+ disabled: P,
352
+ onClick: () => Q(e, i.Snooze),
353
+ "data-testid": "focus-suggestion-snooze",
354
+ children: [/* @__PURE__ */ h(xe, { className: "h-4 w-4" }), " Snooze 30 min"]
355
+ }),
356
+ /* @__PURE__ */ g("button", {
357
+ type: "button",
358
+ className: x,
359
+ disabled: P,
360
+ onClick: () => Q(e, i.Dismiss),
361
+ "data-testid": "focus-suggestion-dismiss",
362
+ children: [/* @__PURE__ */ h(Oe, { className: "h-4 w-4" }), " Dismiss"]
363
+ })
364
+ ]
365
+ })
366
+ ]
367
+ }, e.startTime))
368
+ ]
369
+ })
370
+ ]
371
+ }), /* @__PURE__ */ g("div", {
372
+ className: "rounded-xl border border-border-seam bg-bg-surface p-6 shadow-elevation-1",
373
+ "data-testid": "focus-scheduled",
374
+ children: [/* @__PURE__ */ g("h3", {
375
+ className: "flex items-center gap-2 text-sm font-semibold text-text-primary",
376
+ children: [
377
+ /* @__PURE__ */ h(m, { className: "h-4 w-4" }),
378
+ "Scheduled (",
379
+ T === 0 ? "today" : "tomorrow",
380
+ ")"
381
+ ]
382
+ }), /* @__PURE__ */ g("div", {
383
+ className: "mt-4 space-y-3",
384
+ children: [Re.data && $e.length === 0 && /* @__PURE__ */ h("p", {
385
+ className: "text-sm text-text-secondary",
386
+ children: "Accept a suggestion to put a focus block on your schedule."
387
+ }), $e.map((e) => /* @__PURE__ */ g("div", {
388
+ className: "flex flex-wrap items-center justify-between gap-2 rounded-lg border border-border-subtle p-3",
389
+ "data-testid": "focus-scheduled-block",
390
+ children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("p", {
391
+ className: "text-sm font-medium text-text-primary",
392
+ children: c(e.startTime, e.endTime)
393
+ }), /* @__PURE__ */ g("p", {
394
+ className: "text-xs text-text-secondary",
395
+ children: [
396
+ e.source === "ADAPTIVE" ? "Suggested" : "Manual",
397
+ " ·",
398
+ " ",
399
+ s(e.durationMinutes * 60)
400
+ ]
401
+ })] }), e.status === "STARTED" ? /* @__PURE__ */ h(r, {
402
+ variant: "success",
403
+ label: "Started"
404
+ }) : /* @__PURE__ */ g("div", {
405
+ className: "flex gap-2",
406
+ children: [!Y && he(e.endTime, E) && /* @__PURE__ */ g("button", {
407
+ type: "button",
408
+ className: b,
409
+ disabled: M,
410
+ onClick: () => nt(e.id),
411
+ children: [/* @__PURE__ */ h(Te, { className: "h-4 w-4" }), " Start"]
412
+ }), /* @__PURE__ */ h("button", {
413
+ type: "button",
414
+ className: x,
415
+ onClick: () => Z(() => We({ variables: { id: e.id } }), "Focus block canceled"),
416
+ "data-testid": "focus-block-cancel",
417
+ children: "Cancel"
418
+ })]
419
+ })]
420
+ }, e.id))]
421
+ })]
422
+ })]
423
+ }),
424
+ /* @__PURE__ */ g("div", {
425
+ className: "mt-6 rounded-xl border border-border-seam bg-bg-surface p-6 shadow-elevation-1",
426
+ "data-testid": "focus-settings",
427
+ children: [
428
+ /* @__PURE__ */ g("h3", {
429
+ className: "flex items-center gap-2 text-sm font-semibold text-text-primary",
430
+ children: [/* @__PURE__ */ h(Se, { className: "h-4 w-4" }), "Distraction blocking and planning"]
431
+ }),
432
+ /* @__PURE__ */ g("div", {
433
+ className: "mt-4 grid grid-cols-1 gap-6 lg:grid-cols-2",
434
+ children: [/* @__PURE__ */ g("div", {
435
+ className: "space-y-3",
436
+ children: [
437
+ /* @__PURE__ */ g("label", {
438
+ className: "flex items-center justify-between gap-4 text-sm text-text-primary",
439
+ children: ["Block sites during focus sessions", /* @__PURE__ */ h("input", {
440
+ type: "checkbox",
441
+ checked: Ye,
442
+ onChange: (e) => Xe(e.target.checked),
443
+ className: "h-4 w-4 rounded border-border-subtle",
444
+ "data-testid": "focus-blocking-enabled"
445
+ })]
446
+ }),
447
+ /* @__PURE__ */ g("label", {
448
+ className: "block text-sm text-text-primary",
449
+ children: ["Blocked sites", /* @__PURE__ */ h("textarea", {
450
+ rows: 6,
451
+ value: Ze,
452
+ onChange: (e) => J(e.target.value),
453
+ className: `${y} mt-1 w-full font-mono`,
454
+ "aria-describedby": "focus-sites-help",
455
+ "data-testid": "focus-blocked-sites"
456
+ })]
457
+ }),
458
+ /* @__PURE__ */ h("p", {
459
+ id: "focus-sites-help",
460
+ className: "text-xs text-text-secondary",
461
+ children: "One site per line (subdomains are included). Blocking is enforced by the TimeCampus browser extension while a session is active; allow-listed apps on a session are never blocked."
462
+ })
463
+ ]
464
+ }), /* @__PURE__ */ g("div", {
465
+ className: "space-y-3",
466
+ children: [
467
+ /* @__PURE__ */ g("div", {
468
+ className: "flex items-center justify-between gap-4",
469
+ children: [/* @__PURE__ */ h("span", {
470
+ className: "text-sm text-text-primary",
471
+ children: "Workday"
472
+ }), /* @__PURE__ */ g("span", {
473
+ className: "flex items-center gap-2",
474
+ children: [
475
+ /* @__PURE__ */ h("input", {
476
+ type: "time",
477
+ value: z,
478
+ onChange: (e) => B(e.target.value),
479
+ className: y,
480
+ "aria-label": "Workday start"
481
+ }),
482
+ /* @__PURE__ */ h("span", {
483
+ className: "text-text-secondary",
484
+ children: "to"
485
+ }),
486
+ /* @__PURE__ */ h("input", {
487
+ type: "time",
488
+ value: V === "24:00" ? "23:59" : V,
489
+ onChange: (e) => H(e.target.value),
490
+ className: y,
491
+ "aria-label": "Workday end"
492
+ })
493
+ ]
494
+ })]
495
+ }),
496
+ /* @__PURE__ */ g("label", {
497
+ className: "flex items-center justify-between gap-4 text-sm text-text-primary",
498
+ children: ["Preferred block length (minutes)", /* @__PURE__ */ h("input", {
499
+ type: "number",
500
+ min: 15,
501
+ max: 240,
502
+ step: 15,
503
+ value: U,
504
+ onChange: (e) => W(Number(e.target.value)),
505
+ className: `${y} w-24 text-right`
506
+ })]
507
+ }),
508
+ /* @__PURE__ */ g("label", {
509
+ className: "flex items-center justify-between gap-4 text-sm text-text-primary",
510
+ children: ["Focus blocks per day", /* @__PURE__ */ h("input", {
511
+ type: "number",
512
+ min: 1,
513
+ max: 6,
514
+ value: Je,
515
+ onChange: (e) => G(Number(e.target.value)),
516
+ className: `${y} w-24 text-right`
517
+ })]
518
+ }),
519
+ /* @__PURE__ */ g("label", {
520
+ className: "flex items-center justify-between gap-4 text-sm text-text-primary",
521
+ children: ["Buffer around meetings (minutes)", /* @__PURE__ */ h("input", {
522
+ type: "number",
523
+ min: 0,
524
+ max: 60,
525
+ step: 5,
526
+ value: K,
527
+ onChange: (e) => q(Number(e.target.value)),
528
+ className: `${y} w-24 text-right`
529
+ })]
530
+ })
531
+ ]
532
+ })]
533
+ }),
534
+ /* @__PURE__ */ h("div", {
535
+ className: "mt-4 flex justify-end",
536
+ children: /* @__PURE__ */ h("button", {
537
+ type: "button",
538
+ className: b,
539
+ disabled: F,
540
+ onClick: it,
541
+ "data-testid": "focus-save-settings",
542
+ children: F ? "Saving..." : "Save focus settings"
543
+ })
544
+ })
545
+ ]
546
+ })
547
+ ]
548
+ });
549
+ }
550
+ //#endregion
551
+ export { C as FocusProtectionPage };
552
+
553
+ //# sourceMappingURL=FocusProtectionPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FocusProtectionPage.js","names":[],"sources":["../../../../src/timecampus/pages/focus/FocusProtectionPage.tsx"],"sourcesContent":["/**\n * Focus Protection (BOFF-4196)\n *\n * One place to protect focus time:\n * - run a focus session now (standard or strict) and see what it caught;\n * - review adaptive focus-block suggestions for today or tomorrow, ranked\n * from your own peak hours, meetings and past sessions, and accept,\n * snooze or dismiss them;\n * - start or cancel the blocks you accepted;\n * - choose the sites the TimeCampus browser extension blocks during a\n * session and the workday the planner works inside.\n */\nimport { useEffect, useMemo, useState } from \"react\";\nimport {\n AlarmClock,\n Ban,\n CalendarClock,\n Check,\n Play,\n Shield,\n ShieldCheck,\n Square,\n Target,\n X,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { ToastContainer, useToast } from \"../../components/Toast\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n TimeCampusFocusBlockResponse,\n TimeCampusFocusProtectionLevel,\n TimeCampusFocusSessionStatus,\n} from \"@/generated/wspace-types\";\nimport {\n useCancelFocusBlockMutation,\n useEndFocusSessionMutation,\n useGetActiveFocusSessionQuery,\n useGetFocusBlockSuggestionsQuery,\n useGetFocusPreferencesQuery,\n useGetFocusProtectionSummaryQuery,\n useListFocusBlocksQuery,\n useLogFocusInterruptionMutation,\n useRespondToFocusBlockSuggestionMutation,\n useStartFocusSessionMutation,\n useUpdateFocusPreferencesMutation,\n type FocusBlockFieldsFragment,\n type FocusPreferencesFieldsFragment,\n type FocusSessionFieldsFragment,\n type GetFocusBlockSuggestionsQuery,\n type GetFocusProtectionSummaryQuery,\n} from \"@/generated/wspace-operations\";\n\ntype FocusSuggestion =\n GetFocusBlockSuggestionsQuery[\"timeCampusFocusBlockSuggestions\"][number];\ntype FocusWeekSummary =\n GetFocusProtectionSummaryQuery[\"timeCampusFocusSummary\"];\nimport {\n DAY_MS,\n canStartBlock,\n formatDuration,\n formatRange,\n localDayStart,\n minutesLeft,\n minutesToTime,\n parseSiteList,\n reasonLabel,\n timeToMinutes,\n} from \"./focusHelpers\";\n\nconst PAGE_TITLE = \"Focus Protection\";\nconst PAGE_DESCRIPTION =\n \"Adaptive focus blocks and distraction blocking during focus sessions\";\nconst REFRESH_QUERIES = [\n \"GetActiveFocusSession\",\n \"GetFocusBlockSuggestions\",\n \"ListFocusBlocks\",\n \"GetFocusProtectionSummary\",\n];\n\nconst inputClass =\n \"rounded-md border border-border-subtle bg-bg-surface px-3 py-1.5 text-sm text-text-primary\";\nconst primaryButton =\n \"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\";\nconst outlineButton =\n \"inline-flex items-center gap-1.5 rounded-md border border-border-subtle bg-bg-surface px-3 py-1.5 text-sm font-medium text-text-primary hover:bg-bg-surface-hover disabled:opacity-50\";\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : \"Something went wrong\";\n}\n\nexport function FocusProtectionPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n const [dayOffset, setDayOffset] = useState(0);\n const [now, setNow] = useState(() => Date.now());\n\n // Re-render once a minute so \"minutes left\" and startable blocks stay current.\n useEffect(() => {\n const timer = window.setInterval(() => setNow(Date.now()), 60_000);\n return () => window.clearInterval(timer);\n }, []);\n\n const dayStart = useMemo(\n () => localDayStart(new Date(), dayOffset),\n [dayOffset],\n );\n const dayEnd = useMemo(\n () => new Date(dayStart.getTime() + DAY_MS),\n [dayStart],\n );\n const weekRange = useMemo(() => {\n const to = new Date();\n return {\n from: new Date(to.getTime() - 7 * DAY_MS).toISOString(),\n to: to.toISOString(),\n };\n }, []);\n\n const active = useGetActiveFocusSessionQuery({ skip: !workspaceId });\n const suggestions = useGetFocusBlockSuggestionsQuery({\n variables: { dayStart: dayStart.toISOString() },\n skip: !workspaceId,\n });\n const blocks = useListFocusBlocksQuery({\n variables: { from: dayStart.toISOString(), to: dayEnd.toISOString() },\n skip: !workspaceId,\n });\n const prefs = useGetFocusPreferencesQuery({ skip: !workspaceId });\n const summary = useGetFocusProtectionSummaryQuery({\n variables: weekRange,\n skip: !workspaceId,\n });\n\n const mutationOptions = { refetchQueries: REFRESH_QUERIES };\n const [startSession, { loading: starting }] =\n useStartFocusSessionMutation(mutationOptions);\n const [endSession, { loading: ending }] =\n useEndFocusSessionMutation(mutationOptions);\n const [logInterruption] = useLogFocusInterruptionMutation(mutationOptions);\n const [respond, { loading: responding }] =\n useRespondToFocusBlockSuggestionMutation(mutationOptions);\n const [cancelBlock] = useCancelFocusBlockMutation(mutationOptions);\n const [savePrefs, { loading: savingPrefs }] =\n useUpdateFocusPreferencesMutation({\n refetchQueries: [\"GetFocusPreferences\", \"GetFocusBlockSuggestions\"],\n });\n\n // ---- Start-session form ----\n const [sessionMinutes, setSessionMinutes] = useState(50);\n const [strict, setStrict] = useState(false);\n\n // ---- Preferences form ----\n const loadedPrefs: FocusPreferencesFieldsFragment | undefined =\n prefs.data?.timeCampusFocusPreferences;\n const [workdayStart, setWorkdayStart] = useState(\"09:00\");\n const [workdayEnd, setWorkdayEnd] = useState(\"18:00\");\n const [blockMinutes, setBlockMinutes] = useState(90);\n const [maxBlocks, setMaxBlocks] = useState(2);\n const [buffer, setBuffer] = useState(10);\n const [blockingEnabled, setBlockingEnabled] = useState(true);\n const [sitesText, setSitesText] = useState(\"\");\n\n useEffect(() => {\n if (!loadedPrefs) return;\n setWorkdayStart(minutesToTime(loadedPrefs.workdayStartMinute));\n setWorkdayEnd(minutesToTime(loadedPrefs.workdayEndMinute));\n setBlockMinutes(loadedPrefs.preferredBlockMinutes);\n setMaxBlocks(loadedPrefs.maxBlocksPerDay);\n setBuffer(loadedPrefs.meetingBufferMinutes);\n setBlockingEnabled(loadedPrefs.blockingEnabled);\n setSitesText(loadedPrefs.blockedDomains.join(\"\\n\"));\n }, [loadedPrefs]);\n\n const session: FocusSessionFieldsFragment | null =\n active.data?.timeCampusActiveFocusSession ?? null;\n const suggestionList: FocusSuggestion[] =\n suggestions.data?.timeCampusFocusBlockSuggestions ?? [];\n const scheduled: FocusBlockFieldsFragment[] =\n blocks.data?.timeCampusFocusBlocks ?? [];\n const week: FocusWeekSummary | undefined =\n summary.data?.timeCampusFocusSummary;\n\n // Gate the skeleton on data, not `loading`: refetches after a mutation must\n // not unmount the page (and the form state in it).\n const firstLoad =\n (!active.data && active.loading) || (!prefs.data && prefs.loading);\n const loadError = active.error ?? prefs.error;\n\n async function run(action: () => Promise<unknown>, success: string) {\n try {\n await action();\n addToast({ title: success, variant: \"success\" });\n } catch (err) {\n addToast({ title: errorMessage(err), variant: \"error\" });\n }\n }\n\n const handleStart = (focusBlockId?: string) =>\n run(\n () =>\n startSession({\n variables: {\n plannedDurationSeconds: focusBlockId\n ? undefined\n : sessionMinutes * 60,\n protectionLevel: strict\n ? TimeCampusFocusProtectionLevel.Strict\n : TimeCampusFocusProtectionLevel.Standard,\n focusBlockId,\n },\n }),\n \"Focus session started\",\n );\n\n const handleEnd = (status: TimeCampusFocusSessionStatus) =>\n session\n ? run(\n () => endSession({ variables: { id: session.id, status } }),\n status === TimeCampusFocusSessionStatus.Completed\n ? \"Focus session completed\"\n : \"Focus session abandoned\",\n )\n : undefined;\n\n const handleRespond = (\n s: { startTime: string; endTime: string },\n response: TimeCampusFocusBlockResponse,\n ) =>\n run(\n () =>\n respond({\n variables: {\n input: {\n startTime: s.startTime,\n endTime: s.endTime,\n response,\n dayStart: dayStart.toISOString(),\n snoozeMinutes:\n response === TimeCampusFocusBlockResponse.Snooze ? 30 : null,\n },\n },\n }),\n response === TimeCampusFocusBlockResponse.Accept\n ? \"Focus block scheduled\"\n : response === TimeCampusFocusBlockResponse.Snooze\n ? \"Snoozed for 30 minutes\"\n : \"Suggestion dismissed\",\n );\n\n const handleSavePrefs = () => {\n const startMinute = timeToMinutes(workdayStart);\n const endMinute = timeToMinutes(workdayEnd);\n if (startMinute === null || endMinute === null) {\n addToast({ title: \"Use HH:MM for the workday\", variant: \"error\" });\n return;\n }\n void run(\n () =>\n savePrefs({\n variables: {\n input: {\n workdayStartMinute: startMinute,\n workdayEndMinute: endMinute,\n preferredBlockMinutes: blockMinutes,\n minBlockMinutes: Math.min(45, blockMinutes),\n maxBlocksPerDay: maxBlocks,\n meetingBufferMinutes: buffer,\n blockingEnabled,\n blockedDomains: parseSiteList(sitesText),\n },\n },\n }),\n \"Focus settings saved\",\n );\n };\n\n if (firstLoad) {\n return (\n <PageLayout title={PAGE_TITLE} description={PAGE_DESCRIPTION}>\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (loadError && !active.data) {\n return (\n <PageLayout title={PAGE_TITLE} description={PAGE_DESCRIPTION}>\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 Failed to load focus protection. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const left = session\n ? minutesLeft(session.startTime, session.plannedDurationSeconds, now)\n : null;\n const ratio = week ? Math.round(week.averageProtectionRatio * 100) : null;\n\n return (\n <PageLayout\n title={PAGE_TITLE}\n description={PAGE_DESCRIPTION}\n purpose=\"Protect the hours that matter: TimeCampus suggests focus blocks around your meetings and the times you focus best, and blocks distracting sites in your browser while a session runs.\"\n >\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n\n {/* Last 7 days */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Protected Time\"\n value={week ? formatDuration(week.totalProtectedSeconds) : \"--\"}\n icon={<ShieldCheck className=\"h-5 w-5\" />}\n description=\"Last 7 days\"\n />\n <StatCard\n title=\"Protection Ratio\"\n value={ratio === null ? \"--\" : `${ratio}%`}\n icon={<Shield className=\"h-5 w-5\" />}\n description=\"Protected vs interrupted\"\n />\n <StatCard\n title=\"Sessions\"\n value={week ? String(week.totalSessions) : \"--\"}\n icon={<Target className=\"h-5 w-5\" />}\n description={\n week ? `${week.completedSessions} completed` : \"Last 7 days\"\n }\n />\n <StatCard\n title=\"Interruptions\"\n value={week ? String(week.totalInterruptionCount) : \"--\"}\n icon={<Ban className=\"h-5 w-5\" />}\n description=\"Logged, detected or bypassed\"\n />\n </div>\n\n {/* Current session */}\n <div className=\"mt-6\" data-testid=\"focus-session-card\">\n <GlassCard treatment=\"glass\" glow className=\"p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div>\n <h3 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Shield className=\"h-4 w-4\" />\n Focus session\n </h3>\n {session ? (\n <p\n className=\"mt-1 text-sm text-text-secondary\"\n data-testid=\"focus-session-status\"\n >\n {left === null\n ? \"Open-ended session in progress.\"\n : left === 0\n ? \"Planned time is up — finish when you're ready.\"\n : `${left} minute${left === 1 ? \"\" : \"s\"} left.`}{\" \"}\n {session.interruptionCount} interruption\n {session.interruptionCount === 1 ? \"\" : \"s\"},{\" \"}\n {session.blockedAttemptCount} site\n {session.blockedAttemptCount === 1 ? \"\" : \"s\"} blocked.\n </p>\n ) : (\n <p className=\"mt-1 text-sm text-text-secondary\">\n No session running. While one is active, the TimeCampus\n browser extension blocks the sites listed below.\n </p>\n )}\n </div>\n {session && (\n <StatusBadge\n variant={\n session.protectionLevel ===\n TimeCampusFocusProtectionLevel.Strict\n ? \"warning\"\n : \"success\"\n }\n label={\n session.protectionLevel ===\n TimeCampusFocusProtectionLevel.Strict\n ? \"Strict\"\n : \"Standard\"\n }\n />\n )}\n </div>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-3\">\n {session ? (\n <>\n <button\n type=\"button\"\n className={primaryButton}\n disabled={ending}\n onClick={() =>\n handleEnd(TimeCampusFocusSessionStatus.Completed)\n }\n data-testid=\"focus-end\"\n >\n <Square className=\"h-4 w-4\" /> End session\n </button>\n <button\n type=\"button\"\n className={outlineButton}\n disabled={ending}\n onClick={() =>\n handleEnd(TimeCampusFocusSessionStatus.Abandoned)\n }\n >\n Abandon\n </button>\n <button\n type=\"button\"\n className={outlineButton}\n onClick={() =>\n run(\n () =>\n logInterruption({\n variables: { focusSessionId: session.id },\n }),\n \"Interruption logged\",\n )\n }\n data-testid=\"focus-log-interruption\"\n >\n Log an interruption\n </button>\n </>\n ) : (\n <>\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n Length\n <select\n className={inputClass}\n value={sessionMinutes}\n onChange={(e) => setSessionMinutes(Number(e.target.value))}\n data-testid=\"focus-length\"\n >\n <option value={25}>25 min</option>\n <option value={50}>50 min</option>\n <option value={90}>90 min</option>\n <option value={120}>2 hours</option>\n </select>\n </label>\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={strict}\n onChange={(e) => setStrict(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n Strict (no &ldquo;continue anyway&rdquo;)\n </label>\n <button\n type=\"button\"\n className={primaryButton}\n disabled={starting}\n onClick={() => handleStart()}\n data-testid=\"focus-start\"\n >\n <Play className=\"h-4 w-4\" /> Start focus session\n </button>\n </>\n )}\n </div>\n </GlassCard>\n </div>\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {/* Suggestions */}\n <div\n className=\"rounded-xl border border-border-seam bg-bg-surface p-6 shadow-elevation-1\"\n data-testid=\"focus-suggestions\"\n >\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <CalendarClock className=\"h-4 w-4\" />\n Suggested focus blocks\n </h3>\n <div className=\"flex gap-1\" role=\"group\" aria-label=\"Day to plan\">\n {[\"Today\", \"Tomorrow\"].map((label, index) => (\n <button\n key={label}\n type=\"button\"\n aria-pressed={dayOffset === index}\n className={\n dayOffset === index ? primaryButton : outlineButton\n }\n onClick={() => setDayOffset(index)}\n >\n {label}\n </button>\n ))}\n </div>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Ranked from your peak hours, meetings and how well past sessions\n held up. Snoozed and dismissed slots are planned around.\n </p>\n\n <div className=\"mt-4 space-y-3\">\n {suggestions.error && (\n <p className=\"text-sm text-status-error-text\">\n {errorMessage(suggestions.error)}\n </p>\n )}\n {!suggestions.error &&\n suggestions.data &&\n suggestionList.length === 0 && (\n <p\n className=\"text-sm text-text-secondary\"\n data-testid=\"focus-suggestions-empty\"\n >\n No free window left for{\" \"}\n {dayOffset === 0 ? \"today\" : \"tomorrow\"} — your day is full or\n you have scheduled your maximum blocks.\n </p>\n )}\n {suggestionList.map((s) => (\n <div\n key={s.startTime}\n className=\"rounded-lg border border-border-subtle p-3\"\n data-testid=\"focus-suggestion\"\n >\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">\n {formatRange(s.startTime, s.endTime)}\n <span className=\"ml-2 text-xs text-text-secondary\">\n {formatDuration(s.durationMinutes * 60)}\n </span>\n </span>\n <span className=\"text-xs text-text-secondary\">\n Fit {Math.round(s.score * 100)}%\n </span>\n </div>\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {s.reasons.map((r: string) => (\n <StatusBadge\n key={r}\n variant=\"info\"\n label={reasonLabel(r)}\n />\n ))}\n </div>\n <div className=\"mt-3 flex flex-wrap gap-2\">\n <button\n type=\"button\"\n className={primaryButton}\n disabled={responding}\n onClick={() =>\n handleRespond(s, TimeCampusFocusBlockResponse.Accept)\n }\n data-testid=\"focus-suggestion-accept\"\n >\n <Check className=\"h-4 w-4\" /> Accept\n </button>\n <button\n type=\"button\"\n className={outlineButton}\n disabled={responding}\n onClick={() =>\n handleRespond(s, TimeCampusFocusBlockResponse.Snooze)\n }\n data-testid=\"focus-suggestion-snooze\"\n >\n <AlarmClock className=\"h-4 w-4\" /> Snooze 30 min\n </button>\n <button\n type=\"button\"\n className={outlineButton}\n disabled={responding}\n onClick={() =>\n handleRespond(s, TimeCampusFocusBlockResponse.Dismiss)\n }\n data-testid=\"focus-suggestion-dismiss\"\n >\n <X className=\"h-4 w-4\" /> Dismiss\n </button>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Scheduled blocks */}\n <div\n className=\"rounded-xl border border-border-seam bg-bg-surface p-6 shadow-elevation-1\"\n data-testid=\"focus-scheduled\"\n >\n <h3 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Target className=\"h-4 w-4\" />\n Scheduled ({dayOffset === 0 ? \"today\" : \"tomorrow\"})\n </h3>\n <div className=\"mt-4 space-y-3\">\n {blocks.data && scheduled.length === 0 && (\n <p className=\"text-sm text-text-secondary\">\n Accept a suggestion to put a focus block on your schedule.\n </p>\n )}\n {scheduled.map((b) => (\n <div\n key={b.id}\n className=\"flex flex-wrap items-center justify-between gap-2 rounded-lg border border-border-subtle p-3\"\n data-testid=\"focus-scheduled-block\"\n >\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {formatRange(b.startTime, b.endTime)}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {b.source === \"ADAPTIVE\" ? \"Suggested\" : \"Manual\"} ·{\" \"}\n {formatDuration(b.durationMinutes * 60)}\n </p>\n </div>\n {b.status === \"STARTED\" ? (\n <StatusBadge variant=\"success\" label=\"Started\" />\n ) : (\n <div className=\"flex gap-2\">\n {!session && canStartBlock(b.endTime, now) && (\n <button\n type=\"button\"\n className={primaryButton}\n disabled={starting}\n onClick={() => handleStart(b.id)}\n >\n <Play className=\"h-4 w-4\" /> Start\n </button>\n )}\n <button\n type=\"button\"\n className={outlineButton}\n onClick={() =>\n run(\n () => cancelBlock({ variables: { id: b.id } }),\n \"Focus block canceled\",\n )\n }\n data-testid=\"focus-block-cancel\"\n >\n Cancel\n </button>\n </div>\n )}\n </div>\n ))}\n </div>\n </div>\n </div>\n\n {/* Settings */}\n <div\n className=\"mt-6 rounded-xl border border-border-seam bg-bg-surface p-6 shadow-elevation-1\"\n data-testid=\"focus-settings\"\n >\n <h3 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Ban className=\"h-4 w-4\" />\n Distraction blocking and planning\n </h3>\n <div className=\"mt-4 grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <div className=\"space-y-3\">\n <label className=\"flex items-center justify-between gap-4 text-sm text-text-primary\">\n Block sites during focus sessions\n <input\n type=\"checkbox\"\n checked={blockingEnabled}\n onChange={(e) => setBlockingEnabled(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n data-testid=\"focus-blocking-enabled\"\n />\n </label>\n <label className=\"block text-sm text-text-primary\">\n Blocked sites\n <textarea\n rows={6}\n value={sitesText}\n onChange={(e) => setSitesText(e.target.value)}\n className={`${inputClass} mt-1 w-full font-mono`}\n aria-describedby=\"focus-sites-help\"\n data-testid=\"focus-blocked-sites\"\n />\n </label>\n <p id=\"focus-sites-help\" className=\"text-xs text-text-secondary\">\n One site per line (subdomains are included). Blocking is enforced\n by the TimeCampus browser extension while a session is active;\n allow-listed apps on a session are never blocked.\n </p>\n </div>\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-4\">\n <span className=\"text-sm text-text-primary\">Workday</span>\n <span className=\"flex items-center gap-2\">\n <input\n type=\"time\"\n value={workdayStart}\n onChange={(e) => setWorkdayStart(e.target.value)}\n className={inputClass}\n aria-label=\"Workday start\"\n />\n <span className=\"text-text-secondary\">to</span>\n <input\n type=\"time\"\n value={workdayEnd === \"24:00\" ? \"23:59\" : workdayEnd}\n onChange={(e) => setWorkdayEnd(e.target.value)}\n className={inputClass}\n aria-label=\"Workday end\"\n />\n </span>\n </div>\n <label className=\"flex items-center justify-between gap-4 text-sm text-text-primary\">\n Preferred block length (minutes)\n <input\n type=\"number\"\n min={15}\n max={240}\n step={15}\n value={blockMinutes}\n onChange={(e) => setBlockMinutes(Number(e.target.value))}\n className={`${inputClass} w-24 text-right`}\n />\n </label>\n <label className=\"flex items-center justify-between gap-4 text-sm text-text-primary\">\n Focus blocks per day\n <input\n type=\"number\"\n min={1}\n max={6}\n value={maxBlocks}\n onChange={(e) => setMaxBlocks(Number(e.target.value))}\n className={`${inputClass} w-24 text-right`}\n />\n </label>\n <label className=\"flex items-center justify-between gap-4 text-sm text-text-primary\">\n Buffer around meetings (minutes)\n <input\n type=\"number\"\n min={0}\n max={60}\n step={5}\n value={buffer}\n onChange={(e) => setBuffer(Number(e.target.value))}\n className={`${inputClass} w-24 text-right`}\n />\n </label>\n </div>\n </div>\n <div className=\"mt-4 flex justify-end\">\n <button\n type=\"button\"\n className={primaryButton}\n disabled={savingPrefs}\n onClick={handleSavePrefs}\n data-testid=\"focus-save-settings\"\n >\n {savingPrefs ? \"Saving...\" : \"Save focus settings\"}\n </button>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}