@owlmeans/web-panel 0.1.18-rc.26 → 0.1.18-rc.28
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.
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/web-panel/SKILL.md +1 -1
- package/build/components/login/screen.d.ts.map +1 -1
- package/build/components/login/screen.js +10 -2
- package/build/components/login/screen.js.map +1 -1
- package/package.json +2 -2
- package/src/components/login/screen.tsx +19 -4
- package/tests/harness/mount.tsx +7 -1
- package/tests/login.spec.ts +33 -0
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/web-panel",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-01T12:
|
|
4
|
+
"version": "0.1.18-rc.28",
|
|
5
|
+
"generatedAt": "2026-09-01T12:37:10.146Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -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;
|
|
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,6 +34,7 @@ export const LoginScreen = props => {
|
|
|
33
34
|
...(props.methods != null ? { methods: props.methods } : {}),
|
|
34
35
|
});
|
|
35
36
|
const Logo = props.Logo;
|
|
37
|
+
const attemptError = model.busy == null ? loginAttemptError(model.outcome) : null;
|
|
36
38
|
// The viewport height is set INLINE, and deliberately.
|
|
37
39
|
//
|
|
38
40
|
// A percentage minimum (`min-h-full`) resolves against a parent that has a height, and this
|
|
@@ -48,9 +50,15 @@ export const LoginScreen = props => {
|
|
|
48
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'
|
|
49
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
|
|
50
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.') })
|
|
51
|
-
: _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,
|
|
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
|
|
52
59
|
? _jsx(Loader2, { className: "size-4 animate-spin" })
|
|
53
|
-
: _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 }),
|
|
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 }) })] }) });
|
|
54
62
|
};
|
|
55
63
|
/**
|
|
56
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":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAC/C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAChG,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"screen.js","sourceRoot":"","sources":["../../../src/components/login/screen.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/web-panel",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.28",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@owlmeans/auth-common": "^0.1.18-rc.9",
|
|
50
50
|
"@owlmeans/basic-envelope": "^0.1.18-rc.9",
|
|
51
51
|
"@owlmeans/client": "^0.1.18-rc.11",
|
|
52
|
-
"@owlmeans/client-auth": "^0.1.18-rc.
|
|
52
|
+
"@owlmeans/client-auth": "^0.1.18-rc.15",
|
|
53
53
|
"@owlmeans/client-config": "^0.1.18-rc.8",
|
|
54
54
|
"@owlmeans/client-context": "^0.1.18-rc.9",
|
|
55
55
|
"@owlmeans/client-entrypoint": "^0.1.18-rc.9",
|
|
@@ -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,6 +38,7 @@ 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
43
|
// The viewport height is set INLINE, and deliberately.
|
|
42
44
|
//
|
|
@@ -84,7 +86,14 @@ export const LoginScreen: FC<LoginScreenProps> = props => {
|
|
|
84
86
|
// why.
|
|
85
87
|
aria-disabled={model.blocked}
|
|
86
88
|
data-blocked={model.blocked ? 'true' : undefined}
|
|
87
|
-
|
|
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
|
+
)}
|
|
88
97
|
autoFocus={method.id === model.primary?.id}
|
|
89
98
|
onClick={() => model.select(method)}
|
|
90
99
|
>
|
|
@@ -97,9 +106,15 @@ export const LoginScreen: FC<LoginScreenProps> = props => {
|
|
|
97
106
|
|
|
98
107
|
{model.terms.required && <LoginTerms model={model.terms} translate={t} />}
|
|
99
108
|
|
|
100
|
-
{
|
|
101
|
-
|
|
102
|
-
|
|
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>}
|
|
103
118
|
</CardContent>
|
|
104
119
|
|
|
105
120
|
<div className="px-6">
|
package/tests/harness/mount.tsx
CHANGED
|
@@ -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
|
|
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
|
})
|
package/tests/login.spec.ts
CHANGED
|
@@ -77,6 +77,39 @@ describe('@owlmeans/web-panel — the sign-in screen', () => {
|
|
|
77
77
|
}
|
|
78
78
|
}, TIMEOUT)
|
|
79
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
|
+
|
|
80
113
|
test('centres the terms confirmation, like every other row in the card', async () => {
|
|
81
114
|
const { page, close } = await open()
|
|
82
115
|
try {
|