@owlmeans/web-panel 0.1.18-rc.25 → 0.1.18-rc.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/web-panel",
4
- "version": "0.1.18-rc.19",
5
- "generatedAt": "2026-08-30T20:53:43.839Z",
4
+ "version": "0.1.18-rc.27",
5
+ "generatedAt": "2026-09-01T12:30:48.603Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/web-panel
9
9
 
10
10
  **Layer:** Web (React)
11
- **Install:** `"@owlmeans/web-panel": "^0.1.18-rc.19"` in `dependencies`
11
+ **Install:** `"@owlmeans/web-panel": "^0.1.18-rc.27"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
@@ -18,6 +18,7 @@ user-invocable: false
18
18
  | `NavLayout` | The application shell — header, section menu, screen menu, content, footer |
19
19
  | `TopNav` / `SideNav` / `Footer` | The shell's pieces, mountable on their own |
20
20
  | `Toaster` | The application's toast surface — mounted once, in the layout |
21
+ | `LoginScreen` / `LocalizedLoginScreen` / `appendLoginScreen` | The identity-provider choice screen — see `login-methods` |
21
22
  | `components` submodule | shadcn/Radix panel/form components |
22
23
  | Re-exports from `@owlmeans/client-panel` | Cross-platform panel primitives, incl. `usePanelNav` and the `PanelNav*` types |
23
24
  | `main`, `exports`, `context`, `modules`, `types` | Wiring helpers |
@@ -174,6 +175,15 @@ export const MainLayout: FC<PropsWithChildren> = ({ children }) => <>
174
175
  - `sonner` is a dependency of this package, so nothing is required of the consumer — but an app
175
176
  raising its own toasts should declare `sonner` too, at a range that resolves to the same copy.
176
177
 
178
+ ## Subpath: `./consent`
179
+
180
+ `PanelCookieConsent` and `PanelCookiePolicy` — `@owlmeans/web-consent`'s components bound to this
181
+ app's language and translations, falling through to the package's own seven-language bundle for
182
+ every key the app has not overridden. See the `consent` skill.
183
+
184
+ A re-export does not move Tailwind class strings, so a consumer still adds an `@source` for
185
+ `@owlmeans/web-consent/build` as well as for this package's.
186
+
177
187
  ## Consumer setup — the `@` contract and Tailwind
178
188
 
179
189
  `web-panel` emits `@/components/ui/*` and `@/lib/utils` verbatim; the app's bundler resolves `@` to
@@ -1,2 +1,2 @@
1
- export declare const modules: import("@owlmeans/entrypoint").CommonEntrypoint[];
1
+ export declare const modules: any[];
2
2
  //# sourceMappingURL=modules.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../../src/auth/modules.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,OAAO,mDAAuB,CAAA"}
1
+ {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../../src/auth/modules.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,OAAO,OAAuB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../../src/components/login/screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAO/B,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAWhF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CA+D5C,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,EAAE,CAAC,gBAAgB,CAOrD,CAAA"}
1
+ {"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../../src/components/login/screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAQ/B,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAWhF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CA6F5C,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,EAAE,CAAC,gBAAgB,CAOrD,CAAA"}
@@ -6,6 +6,7 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/com
6
6
  import { cn } from '@/lib/utils';
7
7
  import { useI18nLib } from '@owlmeans/client-i18n';
8
8
  import { useLoginMethods } from '@owlmeans/client-panel/auth';
9
+ import { loginAttemptError } from '@owlmeans/client-auth/login';
9
10
  import { LoginMethodIcon } from './icons.js';
10
11
  import { LoginTerms } from './terms.js';
11
12
  import { LoginCredit } from './credit.js';
@@ -33,12 +34,31 @@ export const LoginScreen = props => {
33
34
  ...(props.methods != null ? { methods: props.methods } : {}),
34
35
  });
35
36
  const Logo = props.Logo;
36
- return _jsx("div", { className: cn('flex min-h-full w-full items-center justify-center p-4', props.className), children: _jsxs(Card, { className: cn('w-full max-w-sm', props.containerClassName), children: [_jsxs(CardHeader, { className: "items-center gap-2 text-center", children: [Logo != null && _jsx("div", { className: "flex justify-center pb-2", children: isValidElement(Logo) ? Logo : typeof Logo === 'function'
37
+ const attemptError = model.busy == null ? loginAttemptError(model.outcome) : null;
38
+ // The viewport height is set INLINE, and deliberately.
39
+ //
40
+ // A percentage minimum (`min-h-full`) resolves against a parent that has a height, and this
41
+ // screen is rendered straight out of the dispatcher into whatever the app has around it — which
42
+ // almost never carries one, so the box collapsed to its content and the card sat at the top of
43
+ // an empty page. The fix is a viewport unit; the reason it is not a utility class is that this
44
+ // package's classes reach a consuming app's stylesheet only through a `@source` scan of its
45
+ // `src`, and a class NEW to that stylesheet is the one thing the scan can be stale about. Every
46
+ // other utility here already exists in any app that renders anything; a missing `min-h-dvh`
47
+ // rule, by contrast, is invisible — the screen looks nearly right and is silently uncentred.
48
+ //
49
+ // `props.style` still wins, because a class-based override no longer can.
50
+ return _jsx("div", { "data-login-screen": true, style: { minHeight: '100dvh', ...props.style }, className: cn('flex w-full items-center justify-center p-4', props.className), children: _jsxs(Card, { className: cn('w-full max-w-sm', props.containerClassName), children: [_jsxs(CardHeader, { className: "items-center gap-2 text-center", children: [Logo != null && _jsx("div", { className: "flex justify-center pb-2", children: isValidElement(Logo) ? Logo : typeof Logo === 'function'
37
51
  ? _jsx(Logo, { className: "h-10 w-auto" }) : Logo }), _jsx(CardTitle, { className: "text-xl", children: props.title ?? t('login.title', 'Sign in') }), _jsx(CardDescription, { children: props.subtitle ?? t('login.subtitle', 'Choose how you would like to continue.') })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [model.methods.length < 1
38
52
  ? _jsx("p", { role: "status", className: "text-sm text-muted-foreground text-center", children: t('login.empty', 'No sign-in method is configured for this application.') })
39
- : _jsx("div", { className: "flex flex-col gap-2", children: model.methods.map((method) => _jsxs(Button, { type: "button", "data-login-method": method.id, variant: VARIANT[method.emphasis ?? 'secondary'] ?? 'outline', "aria-disabled": model.blocked, "data-blocked": model.blocked ? 'true' : undefined, className: cn('w-full justify-center gap-2', model.blocked && 'opacity-60'), autoFocus: method.id === model.primary?.id, onClick: () => model.select(method), children: [model.busy === method.id
53
+ : _jsx("div", { className: "flex flex-col gap-2", children: model.methods.map((method) => _jsxs(Button, { type: "button", "data-login-method": method.id, variant: VARIANT[method.emphasis ?? 'secondary'] ?? 'outline', "aria-disabled": model.blocked, "data-blocked": model.blocked ? 'true' : undefined,
54
+ // `cursor-pointer` explicitly: this screen renders through the CONSUMER's vendored
55
+ // `@/components/ui/button`, and an app whose shadcn copy predates the cursor rule
56
+ // shows an arrow over the one control on the page. Stating it here makes the screen
57
+ // behave the same whichever copy it lands on.
58
+ className: cn('w-full justify-center gap-2 cursor-pointer', model.blocked && 'opacity-60'), autoFocus: method.id === model.primary?.id, onClick: () => model.select(method), children: [model.busy === method.id
40
59
  ? _jsx(Loader2, { className: "size-4 animate-spin" })
41
- : _jsx(LoginMethodIcon, { name: method.icon, className: "size-4" }), method.label ?? t(`login.method.${method.i18nKey ?? method.id}`, method.id)] }, method.id)) }), model.terms.required && _jsx(LoginTerms, { model: model.terms, translate: t }), model.error != null && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: model.error })] }), _jsx("div", { className: "px-6", children: props.footer ?? _jsx(LoginCredit, { model: model.credit, translate: t }) })] }) });
60
+ : _jsx(LoginMethodIcon, { name: method.icon, className: "size-4" }), method.label ?? t(`login.method.${method.i18nKey ?? method.id}`, method.id)] }, method.id)) }), model.terms.required && _jsx(LoginTerms, { model: model.terms, translate: t }), (model.error ?? attemptError) != null
61
+ && _jsx("p", { role: "alert", "data-login-error": true, className: "text-sm text-destructive text-center", children: model.error ?? t(attemptError, 'Sign-in did not complete. Please try again.') })] }), _jsx("div", { className: "px-6", children: props.footer ?? _jsx(LoginCredit, { model: model.credit, translate: t }) })] }) });
42
62
  };
43
63
  /**
44
64
  * {@link LoginScreen} bound to the application's own translations.
@@ -1 +1 @@
1
- {"version":3,"file":"screen.js","sourceRoot":"","sources":["../../../src/components/login/screen.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"screen.js","sourceRoot":"","sources":["../../../src/components/login/screen.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"terms.d.ts","sourceRoot":"","sources":["../../../src/components/login/terms.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAA;AAE1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAElE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAA;IACtB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACxD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAuBD,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CAuC1C,CAAA"}
1
+ {"version":3,"file":"terms.d.ts","sourceRoot":"","sources":["../../../src/components/login/terms.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAA;AAE1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAElE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAA;IACtB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACxD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAuBD,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CA0C1C,CAAA"}
@@ -19,7 +19,10 @@ const interpolate = (sentence, slots) => sentence
19
19
  });
20
20
  export const LoginTerms = ({ model, translate, className }) => {
21
21
  const link = (href, label) => _jsx("a", { href: href, target: "_blank", rel: "noreferrer noopener", className: "underline underline-offset-2 hover:text-foreground", children: label });
22
- return _jsxs("div", { className: cn('flex flex-col gap-1.5', className), children: [_jsxs("label", { className: "flex items-start gap-2 text-sm text-muted-foreground cursor-pointer", children: [_jsx("input", { type: "checkbox", "data-login-terms": true, className: "mt-0.5 size-4 shrink-0 accent-primary", checked: model.accepted, "aria-invalid": model.attempted && !model.accepted, onChange: event => model.accept(event.target.checked) }), _jsx("span", { children: interpolate(translate('login.terms.agreement', 'I have read and agree to the {{terms}} and the {{privacy}}.'), {
22
+ // Centred, like every other row in the card. The checkbox stays at the start of the sentence
23
+ // rather than above it, so `justify-center` centres the pair and `text-center` centres the
24
+ // wrapped lines within it.
25
+ return _jsxs("div", { className: cn('flex flex-col items-center gap-1.5 text-center', className), children: [_jsxs("label", { className: "flex items-start justify-center gap-2 text-sm text-muted-foreground cursor-pointer", children: [_jsx("input", { type: "checkbox", "data-login-terms": true, className: "mt-0.5 size-4 shrink-0 accent-primary", checked: model.accepted, "aria-invalid": model.attempted && !model.accepted, onChange: event => model.accept(event.target.checked) }), _jsx("span", { children: interpolate(translate('login.terms.agreement', 'I have read and agree to the {{terms}} and the {{privacy}}.'), {
23
26
  terms: link(model.urls.terms, translate('login.terms.terms', 'Terms & Conditions')),
24
27
  privacy: link(model.urls.privacy, translate('login.terms.privacy', 'Privacy Policy')),
25
28
  cookies: model.urls.cookies != null
@@ -1 +1 @@
1
- {"version":3,"file":"terms.js","sourceRoot":"","sources":["../../../src/components/login/terms.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAShC;;;;;;;;GAQG;AACH,MAAM,WAAW,GAAG,CAClB,QAAgB,EAAE,KAAgC,EACrC,EAAE,CAAC,QAAQ;KACvB,KAAK,CAAC,mBAAmB,CAAC;KAC1B,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;IACnB,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAE9C,OAAO,KAAK,IAAI,IAAI;QAClB,CAAC,CAAC,yBAAmB,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,IAA/B,KAAK,CAAkC;QACpD,CAAC,CAAC,yBAAmB,IAAI,IAAZ,KAAK,CAAe,CAAA;AACrC,CAAC,CAAC,CAAA;AAEJ,MAAM,CAAC,MAAM,UAAU,GAAwB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE;IACjF,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,KAAa,EAAa,EAAE,CACtD,YAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,qBAAqB,EACtD,SAAS,EAAC,oDAAoD,YAAE,KAAK,GAAK,CAAA;IAE9E,OAAO,eAAK,SAAS,EAAE,EAAE,CAAC,uBAAuB,EAAE,SAAS,CAAC,aAC3D,iBAAO,SAAS,EAAC,qEAAqE,aAOpF,gBACE,IAAI,EAAC,UAAU,4BACf,SAAS,EAAC,uCAAuC,EACjD,OAAO,EAAE,KAAK,CAAC,QAAQ,kBACT,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,QAAQ,EAChD,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GACrD,EACF,yBACG,WAAW,CACV,SAAS,CAAC,uBAAuB,EAC/B,6DAA6D,CAAC,EAChE;4BACE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,mBAAmB,EAAE,oBAAoB,CAAC,CAAC;4BACnF,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,qBAAqB,EAAE,gBAAgB,CAAC,CAAC;4BACrF,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI;gCACjC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,qBAAqB,EAAE,eAAe,CAAC,CAAC;gCAC7E,CAAC,CAAC,IAAI;yBACT,CACF,GACI,IACD,EACP,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,YAAG,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,0BAA0B,YACxF,SAAS,CAAC,sBAAsB,EAC/B,2EAA2E,CAAC,GAC5E,IACA,CAAA;AACR,CAAC,CAAA"}
1
+ {"version":3,"file":"terms.js","sourceRoot":"","sources":["../../../src/components/login/terms.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAShC;;;;;;;;GAQG;AACH,MAAM,WAAW,GAAG,CAClB,QAAgB,EAAE,KAAgC,EACrC,EAAE,CAAC,QAAQ;KACvB,KAAK,CAAC,mBAAmB,CAAC;KAC1B,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;IACnB,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAE9C,OAAO,KAAK,IAAI,IAAI;QAClB,CAAC,CAAC,yBAAmB,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,IAA/B,KAAK,CAAkC;QACpD,CAAC,CAAC,yBAAmB,IAAI,IAAZ,KAAK,CAAe,CAAA;AACrC,CAAC,CAAC,CAAA;AAEJ,MAAM,CAAC,MAAM,UAAU,GAAwB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE;IACjF,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,KAAa,EAAa,EAAE,CACtD,YAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,qBAAqB,EACtD,SAAS,EAAC,oDAAoD,YAAE,KAAK,GAAK,CAAA;IAE9E,6FAA6F;IAC7F,2FAA2F;IAC3F,2BAA2B;IAC3B,OAAO,eAAK,SAAS,EAAE,EAAE,CAAC,gDAAgD,EAAE,SAAS,CAAC,aACpF,iBAAO,SAAS,EAAC,oFAAoF,aAOnG,gBACE,IAAI,EAAC,UAAU,4BACf,SAAS,EAAC,uCAAuC,EACjD,OAAO,EAAE,KAAK,CAAC,QAAQ,kBACT,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,QAAQ,EAChD,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GACrD,EACF,yBACG,WAAW,CACV,SAAS,CAAC,uBAAuB,EAC/B,6DAA6D,CAAC,EAChE;4BACE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,mBAAmB,EAAE,oBAAoB,CAAC,CAAC;4BACnF,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,qBAAqB,EAAE,gBAAgB,CAAC,CAAC;4BACrF,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI;gCACjC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,qBAAqB,EAAE,eAAe,CAAC,CAAC;gCAC7E,CAAC,CAAC,IAAI;yBACT,CACF,GACI,IACD,EACP,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,YAAG,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,0BAA0B,YACxF,SAAS,CAAC,sBAAsB,EAC/B,2EAA2E,CAAC,GAC5E,IACA,CAAA;AACR,CAAC,CAAA"}
@@ -1,2 +1,2 @@
1
- export declare const modules: (import("@owlmeans/client-entrypoint").ClientEntrypoint<{}, import("@owlmeans/client-entrypoint").ClientRequest<{}>> | import("@owlmeans/entrypoint").CommonEntrypoint)[];
1
+ export declare const modules: any[];
2
2
  //# sourceMappingURL=modules.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,OAAO,2KAAuB,CAAA"}
1
+ {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,OAAO,OAAuB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/web-panel",
3
- "version": "0.1.18-rc.25",
3
+ "version": "0.1.18-rc.27",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
package/probe.d.mts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=probe.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.d.mts","sourceRoot":"","sources":["probe.mjs"],"names":[],"mappings":""}
package/probe2.d.mts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=probe2.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe2.d.mts","sourceRoot":"","sources":["probe2.mjs"],"names":[],"mappings":""}
@@ -6,6 +6,7 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/com
6
6
  import { cn } from '@/lib/utils'
7
7
  import { useI18nLib } from '@owlmeans/client-i18n'
8
8
  import { useLoginMethods } from '@owlmeans/client-panel/auth'
9
+ import { loginAttemptError } from '@owlmeans/client-auth/login'
9
10
  import type { LoginMethod, LoginScreenProps } from '@owlmeans/client-auth/login'
10
11
  import { LoginMethodIcon } from './icons.js'
11
12
  import { LoginTerms } from './terms.js'
@@ -37,8 +38,25 @@ export const LoginScreen: FC<LoginScreenProps> = props => {
37
38
  })
38
39
 
39
40
  const Logo = props.Logo
41
+ const attemptError = model.busy == null ? loginAttemptError(model.outcome) : null
40
42
 
41
- return <div className={cn('flex min-h-full w-full items-center justify-center p-4', props.className)}>
43
+ // The viewport height is set INLINE, and deliberately.
44
+ //
45
+ // A percentage minimum (`min-h-full`) resolves against a parent that has a height, and this
46
+ // screen is rendered straight out of the dispatcher into whatever the app has around it — which
47
+ // almost never carries one, so the box collapsed to its content and the card sat at the top of
48
+ // an empty page. The fix is a viewport unit; the reason it is not a utility class is that this
49
+ // package's classes reach a consuming app's stylesheet only through a `@source` scan of its
50
+ // `src`, and a class NEW to that stylesheet is the one thing the scan can be stale about. Every
51
+ // other utility here already exists in any app that renders anything; a missing `min-h-dvh`
52
+ // rule, by contrast, is invisible — the screen looks nearly right and is silently uncentred.
53
+ //
54
+ // `props.style` still wins, because a class-based override no longer can.
55
+ return <div
56
+ data-login-screen
57
+ style={{ minHeight: '100dvh', ...props.style }}
58
+ className={cn('flex w-full items-center justify-center p-4', props.className)}
59
+ >
42
60
  <Card className={cn('w-full max-w-sm', props.containerClassName)}>
43
61
  <CardHeader className="items-center gap-2 text-center">
44
62
  {Logo != null && <div className="flex justify-center pb-2">
@@ -68,7 +86,14 @@ export const LoginScreen: FC<LoginScreenProps> = props => {
68
86
  // why.
69
87
  aria-disabled={model.blocked}
70
88
  data-blocked={model.blocked ? 'true' : undefined}
71
- className={cn('w-full justify-center gap-2', model.blocked && 'opacity-60')}
89
+ // `cursor-pointer` explicitly: this screen renders through the CONSUMER's vendored
90
+ // `@/components/ui/button`, and an app whose shadcn copy predates the cursor rule
91
+ // shows an arrow over the one control on the page. Stating it here makes the screen
92
+ // behave the same whichever copy it lands on.
93
+ className={cn(
94
+ 'w-full justify-center gap-2 cursor-pointer',
95
+ model.blocked && 'opacity-60'
96
+ )}
72
97
  autoFocus={method.id === model.primary?.id}
73
98
  onClick={() => model.select(method)}
74
99
  >
@@ -81,9 +106,15 @@ export const LoginScreen: FC<LoginScreenProps> = props => {
81
106
 
82
107
  {model.terms.required && <LoginTerms model={model.terms} translate={t} />}
83
108
 
84
- {model.error != null && <p role="alert" className="text-sm text-destructive">
85
- {model.error}
86
- </p>}
109
+ {/*
110
+ A thrown message first, because it names the actual fault; otherwise whatever the
111
+ finished outcome means for someone still looking at this screen. An attempt that ends
112
+ without moving the document MUST say so — silence here reads as a dead button.
113
+ */}
114
+ {(model.error ?? attemptError) != null
115
+ && <p role="alert" data-login-error className="text-sm text-destructive text-center">
116
+ {model.error ?? t(attemptError as string, 'Sign-in did not complete. Please try again.')}
117
+ </p>}
87
118
  </CardContent>
