@owlmeans/client-panel 0.1.18-rc.13 → 0.1.18-rc.16

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/client-panel",
4
- "version": "0.1.18-rc.12",
5
- "generatedAt": "2026-08-25T21:16:30.725Z",
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
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/client-panel
9
9
 
10
10
  **Layer:** Client
11
- **Install:** `"@owlmeans/client-panel": "^0.1.18-rc.12"` in `dependencies`
11
+ **Install:** `"@owlmeans/client-panel": "^0.1.18-rc.15"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
@@ -1,2 +1,3 @@
1
1
  export * from './i18n.js';
2
+ export * from './login/index.js';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,cAAc,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,kBAAkB,CAAA"}
@@ -1,2 +1,3 @@
1
1
  export * from './i18n.js';
2
+ export * from './login/index.js';
2
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,cAAc,WAAW,CAAA"}
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,3 @@
1
+ export type * from './types.js';
2
+ export * from './model.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,2 @@
1
+ export * from './model.js';
2
+ //# sourceMappingURL=index.js.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -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.13",
3
+ "version": "0.1.18-rc.16",
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.9",
40
- "@owlmeans/client-i18n": "^0.1.18-rc.8",
41
- "@owlmeans/client-entrypoint": "^0.1.18-rc.8",
42
- "@owlmeans/client-route": "^0.1.18-rc.8",
39
+ "@owlmeans/client": "^0.1.18-rc.10",
40
+ "@owlmeans/client-i18n": "^0.1.18-rc.9",
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.7"
44
+ "@owlmeans/entrypoint": "^0.1.18-rc.8"
45
45
  },
46
46
  "peerDependencies": {
47
- "@owlmeans/auth": "^0.1.18-rc.6",
48
- "@owlmeans/client-auth": "^0.1.18-rc.10",
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.6",
66
- "@owlmeans/client-auth": "^0.1.18-rc.10",
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
@@ -1,2 +1,2 @@
1
-
2
1
  export * from './i18n.js'
2
+ export * from './login/index.js'
@@ -0,0 +1,2 @@
1
+ export type * from './types.js'
2
+ export * from './model.js'
@@ -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
+ }