@visns-studio/visns-components 6.6.4 → 6.15.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.
Files changed (97) hide show
  1. package/README.md +1623 -18
  2. package/package.json +10 -4
  3. package/src/components/DataGrid.jsx +171 -37
  4. package/src/components/Fetch.jsx +80 -3
  5. package/src/components/Form.jsx +9 -0
  6. package/src/components/Navigation.jsx +109 -50
  7. package/src/components/Notification.jsx +279 -9
  8. package/src/components/TableFilter.jsx +14 -1
  9. package/src/components/auth/AuthBrandPanel.jsx +43 -0
  10. package/src/components/auth/AuthLoading.jsx +78 -0
  11. package/src/components/auth/AuthShell.jsx +59 -0
  12. package/src/components/auth/ClientAuth.jsx +161 -0
  13. package/src/components/auth/ClientAuthFrame.jsx +59 -0
  14. package/src/components/auth/ClientLogin.jsx +266 -55
  15. package/src/components/auth/ClientOTPVerify.jsx +587 -115
  16. package/src/components/auth/ImpersonateGate.jsx +254 -0
  17. package/src/components/auth/Login.jsx +134 -41
  18. package/src/components/auth/LogoutScreen.jsx +112 -0
  19. package/src/components/auth/Reset.jsx +134 -77
  20. package/src/components/auth/TwoFactorAuth.jsx +475 -297
  21. package/src/components/auth/Verify.jsx +237 -126
  22. package/src/components/auth/authEndpoints.js +105 -0
  23. package/src/components/auth/authFont.js +23 -0
  24. package/src/components/auth/authHelpers.js +465 -0
  25. package/src/components/auth/clientAuthProtocols.js +240 -0
  26. package/src/components/auth/useOptionalRouter.js +49 -0
  27. package/src/components/callQueue/CallQueuePop.jsx +1502 -0
  28. package/src/components/callQueue/CallQueueSettings.jsx +508 -0
  29. package/src/components/callQueue/callQueueHelpers.js +280 -0
  30. package/src/components/callQueue/callQueueSettingsHelpers.js +75 -0
  31. package/src/components/columns/ColumnRenderers.jsx +53 -7
  32. package/src/components/generic/GenericAuth.jsx +163 -96
  33. package/src/components/generic/GenericDetail.jsx +215 -90
  34. package/src/components/generic/GenericIndex.jsx +20 -47
  35. package/src/components/generic/GenericMain.jsx +5 -0
  36. package/src/components/generic/GroupedReportRenderer.jsx +1 -5
  37. package/src/components/generic/StandardModal.jsx +17 -5
  38. package/src/components/generic/reportSemanticSteps/SemanticEntityStep.jsx +1 -6
  39. package/src/components/generic/reportSemanticSteps/SemanticFieldsStep.jsx +4 -11
  40. package/src/components/generic/reportSemanticSteps/SemanticFiltersStep.jsx +9 -27
  41. package/src/components/generic/reportSemanticSteps/SemanticGroupingStep.jsx +5 -13
  42. package/src/components/generic/reportSemanticSteps/SemanticParameterPrompt.jsx +5 -13
  43. package/src/components/generic/reportSemanticSteps/SemanticPreviewStep.jsx +14 -31
  44. package/src/components/generic/reportSemanticSteps/SemanticRelationsStep.jsx +3 -9
  45. package/src/components/generic/reportSemanticSteps/SemanticValueInput.jsx +2 -15
  46. package/src/components/navActive.js +60 -0
  47. package/src/components/notify/desktopNotifications.js +256 -0
  48. package/src/components/sketch/SketchField.jsx +12 -2
  49. package/src/components/sms/SmsComposeModal.jsx +495 -0
  50. package/src/components/sms/SmsInbox.jsx +596 -0
  51. package/src/components/sms/SmsInboxBadge.jsx +468 -0
  52. package/src/components/sms/SmsLineSettings.jsx +854 -0
  53. package/src/components/sms/SmsThreadPanel.jsx +1184 -0
  54. package/src/components/sms/smsEndpoints.js +56 -0
  55. package/src/components/sms/smsHelpers.js +1075 -0
  56. package/src/components/sms/smsLiveState.js +132 -0
  57. package/src/components/sms/useSmsLive.js +307 -0
  58. package/src/components/styles/CallQueuePop.module.scss +646 -0
  59. package/src/components/styles/CallQueueSettings.module.scss +460 -0
  60. package/src/components/styles/ClientAuth.module.scss +229 -0
  61. package/src/components/styles/GenericDetail.module.scss +153 -26
  62. package/src/components/styles/GenericIndex.module.scss +13 -0
  63. package/src/components/styles/GenericMain.module.scss +19 -2
  64. package/src/components/styles/ImpersonateGate.module.scss +85 -0
  65. package/src/components/styles/Login.module.scss +13 -122
  66. package/src/components/styles/LogoutScreen.module.scss +89 -0
  67. package/src/components/styles/Navigation.module.scss +509 -223
  68. package/src/components/styles/Notification.module.scss +103 -0
  69. package/src/components/styles/Reset.module.scss +52 -186
  70. package/src/components/styles/Sms.module.scss +2382 -0
  71. package/src/components/styles/TableFilter.module.scss +118 -98
  72. package/src/components/styles/TwoFactorAuth.module.scss +115 -176
  73. package/src/components/styles/Vault.module.scss +1983 -0
  74. package/src/components/styles/Verify.module.scss +57 -180
  75. package/src/components/styles/_authBrandPanel.scss +163 -0
  76. package/src/components/styles/_authShell.scss +369 -0
  77. package/src/components/utils/buildEnv.js +67 -0
  78. package/src/components/utils/displayValue.js +94 -0
  79. package/src/components/utils/useDensity.js +345 -9
  80. package/src/components/vault/OtpChip.jsx +208 -0
  81. package/src/components/vault/PasswordGenerator.jsx +145 -0
  82. package/src/components/vault/QrScanner.jsx +326 -0
  83. package/src/components/vault/VaultAccessLog.jsx +225 -0
  84. package/src/components/vault/VaultConfirmPanel.jsx +113 -0
  85. package/src/components/vault/VaultEntryForm.jsx +740 -0
  86. package/src/components/vault/VaultManager.jsx +1000 -0
  87. package/src/components/vault/VaultQuickSearch.jsx +681 -0
  88. package/src/components/vault/qrDecode.js +257 -0
  89. package/src/components/vault/useDebouncedValue.js +22 -0
  90. package/src/components/vault/useVaultReveal.js +160 -0
  91. package/src/components/vault/vaultClipboard.js +33 -0
  92. package/src/components/vault/vaultEndpoints.js +40 -0
  93. package/src/components/vault/vaultFit.js +116 -0
  94. package/src/components/vault/vaultHelpers.js +547 -0
  95. package/src/components/vault/vaultNavigation.jsx +58 -0
  96. package/src/components/vault/vaultOtp.js +105 -0
  97. package/src/index.js +233 -1
