@flamingo-stack/openframe-frontend-core 0.0.838 → 0.0.839

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.
Files changed (64) hide show
  1. package/dist/build/export-map.json +1 -1
  2. package/dist/{chunk-MMPMQUQM.cjs → chunk-2XZZHRRU.cjs} +4 -4
  3. package/dist/{chunk-JFR3OFE6.cjs → chunk-37XKZ4JM.cjs} +2 -2
  4. package/dist/{chunk-U4MJ2Z4V.cjs → chunk-3MBKEF7Z.cjs} +10 -10
  5. package/dist/{chunk-V322TUBY.cjs → chunk-C4RYIDPU.cjs} +3 -3
  6. package/dist/{chunk-TMD55O5U.cjs → chunk-DFTXZE7B.cjs} +24 -24
  7. package/dist/{chunk-JI34QKPJ.cjs → chunk-GN6LEJWY.cjs} +20 -20
  8. package/dist/{chunk-B4THEQOW.cjs → chunk-N2TCVIPR.cjs} +208 -25
  9. package/dist/{chunk-NV5AHL4G.cjs → chunk-SZVPRP5E.cjs} +3 -3
  10. package/dist/{chunk-7QNGILIY.cjs → chunk-TKS25VKE.cjs} +7 -7
  11. package/dist/{chunk-4TUHHKLR.cjs → chunk-UDRVVCOC.cjs} +31 -31
  12. package/dist/{chunk-2X5LYDNA.cjs → chunk-XJ4AB24Y.cjs} +3 -3
  13. package/dist/{cloud-tenants-GLPDB2F5.cjs → cloud-tenants-WE3H3VAW.cjs} +2 -2
  14. package/dist/components/case-studies/index.cjs +11 -11
  15. package/dist/components/contact/index.cjs +3 -3
  16. package/dist/components/docs/index.cjs +4 -4
  17. package/dist/components/embeds/index.cjs +3 -3
  18. package/dist/components/faq/index.cjs +3 -3
  19. package/dist/components/features/command-box.d.ts.map +1 -1
  20. package/dist/components/features/command-box.js +4 -23
  21. package/dist/components/features/index.cjs +12 -2
  22. package/dist/components/features/software/index.d.ts +1 -0
  23. package/dist/components/features/software/index.d.ts.map +1 -1
  24. package/dist/components/features/software/index.js +12 -0
  25. package/dist/components/features/software/software-action-form.d.ts +9 -1
  26. package/dist/components/features/software/software-action-form.d.ts.map +1 -1
  27. package/dist/components/features/software/software-action-form.js +18 -1
  28. package/dist/components/features/software/update-settings-fields.d.ts +43 -0
  29. package/dist/components/features/software/update-settings-fields.d.ts.map +1 -0
  30. package/dist/components/features/software/update-settings-fields.js +202 -0
  31. package/dist/components/help-center-pages/download-apps-page.d.ts +1 -1
  32. package/dist/components/help-center-pages/download-apps-page.d.ts.map +1 -1
  33. package/dist/components/help-center-pages/download-apps-page.js +2 -3
  34. package/dist/components/help-center-pages/index.cjs +48 -49
  35. package/dist/components/index.cjs +27 -17
  36. package/dist/components/meeting-scheduler/index.cjs +20 -20
  37. package/dist/components/navigation/index.cjs +3 -3
  38. package/dist/components/onboarding-guides/index.cjs +3 -3
  39. package/dist/components/product-demo/fixtures/software-update.d.ts +2 -0
  40. package/dist/components/product-demo/fixtures/software-update.d.ts.map +1 -1
  41. package/dist/components/product-demo/fixtures/software-update.js +3 -2
  42. package/dist/components/product-demo/index.cjs +11 -11
  43. package/dist/components/tickets/index.cjs +5 -5
  44. package/dist/components/ui/index.cjs +2 -2
  45. package/dist/{devices-GHPW5WCO.cjs → devices-ZBZN2RRG.cjs} +3 -3
  46. package/dist/index.cjs +13 -3
  47. package/dist/{knowledge-4TOSY2S5.cjs → knowledge-2QNZGDFO.cjs} +2 -2
  48. package/dist/{logs-MUODFY6N.cjs → logs-YPWTJVIH.cjs} +9 -9
  49. package/dist/{policies-TJHO3VLE.cjs → policies-BK7TUKAF.cjs} +6 -6
  50. package/dist/{scripts-X5CMYE2V.cjs → scripts-K7AOYWZE.cjs} +7 -7
  51. package/dist/{software-update-2UZKHTYM.cjs → software-update-UFN4SBMY.cjs} +7 -7
  52. package/dist/utils/notifications.cjs +2 -0
  53. package/dist/utils/notifications.d.ts +1 -1
  54. package/dist/utils/notifications.d.ts.map +1 -1
  55. package/dist/utils/notifications.js +2 -0
  56. package/package.json +1 -1
  57. package/src/components/features/command-box.tsx +17 -19
  58. package/src/components/features/notifications/notification-category-icon.tsx +1 -1
  59. package/src/components/features/software/index.ts +11 -0
  60. package/src/components/features/software/software-action-form.tsx +28 -1
  61. package/src/components/features/software/update-settings-fields.tsx +265 -0
  62. package/src/components/help-center-pages/download-apps-page.tsx +4 -5
  63. package/src/components/product-demo/fixtures/software-update.ts +2 -2
  64. package/src/utils/notifications.ts +2 -0