88
119
 
89
120
  <div className="px-6">
@@ -34,8 +34,11 @@ export const LoginTerms: FC<LoginTermsProps> = ({ model, translate, className })
34
34
  <a href={href} target="_blank" rel="noreferrer noopener"
35
35
  className="underline underline-offset-2 hover:text-foreground">{label}</a>
36
36
 
37
- return <div className={cn('flex flex-col gap-1.5', className)}>
38
- <label className="flex items-start gap-2 text-sm text-muted-foreground cursor-pointer">
37
+ // Centred, like every other row in the card. The checkbox stays at the start of the sentence
38
+ // rather than above it, so `justify-center` centres the pair and `text-center` centres the
39
+ // wrapped lines within it.
40
+ return <div className={cn('flex flex-col items-center gap-1.5 text-center', className)}>
41
+ <label className="flex items-start justify-center gap-2 text-sm text-muted-foreground cursor-pointer">
39
42
  {/*
40
43
  A NATIVE checkbox, deliberately. `web-panel` ships no `checkbox` primitive, and requiring
41
44
  every consumer to vendor one plus its Radix peer to render a sign-in screen would be a
@@ -150,7 +150,13 @@ ensureLoginService(context as never).registerMethodSource({
150
150
  alias: 'harness',
151
151
  list: () => [
152
152
  method('primary', { emphasis: 'primary', icon: 'google', label: 'Continue with Google' }),
153
- method('secondary', { order: 20, label: 'Sign in with a key' }),
153
+ // A method that finishes without moving the document — the shape of a relying party that
154
+ // could not build an authorization URL. The screen must SAY so; silence reads as a dead
155
+ // button, which is exactly the bug this pins.
156
+ method('secondary', {
157
+ order: 20, label: 'Sign in with a key',
158
+ start: async () => { started.push('secondary'); return LoginOutcome.Passed },
159
+ }),
154
160
  method('operator', { order: 900, restricted: true, label: 'Secret key' }),
155
161
  ],
156
162
  })
@@ -44,7 +44,80 @@ describe('@owlmeans/web-panel — the sign-in screen', () => {
44
44
  const credit = await page.locator('[data-login-credit]').textContent()
45
45
  expect(credit).toContain('Powered by OwlMeans')
46
46
  expect(credit).toContain('Harness')
47
- expect(credit).toContain('Acme')
47
+ // A copyright NOTICE, not a name: the mark and the year are what make it one.
48
+ expect(credit).toContain(`© ${new Date().getFullYear()} Acme`)
49
+ } finally {
50
+ await close()
51
+ }
52
+ }, TIMEOUT)
53
+
54
+ test('centres the card in the viewport, rather than stacking it at the top', async () => {
55
+ const { page, close } = await open()
56
+ try {
57
+ const screen = await page.locator('[data-login-screen]').boundingBox()
58
+ const viewport = page.viewportSize()
59
+
60
+ // A percentage minimum height resolves against a parent that HAS a height, and the
61
+ // dispatcher renders this screen into a chain with none — so `min-h-full` collapsed the
62
+ // box to its content and left the card at the top of an otherwise empty page.
63
+ expect(screen?.height).toBeGreaterThanOrEqual((viewport?.height ?? 0) - 1)
64
+
65
+ // The height on its own is not the requirement — where the CARD ends up is. Asserted from
66
+ // the card's own centre so the test fails on a lost `items-center` too, not only on a lost
67
+ // height, and against the SCREEN box rather than the window because this harness mounts the
68
+ // screen inside the app shell, one header down. A dispatcher renders it as the whole page,
69
+ // where the two coincide.
70
+ const card = await page.locator('[data-login-screen] > *').first().boundingBox()
71
+ const cardCentre = (card?.y ?? 0) + (card?.height ?? 0) / 2
72
+ const screenCentre = (screen?.y ?? 0) + (screen?.height ?? 0) / 2
73
+
74
+ expect(Math.abs(cardCentre - screenCentre)).toBeLessThanOrEqual(2)
75
+ } finally {
76
+ await close()
77
+ }
78
+ }, TIMEOUT)
79
+
80
+ test('an attempt that goes nowhere SAYS so, instead of reading as a dead button', async () => {
81
+ const { page, close } = await open()
82
+ try {
83
+ await page.locator('[data-login-terms]').check()
84
+ expect(await page.locator('[data-login-error]').count()).toBe(0)
85
+
86
+ // `secondary` finishes as `Passed` — it ran, and the document did not move. That is a valid
87
+ // answer to a dispatcher, which has a continuation, and a dead end on a screen.
88
+ await page.locator('[data-login-method="secondary"]').click()
89
+ await page.waitForSelector('[data-login-error]', { timeout: 10_000 })
90
+
91
+ expect(await page.evaluate(() => (window as never as { __loginStarted: string[] }).__loginStarted))
92
+ .toContain('secondary')
93
+ expect(await page.locator('[data-login-error]').textContent()).toBeTruthy()
94
+ } finally {
95
+ await close()
96
+ }
97
+ }, TIMEOUT)
98
+
99
+ test('offers the methods with a pointer cursor', async () => {
100
+ const { page, close } = await open()
101
+ try {
102
+ // The screen renders through the CONSUMER's vendored button, and an older shadcn copy has
103
+ // no cursor rule — so the one control on the page showed an arrow.
104
+ const cursor = await page.locator('[data-login-method]').first()
105
+ .evaluate(node => window.getComputedStyle(node).cursor)
106
+
107
+ expect(cursor).toBe('pointer')
108
+ } finally {
109
+ await close()
110
+ }
111
+ }, TIMEOUT)
112
+
113
+ test('centres the terms confirmation, like every other row in the card', async () => {
114
+ const { page, close } = await open()
115
+ try {
116
+ const align = await page.locator('[data-login-terms]').evaluate(
117
+ node => window.getComputedStyle(node.closest('label') as Element).textAlign
118
+ )
119
+
120
+ expect(align).toBe('center')
48
121
  } finally {
49
122
  await close()
50
123
  }
package/build/.gitkeep DELETED
File without changes