@@ -1,19 +1,84 @@
1
1
  import '../styles/global.css';
2
+ import './authFont';
2
3
 
3
4
  import React, { useState } from 'react';
4
- import { useNavigate, useParams } from 'react-router-dom';
5
+ import { Link, useNavigate, useParams } from 'react-router-dom';
5
6
  import { toast } from 'react-toastify';
6
- import parse from 'html-react-parser';
7
7
  import validator from 'validator';
8
- import Reveal from '../utils/Reveal';
8
+ import { ArrowLeft, Eye, EyeOff } from 'lucide-react';
9
9
 
10
10
  import CustomFetch from '../Fetch';
11
+ import AuthShell from './AuthShell';
12
+ import { resolveAuthEndpoints } from './authEndpoints';
13
+ import { normaliseAuthError } from './authHelpers';
11
14
 
12
15
  import styles from '../styles/Verify.module.scss';
13
16
 
14
- const Verify = ({ logo }) => {
17
+ /**
18
+ * Set a new password — the second half of the reset journey, reached from the
19
+ * link in the reset email (`/verify/:code`).
20
+ *
21
+ * Redesigned in 6.7.0 onto the same two-pane frame as the rest of the journey.
22
+ * The one behavioural change that came with it: the password rules are now
23
+ * stated up front and tick themselves off as the user types, instead of
24
+ * arriving as a five-line `<br />`-separated toast after a rejected submit. The
25
+ * rules themselves are unchanged — `validator.isStrongPassword`'s defaults, the
26
+ * same function that gates the submit.
27
+ */
28
+
29
+ /**
30
+ * The checks `validator.isStrongPassword` applies at its defaults, restated so
31
+ * the screen can show which are already satisfied. This list is display only:
32
+ * the submit is still gated by validator itself, so the two can never disagree
33
+ * about whether a password is acceptable.
34
+ */
35
+ const PASSWORD_RULES = [
36
+ { label: 'At least 8 characters', test: (v) => v.length >= 8 },
37
+ { label: 'One uppercase letter', test: (v) => /[A-Z]/.test(v) },
38
+ { label: 'One lowercase letter', test: (v) => /[a-z]/.test(v) },
39
+ { label: 'One number', test: (v) => /\d/.test(v) },
40
+ { label: 'One symbol', test: (v) => /[^A-Za-z0-9]/.test(v) },
41
+ ];
42
+
43
+ const DEFAULT_COPY = {
44
+ title: 'Reset your password',
45
+ subtitle: 'Choose a new password for your account.',
46
+ passwordLabel: 'Password',
47
+ repeatLabel: 'Re-type Password',
48
+ submit: 'Set New Password',
49
+ submitting: 'Saving…',
50
+ back: 'Back to Login',
51
+ success:
52
+ 'You have successfully set a new password, please use your new password to login into the system.',
53
+ /** Client-side validation. */
54
+ required: 'Please type in your new password.',
55
+ mismatch: 'The password does not match, please re-type the password.',
56
+ weak: 'That password does not meet all of the requirements listed below.',
57
+ failed: 'Could not set a new password, please try again.',
58
+ };
59
+
60
+ const Verify = ({
61
+ logo,
62
+ loginBg,
63
+ config = {},
64
+ /** Merged over the 6.6.3 literals — see authEndpoints.js. */
65
+ endpoints,
66
+ /** Merged over DEFAULT_COPY above. */
67
+ copy: copyOverrides,
68
+ /** Set false to hide the live rules checklist. */
69
+ showRules = true,
70
+ rules = PASSWORD_RULES,
71
+ loginPath = '/login',
72
+ }) => {
15
73
  const navigate = useNavigate();
16
74
  const { code } = useParams();
75
+
76
+ const urls = resolveAuthEndpoints(endpoints);
77
+ const copy = { ...DEFAULT_COPY, ...(copyOverrides || {}) };
78
+ const branding = config.branding || {};
79
+
80
+ const [isLoading, setIsLoading] = useState(false);
81
+ const [showPassword, setShowPassword] = useState(false);
17
82
  const [formData, setFormData] = useState({
18
83
  password: '',
19
84
  passwordRepeat: '',
@@ -26,150 +91,196 @@ const Verify = ({ logo }) => {
26
91
 
27
92
  const handleInputChange = (e) => {
28
93
  const { name, value } = e.target;
29
- setFormData((prevFormData) => ({
30
- ...prevFormData,
94
+
95
+ setFormData((prev) => ({
96
+ ...prev,
31
97
  [name]: value,
32
98
  passwordClass: '',
33
99
  passwordRepeatClass: '',
34
100
  }));
35
101
  };
36
102
 
103
+ /** Returns an error string, or '' when the pair is acceptable. */
37
104
  const validatePassword = () => {
38
- let _error = '';
39
-
40
105
  if (password === '') {
41
- _error = 'Please type in your new password.';
42
- setFormData((prevFormData) => ({
43
- ...prevFormData,
106
+ setFormData((prev) => ({ ...prev, passwordClass: 'inputError' }));
107
+
108
+ return copy.required;
109
+ }
110
+
111
+ if (password !== passwordRepeat) {
112
+ setFormData((prev) => ({
113
+ ...prev,
44
114
  passwordClass: 'inputError',
115
+ passwordRepeatClass: 'inputError',
45
116
  }));
46
- } else {
47
- if (password !== passwordRepeat) {
48
- _error =
49
- 'The password does not match, please re-type the password.';
50
- setFormData((prevFormData) => ({
51
- ...prevFormData,
52
- passwordClass: 'inputError',
53
- passwordRepeatClass: 'inputError',
54
- }));
55
- } else {
56
- if (!validator.isStrongPassword(password)) {
57
- _error =
58
- 'Your password is too weak it must contain the following:<br />- Must contain at least 8 characters.<br />- Must contain at least one uppercase letter.<br />- Must contain at least one lowercase letter.<br />- Must contain at least one number.<br />- Must contain at least one symbol.<br />';
59
- }
60
- }
117
+
118
+ return copy.mismatch;
119
+ }
120
+
121
+ if (!validator.isStrongPassword(password)) {
122
+ setFormData((prev) => ({ ...prev, passwordClass: 'inputError' }));
123
+
124
+ return copy.weak;
61
125
  }
62
126
 
63
- return _error;
127
+ return '';
64
128
  };
65
129
 
66
130
  const handleSubmit = (e) => {
67
- if (e) {
68
- e.preventDefault();
69
- const errorMsg = validatePassword();
70
-
71
- if (errorMsg === '') {
72
- CustomFetch(
73
- '/password/reset',
74
- 'POST',
75
- {
76
- code: code,
77
- password: password,
78
- password_confirmation: passwordRepeat,
79
- },
80
- function (result) {
81
- if (result.error === '') {
82
- toast.success(
83
- 'You have successfully set a new password, please use your new password to login into the system.'
84
- );
131
+ if (!e) {
132
+ return;
133
+ }
85
134
 
86
- navigate('/login');
87
- } else {
88
- toast.error(result.error);
89
- }
90
- }
91
- );
92
- } else {
93
- toast.error(<div>{parse(errorMsg)}</div>);
94
- }
135
+ e.preventDefault();
136
+
137
+ // The button is disabled too; this also covers Enter in the field.
138
+ if (isLoading) {
139
+ return;
140
+ }
141
+
142
+ const errorMsg = validatePassword();
143
+
144
+ if (errorMsg !== '') {
145
+ toast.error(errorMsg);
146
+
147
+ return;
95
148
  }
149
+
150
+ setIsLoading(true);
151
+
152
+ CustomFetch(
153
+ urls.passwordReset,
154
+ 'POST',
155
+ {
156
+ code: code,
157
+ password: password,
158
+ password_confirmation: passwordRepeat,
159
+ },
160
+ (result) => {
161
+ // Cleared only on failure: on success this screen navigates
162
+ // away, and a button that returns to "Set New Password" first
163
+ // invites a second submit against a spent reset code.
164
+ const failure =
165
+ (result && typeof result === 'object' && result.error) ||
166
+ '';
167
+
168
+ if (failure) {
169
+ setIsLoading(false);
170
+ toast.error(normaliseAuthError(failure, copy.failed));
171
+
172
+ return;
173
+ }
174
+
175
+ toast.success(copy.success);
176
+ navigate(loginPath);
177
+ },
178
+ (error) => {
179
+ setIsLoading(false);
180
+ toast.error(normaliseAuthError(error, copy.failed));
181
+ }
182
+ );
96
183
  };
97
184
 
98
185
  return (
99
- <div className={styles.lcontainer}>
100
- <div className={styles.lwrap}>
101
- <div className={styles.logincontainer}>
102
- <form onSubmit={handleSubmit}>
103
- <Reveal effect="fadeInUp">
104
- <div className={styles.login}>
105
- <img
106
- src={logo}
107
- alt="CRM"
108
- className={styles.loginlogo}
109
- />
110
- <h2
111
- style={{
112
- textAlign: 'center',
113
- marginBottom: '1.5rem',
114
- color: '#111827',
115
- fontWeight: '600',
116
- fontSize: '1.5rem',
117
- }}
118
- >
119
- Reset your password
120
- </h2>
121
- <div
122
- className={`${styles.formItem} ${styles.fwItem}`}
123
- >
124
- <label className={styles.fi__label}>
125
- <input
126
- type="password"
127
- name="password"
128
- value={password}
129
- onChange={handleInputChange}
130
- tabIndex="1"
131
- className={passwordClass}
132
- />
133
- <span className={styles.fi__span}>
134
- Password
135
- </span>
136
- </label>
137
- </div>
138
- <div
139
- className={`${styles.formItem} ${styles.fwItem}`}
140
- >
141
- <label className={styles.fi__label}>
142
- <input
143
- type="password"
144
- name="passwordRepeat"
145
- value={passwordRepeat}
146
- onChange={handleInputChange}
147
- tabIndex="2"
148
- className={passwordRepeatClass}
149
- />
150
- <span className={styles.fi__span}>
151
- Re-type Password
152
- </span>
153
- </label>
154
- </div>
155
- <div
156
- className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem}`}
157
- >
158
- <button
159
- className={styles.btn}
160
- type="submit"
161
- tabIndex="3"
162
- >
163
- Set New Password
164
- </button>
165
- </div>
166
- </div>
167
- </Reveal>
168
- </form>
186
+ <AuthShell
187
+ styles={styles}
188
+ logo={logo}
189
+ loginBg={loginBg}
190
+ branding={branding}
191
+ title={copy.title}
192
+ subtitle={copy.subtitle}
193
+ footer={branding.footer}
194
+ >
195
+ <form onSubmit={handleSubmit} className={styles.form}>
196
+ <div className={styles.field}>
197
+ <input
198
+ id="verify-password"
199
+ type={showPassword ? 'text' : 'password'}
200
+ name="password"
201
+ value={password}
202
+ onChange={handleInputChange}
203
+ tabIndex="1"
204
+ className={passwordClass}
205
+ autoComplete="new-password"
206
+ placeholder=" "
207
+ autoFocus
208
+ disabled={isLoading}
209
+ />
210
+ <label htmlFor="verify-password">
211
+ {copy.passwordLabel}
212
+ </label>
213
+ {/* Typing a password blind, twice, is where most failed
214
+ resets come from. */}
215
+ <button
216
+ type="button"
217
+ className={styles.reveal}
218
+ onClick={() => setShowPassword((on) => !on)}
219
+ aria-label={
220
+ showPassword ? 'Hide password' : 'Show password'
221
+ }
222
+ tabIndex="-1"
223
+ >
224
+ {showPassword ? (
225
+ <EyeOff size={18} strokeWidth={1.9} />
226
+ ) : (
227
+ <Eye size={18} strokeWidth={1.9} />
228
+ )}
229
+ </button>
169
230
  </div>
170
- </div>
171
- </div>
231
+
232
+ <div className={styles.field}>
233
+ <input
234
+ id="verify-password-repeat"
235
+ type={showPassword ? 'text' : 'password'}
236
+ name="passwordRepeat"
237
+ value={passwordRepeat}
238
+ onChange={handleInputChange}
239
+ tabIndex="2"
240
+ className={passwordRepeatClass}
241
+ autoComplete="new-password"
242
+ placeholder=" "
243
+ disabled={isLoading}
244
+ />
245
+ <label htmlFor="verify-password-repeat">
246
+ {copy.repeatLabel}
247
+ </label>
248
+ </div>
249
+
250
+ {showRules ? (
251
+ <ul className={styles.rules}>
252
+ {rules.map((rule) => {
253
+ const met = rule.test(password);
254
+
255
+ return (
256
+ <li
257
+ key={rule.label}
258
+ className={met ? styles.ruleMet : undefined}
259
+ >
260
+ {rule.label}
261
+ </li>
262
+ );
263
+ })}
264
+ </ul>
265
+ ) : null}
266
+
267
+ <button
268
+ className={styles.submit}
269
+ type="submit"
270
+ disabled={isLoading}
271
+ tabIndex="3"
272
+ >
273
+ {isLoading ? copy.submitting : copy.submit}
274
+ </button>
275
+ </form>
276
+
277
+ <Link to={loginPath} className={styles.back}>
278
+ <ArrowLeft size={15} strokeWidth={2} />
279
+ {copy.back}
280
+ </Link>
281
+ </AuthShell>
172
282
  );
173
283
  };
174
284
 
175
285
  export default Verify;
286
+ export { PASSWORD_RULES };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Auth endpoint table.
3
+ *
4
+ * Every URL the auth screens talk to used to be a string literal inside the
5
+ * component that used it, which made the whole journey unusable by any app
6
+ * whose backend does not mount Laravel's routes at exactly those paths. They
7
+ * are collected here instead, and every screen takes an `endpoints` prop that
8
+ * is merged over these defaults.
9
+ *
10
+ * The defaults ARE the 6.6.3 literals, so an app that passes nothing keeps the
11
+ * behaviour it already has.
12
+ *
13
+ * Pure module (no React, no DOM) so it can be unit-tested with `node --test`.
14
+ */
15
+
16
+ /** The paths the library used before they were configurable. */
17
+ export const DEFAULT_AUTH_ENDPOINTS = {
18
+ /** Login.jsx — POST {email, password, remember, location}. */
19
+ authenticate: '/login/authenticate',
20
+ /** TwoFactorAuth.jsx — POST {code, previous_url, remember}. */
21
+ twoFactorChallenge: '/login/two-factor-challenge',
22
+ /** TwoFactorAuth.jsx (mode="code") — POST {} to re-send the SMS code. */
23
+ twoFactorResend: '/login/two-factor-resend',
24
+ /** Login.jsx — the azure SSO button navigates here. */
25
+ azureSso: '/auth/azure',
26
+ /** GenericAuth.jsx — GET, polled to decide whether a session exists. */
27
+ profile: '/ajax/user/profile',
28
+ /** Reset.jsx — POST {email}. */
29
+ passwordForgot: '/password/forgot',
30
+ /** Verify.jsx — POST {code, password, password_confirmation}. */
31
+ passwordReset: '/password/reset',
32
+ };
33
+
34
+ /**
35
+ * Merge a partial `endpoints` prop over the defaults.
36
+ *
37
+ * Nullish and empty-string entries are dropped rather than applied: passing
38
+ * `{authenticate: undefined}` must not blank the default and leave a component
39
+ * POSTing to ''. Unknown keys are carried through, so a consumer can park its
40
+ * own paths on the same object.
41
+ */
42
+ export const resolveAuthEndpoints = (overrides) => {
43
+ const resolved = { ...DEFAULT_AUTH_ENDPOINTS };
44
+
45
+ if (!overrides || typeof overrides !== 'object') {
46
+ return resolved;
47
+ }
48
+
49
+ Object.keys(overrides).forEach((key) => {
50
+ const value = overrides[key];
51
+
52
+ if (value === null || value === undefined || value === '') {
53
+ return;
54
+ }
55
+
56
+ resolved[key] = value;
57
+ });
58
+
59
+ return resolved;
60
+ };
61
+
62
+ /** The client-portal route paths, as they were hardcoded before 6.7.0. */
63
+ export const DEFAULT_CLIENT_PATHS = {
64
+ /** Route segment the client routes are nested under in GenericAuth. */
65
+ base: 'client',
66
+ login: '/client/login',
67
+ verify: '/client/verify',
68
+ portal: '/client/portal',
69
+ };
70
+
71
+ /**
72
+ * Merge a partial `paths` prop over the client defaults, the same way
73
+ * `resolveAuthEndpoints` does. When only `base` is given the three absolute
74
+ * paths are derived from it, so `{base: 'portal'}` yields the whole
75
+ * `/portal/*` family without restating each one.
76
+ */
77
+ export const resolveClientPaths = (overrides) => {
78
+ if (!overrides || typeof overrides !== 'object') {
79
+ return { ...DEFAULT_CLIENT_PATHS };
80
+ }
81
+
82
+ const base =
83
+ typeof overrides.base === 'string' && overrides.base !== ''
84
+ ? overrides.base.replace(/^\/+|\/+$/g, '')
85
+ : DEFAULT_CLIENT_PATHS.base;
86
+
87
+ const derived = {
88
+ base,
89
+ login: `/${base}/login`,
90
+ verify: `/${base}/verify`,
91
+ portal: `/${base}/portal`,
92
+ };
93
+
94
+ Object.keys(overrides).forEach((key) => {
95
+ const value = overrides[key];
96
+
97
+ if (key === 'base' || value === null || value === undefined || value === '') {
98
+ return;
99
+ }
100
+
101
+ derived[key] = value;
102
+ });
103
+
104
+ return derived;
105
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The auth journey's typeface.
3
+ *
4
+ * Login.module.scss has asked for 'Barlow' since the screen was redesigned, but
5
+ * nothing in this library ever loaded it — @fontsource/barlow was a dependency
6
+ * that no file imported, so every consumer fell through to system-ui and the
7
+ * screen only looked right in the one app that happened to load Barlow itself.
8
+ *
9
+ * Importing it from a module of its own means each auth screen adds the font
10
+ * with one line, and an app that never renders an auth screen never pays for
11
+ * the four @font-face declarations.
12
+ *
13
+ * The weights are exactly the ones the auth styles use: 300 (.brandSub), 400
14
+ * (body), 600 (labels, buttons) and 700 (.brandLine, .title). An app that
15
+ * prefers its own face sets `--auth-font-family` and the CSS never asks for
16
+ * Barlow at all — the files are still fetched by the browser only if a rule
17
+ * actually uses the family.
18
+ */
19
+
20
+ import '@fontsource/barlow/300.css';
21
+ import '@fontsource/barlow/400.css';
22
+ import '@fontsource/barlow/600.css';
23
+ import '@fontsource/barlow/700.css';