@@ -0,0 +1,265 @@
1
+ 'use client';
2
+
3
+ import { InfoCircleIcon } from '../../icons-v2-generated';
4
+ import {
5
+ Alert,
6
+ CheckboxBlock,
7
+ Label,
8
+ RadioGroupBlock,
9
+ type RadioGroupBlockOption,
10
+ Select,
11
+ SelectContent,
12
+ SelectItem,
13
+ SelectTrigger,
14
+ SelectValue,
15
+ } from '../../ui';
16
+
17
+ /** What an update does on a device where the app is running. */
18
+ export type SoftwareUpdateMode = 'WAIT' | 'FORCE';
19
+
20
+ /** "If the app is running": one update's behaviour on a device where the app is open. */
21
+ export interface SoftwareUpdatePolicy {
22
+ mode: SoftwareUpdateMode;
23
+ /** WAIT only: "Countdown after the deadline". Off, the update waits for the user however long it takes. */
24
+ deadlineEnabled: boolean;
25
+ /** WAIT only: seconds after the first try before the countdown starts. */
26
+ deadlineSeconds: number;
27
+ /** Seconds of countdown before the app is closed: after the deadline (WAIT) or right away (FORCE). */
28
+ countdownSeconds: number;
29
+ }
30
+
31
+ /** The Update Software form's "Update Settings": this update, and whether later releases follow it. */
32
+ export interface SoftwareUpdateSettingsValues {
33
+ policy: SoftwareUpdatePolicy;
34
+ /** "Keep it auto-updated": every new version is installed on the same devices after this update. */
35
+ autoUpdate: boolean;
36
+ /** "Same settings for auto-update": auto-updates run with `policy`; off, with `autoUpdatePolicy`. */
37
+ sameForAutoUpdate: boolean;
38
+ autoUpdatePolicy: SoftwareUpdatePolicy;
39
+ }
40
+
41
+ const DAY = 24 * 60 * 60;
42
+ const MINUTE = 60;
43
+
44
+ export const SOFTWARE_UPDATE_DEADLINE_OPTIONS: ReadonlyArray<{ seconds: number; label: string }> = [
45
+ { seconds: DAY, label: '1 day after the first try' },
46
+ { seconds: 3 * DAY, label: '3 days after the first try' },
47
+ { seconds: 7 * DAY, label: '7 days after the first try' },
48
+ { seconds: 14 * DAY, label: '14 days after the first try' },
49
+ { seconds: 30 * DAY, label: '30 days after the first try' },
50
+ ];
51
+
52
+ export const SOFTWARE_UPDATE_COUNTDOWN_OPTIONS: ReadonlyArray<{ seconds: number; label: string }> = [
53
+ { seconds: MINUTE, label: '1 minute' },
54
+ { seconds: 5 * MINUTE, label: '5 minutes' },
55
+ { seconds: 15 * MINUTE, label: '15 minutes' },
56
+ { seconds: 30 * MINUTE, label: '30 minutes' },
57
+ ];
58
+
59
+ /** Wait & notify, a countdown 7 days after the first try, 5 minutes long: what the server applies when sent none. */
60
+ export const DEFAULT_SOFTWARE_UPDATE_POLICY: SoftwareUpdatePolicy = {
61
+ mode: 'WAIT',
62
+ deadlineEnabled: true,
63
+ deadlineSeconds: 7 * DAY,
64
+ countdownSeconds: 5 * MINUTE,
65
+ };
66
+
67
+ export const DEFAULT_SOFTWARE_UPDATE_SETTINGS: SoftwareUpdateSettingsValues = {
68
+ policy: DEFAULT_SOFTWARE_UPDATE_POLICY,
69
+ autoUpdate: true,
70
+ sameForAutoUpdate: true,
71
+ autoUpdatePolicy: DEFAULT_SOFTWARE_UPDATE_POLICY,
72
+ };
73
+
74
+ const MODE_OPTIONS: RadioGroupBlockOption[] = [
75
+ {
76
+ value: 'WAIT',
77
+ label: 'Wait & notify',
78
+ description: 'The user gets Update now or Ask Later. If they close the app, it updates then.',
79
+ },
80
+ {
81
+ value: 'FORCE',
82
+ label: 'Force Update',
83
+ description: 'The user gets a short countdown, then the app closes and updates.',
84
+ },
85
+ ];
86
+
87
+ const SAME_SETTINGS_DESCRIPTION: Record<SoftwareUpdateMode, string> = {
88
+ WAIT: 'Auto-updates follow "If the app is running" above. Uncheck to set them separately.',
89
+ FORCE: 'Later releases will also be force-updated.',
90
+ };
91
+
92
+ const groupLabelClass = 'text-h5 text-ods-text-secondary';
93
+
94
+ function SecondsSelect({
95
+ label,
96
+ value,
97
+ options,
98
+ onChange,
99
+ }: {
100
+ label: string;
101
+ value: number;
102
+ options: ReadonlyArray<{ seconds: number; label: string }>;
103
+ onChange: (seconds: number) => void;
104
+ }) {
105
+ return (
106
+ <div className="flex min-w-0 flex-col gap-[var(--spacing-system-xxs)]">
107
+ <Label className="text-h4">{label}</Label>
108
+ <Select value={String(value)} onValueChange={next => onChange(Number(next))}>
109
+ <SelectTrigger aria-label={label}>
110
+ <SelectValue />
111
+ </SelectTrigger>
112
+ <SelectContent>
113
+ {options.map(option => (
114
+ <SelectItem key={option.seconds} value={String(option.seconds)}>
115
+ {option.label}
116
+ </SelectItem>
117
+ ))}
118
+ </SelectContent>
119
+ </Select>
120
+ </div>
121
+ );
122
+ }
123
+
124
+ /** "If the app is running" with its deadline and countdown. */
125
+ function UpdatePolicyFields({
126
+ name,
127
+ label,
128
+ policy,
129
+ onChange,
130
+ }: {
131
+ /** The radio group's name: two policies on one page must not share it. */
132
+ name: string;
133
+ label: string;
134
+ policy: SoftwareUpdatePolicy;
135
+ onChange: (policy: SoftwareUpdatePolicy) => void;
136
+ }) {
137
+ const waits = policy.mode === 'WAIT';
138
+ const deadline = waits && policy.deadlineEnabled;
139
+ const countdown = (
140
+ <SecondsSelect
141
+ label="Countdown"
142
+ value={policy.countdownSeconds}
143
+ options={SOFTWARE_UPDATE_COUNTDOWN_OPTIONS}
144
+ onChange={countdownSeconds => onChange({ ...policy, countdownSeconds })}
145
+ />
146
+ );
147
+ return (
148
+ <div className="flex flex-col gap-[var(--spacing-system-m)]">
149
+ <div className="flex flex-col gap-[var(--spacing-system-xxs)]">
150
+ <span className={groupLabelClass}>{label}</span>
151
+ <RadioGroupBlock
152
+ name={name}
153
+ aria-label={label}
154
+ variant="grouped"
155
+ value={policy.mode}
156
+ onValueChange={mode => onChange({ ...policy, mode: mode as SoftwareUpdateMode })}
157
+ options={MODE_OPTIONS}
158
+ itemClassName="py-[var(--spacing-system-sf)]"
159
+ />
160
+ </div>
161
+ {waits ? (
162
+ <div className="flex flex-col gap-[var(--spacing-system-xsf)]">
163
+ <CheckboxBlock
164
+ id={`${name}-deadline`}
165
+ checked={policy.deadlineEnabled}
166
+ onCheckedChange={deadlineEnabled => onChange({ ...policy, deadlineEnabled })}
167
+ label="Countdown after the deadline"
168
+ description="If the update still isn’t installed by the deadline, the user gets a countdown and then it installs."
169
+ />
170
+ {deadline && (
171
+ <div className="grid grid-cols-1 gap-[var(--spacing-system-lf)] content-md:grid-cols-3 content-md:items-start">
172
+ <SecondsSelect
173
+ label="Deadline"
174
+ value={policy.deadlineSeconds}
175
+ options={SOFTWARE_UPDATE_DEADLINE_OPTIONS}
176
+ onChange={deadlineSeconds => onChange({ ...policy, deadlineSeconds })}
177
+ />
178
+ {countdown}
179
+ </div>
180
+ )}
181
+ </div>
182
+ ) : (
183
+ <div className="grid grid-cols-1 gap-[var(--spacing-system-lf)] content-md:grid-cols-3">{countdown}</div>
184
+ )}
185
+ </div>
186
+ );
187
+ }
188
+
189
+ export interface SoftwareUpdateSettingsFieldsProps {
190
+ value: SoftwareUpdateSettingsValues;
191
+ onChange: (value: SoftwareUpdateSettingsValues) => void;
192
+ }
193
+
194
+ /**
195
+ * Update Software's "Update Settings" (design 988:21462, the separate auto-update
196
+ * rules 1034:8173): what happens on a device where the app is running, and
197
+ * whether later releases are installed the same way.
198
+ */
199
+ export function SoftwareUpdateSettingsFields({ value, onChange }: SoftwareUpdateSettingsFieldsProps) {
200
+ return (
201
+ <section aria-labelledby="software-update-settings" className="flex flex-col gap-[var(--spacing-system-l)]">
202
+ <h2 id="software-update-settings" className="pt-[var(--spacing-system-l)] text-ods-text-primary text-h2">
203
+ Update Settings
204
+ </h2>
205
+
206
+ <UpdatePolicyFields
207
+ name="updatePolicy"
208
+ label="If the app is running"
209
+ policy={value.policy}
210
+ onChange={policy => onChange({ ...value, policy })}
211
+ />
212
+
213
+ <div className="flex flex-col gap-[var(--spacing-system-xxs)]">
214
+ <span className={groupLabelClass}>Auto-update</span>
215
+ <div className="flex flex-col gap-[var(--spacing-system-sf)]">
216
+ <CheckboxBlock
217
+ id="software-auto-update"
218
+ checked={value.autoUpdate}
219
+ onCheckedChange={autoUpdate => onChange({ ...value, autoUpdate })}
220
+ label="Keep it auto-updated"
221
+ description="After this update, OpenFrame installs each new version from Winget or Brew on the selected devices. When off, only this update runs."
222
+ />
223
+ {value.autoUpdate && (
224
+ <>
225
+ <CheckboxBlock
226
+ id="software-auto-update-same"
227
+ checked={value.sameForAutoUpdate}
228
+ onCheckedChange={sameForAutoUpdate => onChange({ ...value, sameForAutoUpdate })}
229
+ label="Same settings for auto-update"
230
+ description={SAME_SETTINGS_DESCRIPTION[value.policy.mode]}
231
+ />
232
+ {!value.sameForAutoUpdate && (
233
+ <div className="rounded-[6px] border border-ods-border p-[var(--spacing-system-m)]">
234
+ <UpdatePolicyFields
235
+ name="autoUpdatePolicy"
236
+ label="For auto-updates"
237
+ policy={value.autoUpdatePolicy}
238
+ onChange={autoUpdatePolicy => onChange({ ...value, autoUpdatePolicy })}
239
+ />
240
+ </div>
241
+ )}
242
+ </>
243
+ )}
244
+ </div>
245
+ </div>
246
+
247
+ {/* Core Alert on the card surface, as a note: it is there from the start, nothing to announce. The icon sits in
248
+ a span so Alert's `[&>svg]` positioning does not apply. */}
249
+ <Alert
250
+ role="note"
251
+ className="flex items-center gap-[var(--spacing-system-m)] rounded-[6px] border-ods-border bg-ods-card p-[var(--spacing-system-m)]"
252
+ >
253
+ <span className="shrink-0 text-ods-text-secondary">
254
+ <InfoCircleIcon size={24} color="currentColor" />
255
+ </span>
256
+ <div className="flex min-w-0 flex-1 flex-col gap-[var(--spacing-system-xxs)]">
257
+ <p className="text-ods-text-primary text-h3">Apps installed by hand get reinstalled</p>
258
+ <p className="text-ods-text-secondary text-h6">
259
+ On the first update, OpenFrame reinstalls them with Winget or Brew so it can keep them updated.
260
+ </p>
261
+ </div>
262
+ </Alert>
263
+ </section>
264
+ );
265
+ }
@@ -7,7 +7,7 @@
7
7
  * lists its platforms; it does not hide them behind tabs, so nothing moves
