@rapidmx/web-client 0.7.0 → 0.8.0

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.
@@ -18,6 +18,23 @@ function toGb(bytes: number): string {
18
18
  return String(bytes / GB);
19
19
  }
20
20
 
21
+ /** Whether a quota field's text is not the given number of bytes. */
22
+ function differsFromBytes(text: string, bytes: number): boolean {
23
+ return Math.round(Number(text) * GB) !== bytes;
24
+ }
25
+
26
+ /** Puts a field back to the server's config value; nothing is saved until the form is. */
27
+ function ResetToDefault({ label, shown, onClick }: { label: string; shown: boolean; onClick: () => void }) {
28
+ if (!shown) {
29
+ return null;
30
+ }
31
+ return (
32
+ <Button type="button" variant="text" className="!w-auto self-start text-xs -mt-2" onClick={onClick}>
33
+ Reset to server default ({label})
34
+ </Button>
35
+ );
36
+ }
37
+
21
38
  /** The form's values as they were loaded or last saved. */
22
39
  interface Baseline {
23
40
  defaultQuotaGb: string;
@@ -35,8 +52,13 @@ export interface MailboxPolicyFormProps {
35
52
  embedded?: boolean;
36
53
  }
37
54
 
38
- /** The mailbox defaults editor, shared by the Mailbox Policy page and the setup wizard. */
55
+ /**
56
+ * The mailbox defaults editor, shared by the Mailbox Policy page and the setup wizard. Each field can be reset to what
57
+ * the server's config currently says (`policy.defaults`), which lets an administrator take a newly deployed value
58
+ * without typing it in; the reset fills the field in and is saved with the rest of the form.
59
+ */
39
60
  export default function MailboxPolicyForm({ policy, onChange, onDirtyChange, embedded = false }: MailboxPolicyFormProps) {
61
+ const defaults = policy.defaults;
40
62
  const [defaultQuotaGb, setDefaultQuotaGb] = useState(toGb(policy.defaultQuotaBytes));
41
63
  const [autoProvisionEnabled, setAutoProvisionEnabled] = useState(policy.autoProvisionEnabled);
42
64
  const [autoProvisionQuotaGb, setAutoProvisionQuotaGb] = useState(toGb(policy.autoProvisionQuotaBytes));
@@ -119,6 +141,11 @@ export default function MailboxPolicyForm({ policy, onChange, onDirtyChange, emb
119
141
  />
120
142
  <span className="text-xs text-text-muted">The storage a mailbox starts with when an administrator creates it.</span>
121
143
  </label>
144
+ <ResetToDefault
145
+ label={defaults ? `${toGb(defaults.defaultQuotaBytes)} GB` : ""}
146
+ shown={!!defaults && differsFromBytes(defaultQuotaGb, defaults.defaultQuotaBytes)}
147
+ onClick={() => setDefaultQuotaGb(toGb(defaults!.defaultQuotaBytes))}
148
+ />
122
149
  <label className="flex items-start gap-2 text-sm">
123
150
  <input
124
151
  type="checkbox"
@@ -134,19 +161,31 @@ export default function MailboxPolicyForm({ policy, onChange, onDirtyChange, emb
134
161
  </span>
135
162
  </span>
136
163
  </label>
164
+ <ResetToDefault
165
+ label={defaults?.autoProvisionEnabled ? "on" : "off"}
166
+ shown={!!defaults && autoProvisionEnabled !== defaults.autoProvisionEnabled}
167
+ onClick={() => setAutoProvisionEnabled(defaults!.autoProvisionEnabled)}
168
+ />
137
169
  {autoProvisionEnabled && (
138
- <label className="flex flex-col gap-1.5 text-sm">
139
- <span className="font-semibold">Quota for self-created mailboxes (GB)</span>
140
- <input
141
- aria-label="Quota for self-created mailboxes (GB)"
142
- type="number"
143
- min={0}
144
- step="any"
145
- className={INPUT_CLASS}
146
- value={autoProvisionQuotaGb}
147
- onChange={(e) => setAutoProvisionQuotaGb(e.target.value)}
170
+ <>
171
+ <label className="flex flex-col gap-1.5 text-sm">
172
+ <span className="font-semibold">Quota for self-created mailboxes (GB)</span>
173
+ <input
174
+ aria-label="Quota for self-created mailboxes (GB)"
175
+ type="number"
176
+ min={0}
177
+ step="any"
178
+ className={INPUT_CLASS}
179
+ value={autoProvisionQuotaGb}
180
+ onChange={(e) => setAutoProvisionQuotaGb(e.target.value)}
181
+ />
182
+ </label>
183
+ <ResetToDefault
184
+ label={defaults ? `${toGb(defaults.autoProvisionQuotaBytes)} GB` : ""}
185
+ shown={!!defaults && differsFromBytes(autoProvisionQuotaGb, defaults.autoProvisionQuotaBytes)}
186
+ onClick={() => setAutoProvisionQuotaGb(toGb(defaults!.autoProvisionQuotaBytes))}
148
187
  />
149
- </label>
188
+ </>
150
189
  )}
151
190
  <div>
152
191
  <Button type="submit" loading={saving} disabled={saving} className="!w-auto">
@@ -9,5 +9,9 @@ export interface MailboxPolicyFormProps {
9
9
  * lower-level heading. */
10
10
  embedded?: boolean;
11
11
  }
12
- /** The mailbox defaults editor, shared by the Mailbox Policy page and the setup wizard. */
12
+ /**
13
+ * The mailbox defaults editor, shared by the Mailbox Policy page and the setup wizard. Each field can be reset to what
14
+ * the server's config currently says (`policy.defaults`), which lets an administrator take a newly deployed value
15
+ * without typing it in; the reset fills the field in and is saved with the rest of the form.
16
+ */
13
17
  export default function MailboxPolicyForm({ policy, onChange, onDirtyChange, embedded }: MailboxPolicyFormProps): React.JSX.Element;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  ///////////////////////////////////////////////////////////////////////////////
3
3
  // Copyright (C) 2026 Jean-Philippe Steinmetz
4
4
  // SPDX-License-Identifier: MPL-2.0
@@ -14,8 +14,24 @@ const GB = 1000000000;
14
14
  function toGb(bytes) {
15
15
  return String(bytes / GB);
16
16
  }
17
- /** The mailbox defaults editor, shared by the Mailbox Policy page and the setup wizard. */
17
+ /** Whether a quota field's text is not the given number of bytes. */
18
+ function differsFromBytes(text, bytes) {
19
+ return Math.round(Number(text) * GB) !== bytes;
20
+ }
21
+ /** Puts a field back to the server's config value; nothing is saved until the form is. */
22
+ function ResetToDefault({ label, shown, onClick }) {
23
+ if (!shown) {
24
+ return null;
25
+ }
26
+ return (_jsxs(Button, { type: "button", variant: "text", className: "!w-auto self-start text-xs -mt-2", onClick: onClick, children: ["Reset to server default (", label, ")"] }));
27
+ }
28
+ /**
29
+ * The mailbox defaults editor, shared by the Mailbox Policy page and the setup wizard. Each field can be reset to what
30
+ * the server's config currently says (`policy.defaults`), which lets an administrator take a newly deployed value
31
+ * without typing it in; the reset fills the field in and is saved with the rest of the form.
32
+ */
18
33
  export default function MailboxPolicyForm({ policy, onChange, onDirtyChange, embedded = false }) {
34
+ const defaults = policy.defaults;
19
35
  const [defaultQuotaGb, setDefaultQuotaGb] = useState(toGb(policy.defaultQuotaBytes));
20
36
  const [autoProvisionEnabled, setAutoProvisionEnabled] = useState(policy.autoProvisionEnabled);
21
37
  const [autoProvisionQuotaGb, setAutoProvisionQuotaGb] = useState(toGb(policy.autoProvisionQuotaBytes));
@@ -70,5 +86,5 @@ export default function MailboxPolicyForm({ policy, onChange, onDirtyChange, emb
70
86
  setSaving(false);
71
87
  }
72
88
  }
73
- return (_jsxs("div", { className: "max-w-xl", children: [embedded ? (_jsx("h3", { className: "text-base font-bold uppercase tracking-wide mb-1", children: "Mailboxes" })) : (_jsx("h2", { className: "text-lg font-bold uppercase tracking-wide mb-1", children: "Mailboxes" })), _jsx("p", { className: "text-sm text-text-muted mb-5", children: "Defaults for new mailboxes on this server." }), error && _jsx(Alert, { children: error }), saved && !error && _jsx("div", { className: "mb-4 text-sm text-success font-medium", children: "Saved." }), _jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("label", { className: "flex flex-col gap-1.5 text-sm", children: [_jsx("span", { className: "font-semibold", children: "Default quota (GB)" }), _jsx("input", { "aria-label": "Default quota (GB)", type: "number", min: 0, step: "any", className: INPUT_CLASS, value: defaultQuotaGb, onChange: (e) => setDefaultQuotaGb(e.target.value) }), _jsx("span", { className: "text-xs text-text-muted", children: "The storage a mailbox starts with when an administrator creates it." })] }), _jsxs("label", { className: "flex items-start gap-2 text-sm", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: autoProvisionEnabled, onChange: (e) => setAutoProvisionEnabled(e.target.checked) }), _jsxs("span", { children: [_jsx("span", { className: "font-semibold block", children: "Let people create their own mailbox" }), _jsx("span", { className: "text-xs text-text-muted", children: "Someone who signs in without a mailbox can create one, using their username on one of this server\u2019s verified domains." })] })] }), autoProvisionEnabled && (_jsxs("label", { className: "flex flex-col gap-1.5 text-sm", children: [_jsx("span", { className: "font-semibold", children: "Quota for self-created mailboxes (GB)" }), _jsx("input", { "aria-label": "Quota for self-created mailboxes (GB)", type: "number", min: 0, step: "any", className: INPUT_CLASS, value: autoProvisionQuotaGb, onChange: (e) => setAutoProvisionQuotaGb(e.target.value) })] })), _jsx("div", { children: _jsx(Button, { type: "submit", loading: saving, disabled: saving, className: "!w-auto", children: "Save" }) })] })] }));
89
+ return (_jsxs("div", { className: "max-w-xl", children: [embedded ? (_jsx("h3", { className: "text-base font-bold uppercase tracking-wide mb-1", children: "Mailboxes" })) : (_jsx("h2", { className: "text-lg font-bold uppercase tracking-wide mb-1", children: "Mailboxes" })), _jsx("p", { className: "text-sm text-text-muted mb-5", children: "Defaults for new mailboxes on this server." }), error && _jsx(Alert, { children: error }), saved && !error && _jsx("div", { className: "mb-4 text-sm text-success font-medium", children: "Saved." }), _jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("label", { className: "flex flex-col gap-1.5 text-sm", children: [_jsx("span", { className: "font-semibold", children: "Default quota (GB)" }), _jsx("input", { "aria-label": "Default quota (GB)", type: "number", min: 0, step: "any", className: INPUT_CLASS, value: defaultQuotaGb, onChange: (e) => setDefaultQuotaGb(e.target.value) }), _jsx("span", { className: "text-xs text-text-muted", children: "The storage a mailbox starts with when an administrator creates it." })] }), _jsx(ResetToDefault, { label: defaults ? `${toGb(defaults.defaultQuotaBytes)} GB` : "", shown: !!defaults && differsFromBytes(defaultQuotaGb, defaults.defaultQuotaBytes), onClick: () => setDefaultQuotaGb(toGb(defaults.defaultQuotaBytes)) }), _jsxs("label", { className: "flex items-start gap-2 text-sm", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: autoProvisionEnabled, onChange: (e) => setAutoProvisionEnabled(e.target.checked) }), _jsxs("span", { children: [_jsx("span", { className: "font-semibold block", children: "Let people create their own mailbox" }), _jsx("span", { className: "text-xs text-text-muted", children: "Someone who signs in without a mailbox can create one, using their username on one of this server\u2019s verified domains." })] })] }), _jsx(ResetToDefault, { label: defaults?.autoProvisionEnabled ? "on" : "off", shown: !!defaults && autoProvisionEnabled !== defaults.autoProvisionEnabled, onClick: () => setAutoProvisionEnabled(defaults.autoProvisionEnabled) }), autoProvisionEnabled && (_jsxs(_Fragment, { children: [_jsxs("label", { className: "flex flex-col gap-1.5 text-sm", children: [_jsx("span", { className: "font-semibold", children: "Quota for self-created mailboxes (GB)" }), _jsx("input", { "aria-label": "Quota for self-created mailboxes (GB)", type: "number", min: 0, step: "any", className: INPUT_CLASS, value: autoProvisionQuotaGb, onChange: (e) => setAutoProvisionQuotaGb(e.target.value) })] }), _jsx(ResetToDefault, { label: defaults ? `${toGb(defaults.autoProvisionQuotaBytes)} GB` : "", shown: !!defaults && differsFromBytes(autoProvisionQuotaGb, defaults.autoProvisionQuotaBytes), onClick: () => setAutoProvisionQuotaGb(toGb(defaults.autoProvisionQuotaBytes)) })] })), _jsx("div", { children: _jsx(Button, { type: "submit", loading: saving, disabled: saving, className: "!w-auto", children: "Save" }) })] })] }));
74
90
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidmx/web-client",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "RapidMX's webmail and admin console React UI for web-based clients.",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/rapidmx/web-client",
@@ -39,7 +39,7 @@
39
39
  "@dnd-kit/core": "^6.3.1",
40
40
  "@journeyapps/wa-sqlite": "^2.0.4",
41
41
  "@peculiar/x509": "^2.1.0",
42
- "@rapidmx/react-shared": "^0.7.0",
42
+ "@rapidmx/react-shared": "^0.8.0",
43
43
  "@tiptap/core": "^3.31.3",
44
44
  "@tiptap/extension-highlight": "^3.31.3",
45
45
  "@tiptap/extension-image": "^3.31.3",