@owlmeans/client-panel 0.1.18-rc.15 → 0.1.18-rc.17
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/client-panel/SKILL.md +1 -1
- package/build/auth/index.d.ts +1 -0
- package/build/auth/index.d.ts.map +1 -1
- package/build/auth/index.js +1 -0
- package/build/auth/index.js.map +1 -1
- package/build/auth/login/index.d.ts +3 -0
- package/build/auth/login/index.d.ts.map +1 -0
- package/build/auth/login/index.js +2 -0
- package/build/auth/login/index.js.map +1 -0
- package/build/auth/login/model.d.ts +10 -0
- package/build/auth/login/model.d.ts.map +1 -0
- package/build/auth/login/model.js +84 -0
- package/build/auth/login/model.js.map +1 -0
- package/build/auth/login/types.d.ts +46 -0
- package/build/auth/login/types.d.ts.map +1 -0
- package/build/auth/login/types.js +2 -0
- package/build/auth/login/types.js.map +1 -0
- package/package.json +11 -10
- package/src/auth/index.ts +1 -1
- package/src/auth/login/index.ts +2 -0
- package/src/auth/login/model.ts +101 -0
- package/src/auth/login/types.ts +45 -0
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/client-panel",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-08-27T21:
|
|
4
|
+
"version": "0.1.18-rc.15",
|
|
5
|
+
"generatedAt": "2026-08-27T21:18:31.381Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
package/build/auth/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,kBAAkB,CAAA"}
|
package/build/auth/index.js
CHANGED
package/build/auth/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,kBAAkB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/login/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auth/login/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LoginMethodsModel, UseLoginMethodsOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Everything a sign-in screen needs, with no opinion about how it looks.
|
|
4
|
+
*
|
|
5
|
+
* Headless and cross-platform, like `usePanelNav`: the rules about what may be offered, what must
|
|
6
|
+
* be confirmed first and what the credit line says are the same on every platform, and only the
|
|
7
|
+
* rendering differs. A renderer that re-derives any of them will drift from the one that does not.
|
|
8
|
+
*/
|
|
9
|
+
export declare const useLoginMethods: (opts?: UseLoginMethodsOptions) => LoginMethodsModel;
|
|
10
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/auth/login/model.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAE3E;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,UAAW,sBAAsB,KAAG,iBAmF/D,CAAA"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { useContext, useNavigate } from '@owlmeans/client';
|
|
3
|
+
import { acceptTerms, primaryLoginMethod, resolveCredit, resolveTerms, termsAccepted, LoginOutcome, LOGIN_SERVICE, } from '@owlmeans/client-auth/login';
|
|
4
|
+
/**
|
|
5
|
+
* Everything a sign-in screen needs, with no opinion about how it looks.
|
|
6
|
+
*
|
|
7
|
+
* Headless and cross-platform, like `usePanelNav`: the rules about what may be offered, what must
|
|
8
|
+
* be confirmed first and what the credit line says are the same on every platform, and only the
|
|
9
|
+
* rendering differs. A renderer that re-derives any of them will drift from the one that does not.
|
|
10
|
+
*/
|
|
11
|
+
export const useLoginMethods = (opts) => {
|
|
12
|
+
const context = useContext();
|
|
13
|
+
const nav = useNavigate();
|
|
14
|
+
const login = context.service(LOGIN_SERVICE);
|
|
15
|
+
const cfg = opts?.config ?? context.cfg.security?.auth?.login;
|
|
16
|
+
const brand = context.cfg.brand;
|
|
17
|
+
const resolved = useMemo(() => resolveTerms(opts?.terms ?? cfg?.terms), [opts?.terms, cfg]);
|
|
18
|
+
const credit = useMemo(() => resolveCredit(cfg?.credit, brand, context.cfg.service), [cfg, brand, context]);
|
|
19
|
+
const [accepted, setAccepted] = useState(() => termsAccepted(resolved));
|
|
20
|
+
const [attempted, setAttempted] = useState(false);
|
|
21
|
+
const [busy, setBusy] = useState(null);
|
|
22
|
+
const [outcome, setOutcome] = useState(null);
|
|
23
|
+
const [error, setError] = useState(null);
|
|
24
|
+
const env = login.env();
|
|
25
|
+
const all = login.methods({
|
|
26
|
+
context, env,
|
|
27
|
+
navigate: (alias, params) => { nav.go(alias, params != null ? { params } : undefined); },
|
|
28
|
+
});
|
|
29
|
+
const methods = typeof opts?.methods === 'function'
|
|
30
|
+
? opts.methods(all)
|
|
31
|
+
: opts?.methods ?? all;
|
|
32
|
+
const blocked = resolved != null && resolved.required && !accepted;
|
|
33
|
+
const select = useCallback((method) => {
|
|
34
|
+
if (blocked) {
|
|
35
|
+
// Nothing starts, and no window opens. The screen renders the reason instead — which is why
|
|
36
|
+
// a control must never be `disabled`: a disabled control swallows the click and says nothing.
|
|
37
|
+
setAttempted(true);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
setBusy(method.id);
|
|
41
|
+
setError(null);
|
|
42
|
+
void method.start({
|
|
43
|
+
context, env,
|
|
44
|
+
navigate: (alias, params) => { nav.go(alias, params != null ? { params } : undefined); },
|
|
45
|
+
}).then(result => {
|
|
46
|
+
setOutcome(result);
|
|
47
|
+
// `Redirected` means the browser is leaving; keeping the control busy is honest there.
|
|
48
|
+
if (result !== LoginOutcome.Redirected) {
|
|
49
|
+
setBusy(null);
|
|
50
|
+
}
|
|
51
|
+
}).catch((e) => {
|
|
52
|
+
setError(e.message);
|
|
53
|
+
setOutcome(LoginOutcome.Failed);
|
|
54
|
+
setBusy(null);
|
|
55
|
+
});
|
|
56
|
+
}, [blocked, context, env, nav]);
|
|
57
|
+
const accept = useCallback((value) => {
|
|
58
|
+
setAccepted(value);
|
|
59
|
+
setAttempted(false);
|
|
60
|
+
acceptTerms(resolved, value);
|
|
61
|
+
}, [resolved]);
|
|
62
|
+
return {
|
|
63
|
+
methods,
|
|
64
|
+
primary: primaryLoginMethod(methods),
|
|
65
|
+
terms: {
|
|
66
|
+
required: resolved?.required ?? false,
|
|
67
|
+
accepted: resolved == null ? true : accepted,
|
|
68
|
+
attempted,
|
|
69
|
+
urls: {
|
|
70
|
+
terms: resolved?.terms ?? '',
|
|
71
|
+
privacy: resolved?.privacy ?? '',
|
|
72
|
+
...(resolved?.cookies != null ? { cookies: resolved.cookies } : {}),
|
|
73
|
+
},
|
|
74
|
+
accept,
|
|
75
|
+
},
|
|
76
|
+
credit,
|
|
77
|
+
blocked,
|
|
78
|
+
busy,
|
|
79
|
+
outcome,
|
|
80
|
+
error,
|
|
81
|
+
select,
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/auth/login/model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { LoginMethod, LoginOutcome } from '@owlmeans/client-auth/login';
|
|
2
|
+
import type { LoginScreenConfig, LoginTermsConfig } from '@owlmeans/config';
|
|
3
|
+
export interface UseLoginMethodsOptions {
|
|
4
|
+
/** Overrides the configuration the context carries. */
|
|
5
|
+
config?: LoginScreenConfig;
|
|
6
|
+
terms?: LoginTermsConfig | false;
|
|
7
|
+
/** Replace or reorder what the resolver produced. */
|
|
8
|
+
methods?: LoginMethod[] | ((methods: LoginMethod[]) => LoginMethod[]);
|
|
9
|
+
}
|
|
10
|
+
export interface LoginTermsModel {
|
|
11
|
+
required: boolean;
|
|
12
|
+
accepted: boolean;
|
|
13
|
+
/** A blocked selection was attempted — render the explanation. */
|
|
14
|
+
attempted: boolean;
|
|
15
|
+
urls: {
|
|
16
|
+
terms: string;
|
|
17
|
+
privacy: string;
|
|
18
|
+
cookies?: string;
|
|
19
|
+
};
|
|
20
|
+
accept: (value: boolean) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface LoginCreditModel {
|
|
23
|
+
poweredBy: boolean;
|
|
24
|
+
line: string | null;
|
|
25
|
+
}
|
|
26
|
+
export interface LoginMethodsModel {
|
|
27
|
+
methods: LoginMethod[];
|
|
28
|
+
/** The one a screen highlights and focuses. It is never started automatically. */
|
|
29
|
+
primary: LoginMethod | null;
|
|
30
|
+
terms: LoginTermsModel;
|
|
31
|
+
credit: LoginCreditModel;
|
|
32
|
+
/** True while the terms have not been confirmed and confirming them is required. */
|
|
33
|
+
blocked: boolean;
|
|
34
|
+
/** Id of the method currently starting. */
|
|
35
|
+
busy: string | null;
|
|
36
|
+
outcome: LoginOutcome | null;
|
|
37
|
+
error: string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Start a method.
|
|
40
|
+
*
|
|
41
|
+
* NOT async, and it awaits nothing before delegating — the flow may need to open a window, and a
|
|
42
|
+
* window opened after the click has finished being handled is eaten by the popup blocker.
|
|
43
|
+
*/
|
|
44
|
+
select: (method: LoginMethod) => void;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/auth/login/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC5E,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAE3E,MAAM,WAAW,sBAAsB;IACrC,uDAAuD;IACvD,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,KAAK,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAA;IAChC,qDAAqD;IACrD,OAAO,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,CAAC,CAAA;CACtE;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;IACjB,kEAAkE;IAClE,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1D,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,WAAW,EAAE,CAAA;IACtB,kFAAkF;IAClF,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;IAC3B,KAAK,EAAE,eAAe,CAAA;IACtB,MAAM,EAAE,gBAAgB,CAAA;IACxB,oFAAoF;IACpF,OAAO,EAAE,OAAO,CAAA;IAChB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;;OAKG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAA;CACtC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/auth/login/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/client-panel",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.17",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -36,16 +36,17 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@hookform/resolvers": "^5.2.2",
|
|
39
|
-
"@owlmeans/client": "^0.1.18-rc.
|
|
40
|
-
"@owlmeans/client-i18n": "^0.1.18-rc.
|
|
41
|
-
"@owlmeans/client-entrypoint": "^0.1.18-rc.
|
|
42
|
-
"@owlmeans/client-route": "^0.1.18-rc.
|
|
39
|
+
"@owlmeans/client": "^0.1.18-rc.11",
|
|
40
|
+
"@owlmeans/client-i18n": "^0.1.18-rc.10",
|
|
41
|
+
"@owlmeans/client-entrypoint": "^0.1.18-rc.9",
|
|
42
|
+
"@owlmeans/client-route": "^0.1.18-rc.9",
|
|
43
43
|
"@owlmeans/error": "^0.1.18-rc.6",
|
|
44
|
-
"@owlmeans/entrypoint": "^0.1.18-rc.
|
|
44
|
+
"@owlmeans/entrypoint": "^0.1.18-rc.8"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@owlmeans/auth": "^0.1.18-rc.
|
|
48
|
-
"@owlmeans/client-auth": "^0.1.18-rc.
|
|
47
|
+
"@owlmeans/auth": "^0.1.18-rc.7",
|
|
48
|
+
"@owlmeans/client-auth": "^0.1.18-rc.13",
|
|
49
|
+
"@owlmeans/config": "^0.1.18-rc.8",
|
|
49
50
|
"@owlmeans/i18n": "^0.1.18-rc.6",
|
|
50
51
|
"@owlmeans/router": "^0.1.18-rc.6",
|
|
51
52
|
"ajv": "*",
|
|
@@ -62,8 +63,8 @@
|
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
65
|
"@owlmeans/dep-config": "workspace:*",
|
|
65
|
-
"@owlmeans/auth": "^0.1.18-rc.
|
|
66
|
-
"@owlmeans/client-auth": "^0.1.18-rc.
|
|
66
|
+
"@owlmeans/auth": "^0.1.18-rc.7",
|
|
67
|
+
"@owlmeans/client-auth": "^0.1.18-rc.13",
|
|
67
68
|
"@types/react": "^19.2.17",
|
|
68
69
|
"nodemon": "^3.1.14",
|
|
69
70
|
"react-hook-form": "^7.53.0",
|
package/src/auth/index.ts
CHANGED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react'
|
|
2
|
+
import { useContext, useNavigate } from '@owlmeans/client'
|
|
3
|
+
import type { CommonConfig } from '@owlmeans/config'
|
|
4
|
+
import {
|
|
5
|
+
acceptTerms, primaryLoginMethod, resolveCredit, resolveTerms, termsAccepted,
|
|
6
|
+
LoginOutcome, LOGIN_SERVICE,
|
|
7
|
+
} from '@owlmeans/client-auth/login'
|
|
8
|
+
import type { LoginContext, LoginMethod, LoginService } from '@owlmeans/client-auth/login'
|
|
9
|
+
import type { LoginMethodsModel, UseLoginMethodsOptions } from './types.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Everything a sign-in screen needs, with no opinion about how it looks.
|
|
13
|
+
*
|
|
14
|
+
* Headless and cross-platform, like `usePanelNav`: the rules about what may be offered, what must
|
|
15
|
+
* be confirmed first and what the credit line says are the same on every platform, and only the
|
|
16
|
+
* rendering differs. A renderer that re-derives any of them will drift from the one that does not.
|
|
17
|
+
*/
|
|
18
|
+
export const useLoginMethods = (opts?: UseLoginMethodsOptions): LoginMethodsModel => {
|
|
19
|
+
const context = useContext() as unknown as LoginContext
|
|
20
|
+
const nav = useNavigate()
|
|
21
|
+
const login = context.service<LoginService>(LOGIN_SERVICE)
|
|
22
|
+
|
|
23
|
+
const cfg = opts?.config ?? (context.cfg as CommonConfig).security?.auth?.login
|
|
24
|
+
const brand = (context.cfg as CommonConfig).brand
|
|
25
|
+
|
|
26
|
+
const resolved = useMemo(() => resolveTerms(opts?.terms ?? cfg?.terms), [opts?.terms, cfg])
|
|
27
|
+
const credit = useMemo(
|
|
28
|
+
() => resolveCredit(cfg?.credit, brand, context.cfg.service), [cfg, brand, context]
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const [accepted, setAccepted] = useState(() => termsAccepted(resolved))
|
|
32
|
+
const [attempted, setAttempted] = useState(false)
|
|
33
|
+
const [busy, setBusy] = useState<string | null>(null)
|
|
34
|
+
const [outcome, setOutcome] = useState<LoginOutcome | null>(null)
|
|
35
|
+
const [error, setError] = useState<string | null>(null)
|
|
36
|
+
|
|
37
|
+
const env = login.env()
|
|
38
|
+
const all = login.methods({
|
|
39
|
+
context, env,
|
|
40
|
+
navigate: (alias, params) => { nav.go(alias, params != null ? { params } : undefined) },
|
|
41
|
+
})
|
|
42
|
+
const methods = typeof opts?.methods === 'function'
|
|
43
|
+
? opts.methods(all)
|
|
44
|
+
: opts?.methods ?? all
|
|
45
|
+
|
|
46
|
+
const blocked = resolved != null && resolved.required && !accepted
|
|
47
|
+
|
|
48
|
+
const select = useCallback((method: LoginMethod) => {
|
|
49
|
+
if (blocked) {
|
|
50
|
+
// Nothing starts, and no window opens. The screen renders the reason instead — which is why
|
|
51
|
+
// a control must never be `disabled`: a disabled control swallows the click and says nothing.
|
|
52
|
+
setAttempted(true)
|
|
53
|
+
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
setBusy(method.id)
|
|
57
|
+
setError(null)
|
|
58
|
+
void method.start({
|
|
59
|
+
context, env,
|
|
60
|
+
navigate: (alias, params) => { nav.go(alias, params != null ? { params } : undefined) },
|
|
61
|
+
}).then(result => {
|
|
62
|
+
setOutcome(result)
|
|
63
|
+
// `Redirected` means the browser is leaving; keeping the control busy is honest there.
|
|
64
|
+
if (result !== LoginOutcome.Redirected) {
|
|
65
|
+
setBusy(null)
|
|
66
|
+
}
|
|
67
|
+
}).catch((e: Error) => {
|
|
68
|
+
setError(e.message)
|
|
69
|
+
setOutcome(LoginOutcome.Failed)
|
|
70
|
+
setBusy(null)
|
|
71
|
+
})
|
|
72
|
+
}, [blocked, context, env, nav])
|
|
73
|
+
|
|
74
|
+
const accept = useCallback((value: boolean) => {
|
|
75
|
+
setAccepted(value)
|
|
76
|
+
setAttempted(false)
|
|
77
|
+
acceptTerms(resolved, value)
|
|
78
|
+
}, [resolved])
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
methods,
|
|
82
|
+
primary: primaryLoginMethod(methods),
|
|
83
|
+
terms: {
|
|
84
|
+
required: resolved?.required ?? false,
|
|
85
|
+
accepted: resolved == null ? true : accepted,
|
|
86
|
+
attempted,
|
|
87
|
+
urls: {
|
|
88
|
+
terms: resolved?.terms ?? '',
|
|
89
|
+
privacy: resolved?.privacy ?? '',
|
|
90
|
+
...(resolved?.cookies != null ? { cookies: resolved.cookies } : {}),
|
|
91
|
+
},
|
|
92
|
+
accept,
|
|
93
|
+
},
|
|
94
|
+
credit,
|
|
95
|
+
blocked,
|
|
96
|
+
busy,
|
|
97
|
+
outcome,
|
|
98
|
+
error,
|
|
99
|
+
select,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { LoginMethod, LoginOutcome } from '@owlmeans/client-auth/login'
|
|
2
|
+
import type { LoginScreenConfig, LoginTermsConfig } from '@owlmeans/config'
|
|
3
|
+
|
|
4
|
+
export interface UseLoginMethodsOptions {
|
|
5
|
+
/** Overrides the configuration the context carries. */
|
|
6
|
+
config?: LoginScreenConfig
|
|
7
|
+
terms?: LoginTermsConfig | false
|
|
8
|
+
/** Replace or reorder what the resolver produced. */
|
|
9
|
+
methods?: LoginMethod[] | ((methods: LoginMethod[]) => LoginMethod[])
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LoginTermsModel {
|
|
13
|
+
required: boolean
|
|
14
|
+
accepted: boolean
|
|
15
|
+
/** A blocked selection was attempted — render the explanation. */
|
|
16
|
+
attempted: boolean
|
|
17
|
+
urls: { terms: string, privacy: string, cookies?: string }
|
|
18
|
+
accept: (value: boolean) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LoginCreditModel {
|
|
22
|
+
poweredBy: boolean
|
|
23
|
+
line: string | null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LoginMethodsModel {
|
|
27
|
+
methods: LoginMethod[]
|
|
28
|
+
/** The one a screen highlights and focuses. It is never started automatically. */
|
|
29
|
+
primary: LoginMethod | null
|
|
30
|
+
terms: LoginTermsModel
|
|
31
|
+
credit: LoginCreditModel
|
|
32
|
+
/** True while the terms have not been confirmed and confirming them is required. */
|
|
33
|
+
blocked: boolean
|
|
34
|
+
/** Id of the method currently starting. */
|
|
35
|
+
busy: string | null
|
|
36
|
+
outcome: LoginOutcome | null
|
|
37
|
+
error: string | null
|
|
38
|
+
/**
|
|
39
|
+
* Start a method.
|
|
40
|
+
*
|
|
41
|
+
* NOT async, and it awaits nothing before delegating — the flow may need to open a window, and a
|
|
42
|
+
* window opened after the click has finished being handled is eaten by the popup blocker.
|
|
43
|
+
*/
|
|
44
|
+
select: (method: LoginMethod) => void
|
|
45
|
+
}
|