8
8
  * when a visitor looks for another system). In a card the installer is THE
9
9
  * action (the other architecture is a quieter button) and each package
10
- * manager is one line under it, with its copy button.
10
+ * manager is one row under it, with its copy button.
11
11
  * - The mobile app: the store badges, and the install QR code beside them. The
12
12
  * code and the address it encodes are the server's (`mobile.install`): the
13
13
  * website's install link, which the server answers with a redirect to the
@@ -111,7 +111,7 @@ export interface DownloadAppsWebOption {
111
111
 
112
112
  const SECTIONS_CLASS = 'flex flex-col gap-[var(--spacing-system-xlf)]';
113
113
  const SECTION_CLASS = 'flex flex-col gap-[var(--spacing-system-lf)]';
114
- const CARDS_GRID_CLASS = 'grid grid-cols-1 gap-[var(--spacing-system-lf)] content-md:grid-cols-2';
114
+ const CARDS_GRID_CLASS = 'grid grid-cols-1 gap-[var(--spacing-system-lf)] content-lg:grid-cols-2';
115
115
  /** One card takes the row; two or more share it. */
116
116
  const cardsGridClass = (cards: number) => (cards > 1 ? CARDS_GRID_CLASS : 'grid grid-cols-1');
117
117
  const CARD_CLASS =
@@ -183,7 +183,7 @@ function installerLabel(download: AppDownload, siblings: number): string {
183
183
  return siblings > 1 && architecture ? `${label} (${architecture})` : label;
184
184
  }
185
185
 
186
- /** One system's card: its logo and name, its installers side by side, then each package manager as one line. */
186
+ /** One system's card: its logo and name, its installers side by side, then each package manager as one row. */
187
187
  function SystemCard({
188
188
  system,
189
189
  onDownloadAction,
@@ -243,8 +243,7 @@ function SystemCard({
243
243
  key={row.id}
244
244
  title={row.label ?? undefined}
245
245
  command={row.command}
246
- maxLines={1}
247
- commandClassName="text-ods-accent"
246
+ commandClassName="break-words text-ods-accent"
248
247
  copyAriaLabel={`Copy the ${row.label ?? 'install'} command`}
249
248
  onCopy={() => {
250
249
  void copy(row.command as string);
@@ -16,7 +16,7 @@ const chrome: SelectedPackage = {
16
16
  const TONIGHT_11_PM = { value: '23:00', label: '11:00 PM' };
17
17
 
18
18
  /** A fixed reading of the schedule fields: tonight's late slots, nothing in the past. */
19
- const timing: SoftwareScheduleTiming = {
19
+ export const SOFTWARE_DEMO_TIMING: SoftwareScheduleTiming = {
20
20
  timeReferenceOptions: [
21
21
  { value: 'SERVER', label: 'Your account timezone' },
22
22
  { value: 'DEVICE_LOCAL', label: 'Device local timezone' },
@@ -49,7 +49,7 @@ export function buildSoftwareUpdateFixture(cast: DemoCast) {
49
49
  return {
50
50
  action: 'UPDATE',
51
51
  package: chrome,
52
- timing,
52
+ timing: SOFTWARE_DEMO_TIMING,
53
53
  initialValues,
54
54
  devices,
55
55
  selectedIds: new Set<string>([DEMO_DEVICES.reception.id, DEMO_DEVICES.samT14.id, DEMO_DEVICES.frontDesk.id]),
@@ -39,6 +39,7 @@ export const NOTIFICATION_CATEGORIES = [
39
39
  'INSIGHTS',
40
40
  'MINGO',
41
41
  'INTEGRATIONS',
42
+ 'BILLING',
42
43
  'GENERIC',
43
44
  ] as const;
44
45
  export type NotificationCategory = (typeof NOTIFICATION_CATEGORIES)[number];
@@ -56,6 +57,7 @@ export const NOTIFICATION_CATEGORY_LABELS: Readonly<Record<NotificationCategory,
56
57
  INSIGHTS: 'Insights',
57
58
  MINGO: 'Mingo',
58
59
  INTEGRATIONS: 'Integrations',
60
+ BILLING: 'Billing',
59
61
  GENERIC: 'General',
60
62
  };
61
63