@owlmeans/client-panel 0.1.18-rc.4 → 0.1.18-rc.41

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 (60) hide show
  1. package/README.md +63 -4
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/client-panel/SKILL.md +118 -11
  4. package/build/auth/i18n/fr.json +56 -0
  5. package/build/auth/i18n.js +2 -0
  6. package/build/auth/i18n.js.map +1 -1
  7. package/build/auth/index.d.ts +1 -0
  8. package/build/auth/index.d.ts.map +1 -1
  9. package/build/auth/index.js +1 -0
  10. package/build/auth/index.js.map +1 -1
  11. package/build/auth/login/index.d.ts +3 -0
  12. package/build/auth/login/index.d.ts.map +1 -0
  13. package/build/auth/login/index.js +2 -0
  14. package/build/auth/login/index.js.map +1 -0
  15. package/build/auth/login/model.d.ts +10 -0
  16. package/build/auth/login/model.d.ts.map +1 -0
  17. package/build/auth/login/model.js +84 -0
  18. package/build/auth/login/model.js.map +1 -0
  19. package/build/auth/login/types.d.ts +46 -0
  20. package/build/auth/login/types.d.ts.map +1 -0
  21. package/build/auth/login/types.js +2 -0
  22. package/build/auth/login/types.js.map +1 -0
  23. package/build/components/index.d.ts +1 -0
  24. package/build/components/index.d.ts.map +1 -1
  25. package/build/components/index.js +1 -0
  26. package/build/components/index.js.map +1 -1
  27. package/build/components/nav/helper.d.ts +21 -0
  28. package/build/components/nav/helper.d.ts.map +1 -0
  29. package/build/components/nav/helper.js +111 -0
  30. package/build/components/nav/helper.js.map +1 -0
  31. package/build/components/nav/index.d.ts +3 -0
  32. package/build/components/nav/index.d.ts.map +1 -0
  33. package/build/components/nav/index.js +2 -0
  34. package/build/components/nav/index.js.map +1 -0
  35. package/build/components/nav/types.d.ts +76 -0
  36. package/build/components/nav/types.d.ts.map +1 -0
  37. package/build/components/nav/types.js +2 -0
  38. package/build/components/nav/types.js.map +1 -0
  39. package/build/i18n.d.ts +2 -0
  40. package/build/i18n.d.ts.map +1 -0
  41. package/build/i18n.js +15 -0
  42. package/build/i18n.js.map +1 -0
  43. package/build/index.d.ts +1 -0
  44. package/build/index.d.ts.map +1 -1
  45. package/build/index.js +1 -0
  46. package/build/index.js.map +1 -1
  47. package/package.json +14 -13
  48. package/src/auth/i18n/fr.json +56 -0
  49. package/src/auth/i18n.ts +2 -0
  50. package/src/auth/index.ts +1 -1
  51. package/src/auth/login/index.ts +2 -0
  52. package/src/auth/login/model.ts +101 -0
  53. package/src/auth/login/types.ts +45 -0
  54. package/src/components/index.ts +1 -0
  55. package/src/components/nav/helper.ts +136 -0
  56. package/src/components/nav/index.ts +3 -0
  57. package/src/components/nav/types.ts +79 -0
  58. package/src/i18n.ts +16 -0
  59. package/src/index.ts +2 -0
  60. package/build/.gitkeep +0 -0
@@ -0,0 +1,111 @@
1
+ import { useMemo } from 'react';
2
+ import { useContext, useNavigate } from '@owlmeans/client';
3
+ /** Default resolver: no i18n, the caller's fallback wins. See {@link NavTranslate}. */
4
+ export const defaultNavTranslate = (_key, defaultValue) => defaultValue;
5
+ /**
6
+ * Turn an alias into something readable — `my-app:web:user-list` becomes `User list`.
7
+ * The last segment is the meaningful one; the prefixes address the app, not the screen.
8
+ */
9
+ export const defaultNavLabel = (alias) => {
10
+ const segment = alias.split(/[:.]/).filter(part => part !== '').pop() ?? alias;
11
+ const words = segment.replace(/[-_]+/g, ' ').trim();
12
+ return words.charAt(0).toUpperCase() + words.slice(1);
13
+ };
14
+ export const resolveNavLabel = (translate, label, key, alias) => label ?? translate(key, defaultNavLabel(alias ?? key));
15
+ const visibleItems = (section) => section.items.filter(item => item.hidden !== true);
16
+ const normalizePath = (path) => path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path;
17
+ /**
18
+ * The navigation model behind both menus.
19
+ *
20
+ * Resolving the CURRENT screen has two sources, and both are needed. The router carries the
21
+ * alias in `location.state`, which is authoritative — but `state` is `window.history.state`,
22
+ * so it is null until the first in-app navigation: on a hard page load or a deep link there
23
+ * is nothing there, and a menu keyed on it alone highlights nothing on exactly the entry that
24
+ * matters most. The pathname is the fallback: an entrypoint computes its path from its own
25
+ * declaration and its ancestors', so matching them is a lookup, not a guess.
26
+ */
27
+ export const usePanelNav = (config) => {
28
+ const context = useContext();
29
+ const nav = useNavigate();
30
+ const location = context.router().useLocation();
31
+ const state = location.state;
32
+ const pathname = location.pathname;
33
+ return useMemo(() => {
34
+ const sections = config.sections
35
+ .filter(section => section.hidden !== true)
36
+ .map(section => ({ ...section, items: visibleItems(section) }));
37
+ const pathOf = (alias) => {
38
+ try {
39
+ return normalizePath(context.entrypoint(alias).path());
40
+ }
41
+ catch {
42
+ // A protocol the app never bound addresses nothing — it cannot be the current screen,
43
+ // and it must not take the menu down with it.
44
+ return null;
45
+ }
46
+ };
47
+ const all = sections.flatMap(section => section.items);
48
+ let current = state?.alias ?? null;
49
+ if (current == null) {
50
+ const here = normalizePath(pathname);
51
+ const exact = all.find(item => pathOf(item.alias) === here);
52
+ if (exact != null) {
53
+ current = exact.alias;
54
+ }
55
+ else {
56
+ // Longest prefix: a detail screen under a listed one still belongs to its section.
57
+ const prefixed = all
58
+ .map(item => ({ item, path: pathOf(item.alias) }))
59
+ .filter((entry) => entry.path != null && entry.path !== '/' && here.startsWith(`${entry.path}/`))
60
+ .sort((a, b) => b.path.length - a.path.length)[0];
61
+ current = prefixed?.item.alias ?? null;
62
+ }
63
+ }
64
+ const sectionOf = (alias) => alias == null ? null : sections.find(section => section.items.some(item => item.alias === alias)) ?? null;
65
+ let active = sectionOf(current);
66
+ if (active == null && current != null) {
67
+ // The current screen is not listed anywhere — walk up to the ancestor that is, so a
68
+ // nested screen keeps its section (and its side menu) rather than clearing the chrome.
69
+ let alias = current;
70
+ const seen = new Set();
71
+ while (alias != null && !seen.has(alias)) {
72
+ seen.add(alias);
73
+ try {
74
+ alias = context.entrypoint(alias).route.route.parent ?? null;
75
+ }
76
+ catch {
77
+ alias = null;
78
+ }
79
+ const found = sectionOf(alias);
80
+ if (found != null) {
81
+ active = found;
82
+ break;
83
+ }
84
+ }
85
+ }
86
+ const isItemActive = (item) => item.alias === current;
87
+ const isSectionActive = (section) => active != null && section.name === active.name;
88
+ return {
89
+ sections,
90
+ current,
91
+ active,
92
+ showSide: active != null && active.items.length > 1,
93
+ isSectionActive,
94
+ isItemActive,
95
+ goSection: section => {
96
+ const first = section.items[0];
97
+ return first != null ? nav.press(first.alias) : () => { };
98
+ },
99
+ goItem: item => nav.press(item.alias),
100
+ hrefOf: target => {
101
+ const alias = 'alias' in target ? target.alias : target.items[0]?.alias;
102
+ if (alias == null) {
103
+ return undefined;
104
+ }
105
+ const path = pathOf(alias);
106
+ return path == null || path.includes(':') ? undefined : path;
107
+ },
108
+ };
109
+ }, [config, pathname, state?.alias, context, nav]);
110
+ };
111
+ //# sourceMappingURL=helper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../../../src/components/nav/helper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export type * from './types.js';
2
+ export * from './helper.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/nav/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAE/B,cAAc,aAAa,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from './helper.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/nav/index.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAA"}
@@ -0,0 +1,76 @@
1
+ import type { ComponentType } from 'react';
2
+ /**
3
+ * One screen in the navigation — the second menu level.
4
+ *
5
+ * `alias` addresses a frontend entrypoint, so an item never carries a URL: the router
6
+ * resolves it, and a path that changes shape stays correct everywhere it is rendered.
7
+ */
8
+ export interface PanelNavItem {
9
+ alias: string;
10
+ /** Literal label. Absent, the label is resolved through `translate` and falls back to a humanized alias. */
11
+ label?: string;
12
+ Icon?: ComponentType<{
13
+ className?: string;
14
+ }>;
15
+ hidden?: boolean;
16
+ }
17
+ /**
18
+ * One section — the first menu level. Its items are the screens the side menu offers
19
+ * while the section is active.
20
+ */
21
+ export interface PanelNavSection {
22
+ /** Stable key. Also the default translation key (`nav.<name>`). */
23
+ name: string;
24
+ label?: string;
25
+ /** Ordered. The first visible item is where pressing the section navigates. */
26
+ items: PanelNavItem[];
27
+ hidden?: boolean;
28
+ }
29
+ export interface PanelNavConfig {
30
+ sections: PanelNavSection[];
31
+ }
32
+ /** A footer link: exactly one of `alias` (an entrypoint) or `href` (anything else). */
33
+ export interface PanelNavLink {
34
+ alias?: string;
35
+ href?: string;
36
+ label?: string;
37
+ /** Open in a new tab. */
38
+ open?: boolean;
39
+ }
40
+ /**
41
+ * Resolves a label from a translation key.
42
+ *
43
+ * It is a PROP, never an implicit context read: an app that mounts without an i18n provider
44
+ * (`renderApp` from `@owlmeans/web-client` does) must still render a menu, and reaching for
45
+ * the panel i18n context there throws inside the render tree and blanks the whole app.
46
+ * The default returns the fallback, so literal labels work with no i18n at all.
47
+ */
48
+ export interface NavTranslate {
49
+ (key: string, defaultValue: string): string;
50
+ }
51
+ export interface PanelNavModel {
52
+ /** Sections and items with `hidden` filtered out. */
53
+ sections: PanelNavSection[];
54
+ /** Alias of the screen currently rendered, when it could be resolved. */
55
+ current: string | null;
56
+ active: PanelNavSection | null;
57
+ /**
58
+ * Whether the side menu carries anything worth showing — false when the active section
59
+ * holds a single screen, which is the whole point: one screen needs no second level.
60
+ */
61
+ showSide: boolean;
62
+ isSectionActive: (section: PanelNavSection) => boolean;
63
+ isItemActive: (item: PanelNavItem) => boolean;
64
+ goSection: (section: PanelNavSection) => () => void;
65
+ goItem: (item: PanelNavItem) => () => void;
66
+ /**
67
+ * The resolved URL of a section's landing screen, when it has one.
68
+ *
69
+ * A menu entry navigates through `nav.press`, but it still has to BE a link: an `<a>`
70
+ * without `href` is not focusable, does not answer the keyboard, and cannot be opened in a
71
+ * new tab. Undefined for a path carrying route parameters — there is no honest URL to show
72
+ * for a screen whose address is not known yet.
73
+ */
74
+ hrefOf: (target: PanelNavItem | PanelNavSection) => string | undefined;
75
+ }
76
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/nav/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,4GAA4G;IAC5G,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,+EAA+E;IAC/E,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,EAAE,CAAA;CAC5B;AAED,uFAAuF;AACvF,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,yBAAyB;IACzB,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAAA;CAC5C;AAED,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,yEAAyE;IACzE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,MAAM,EAAE,eAAe,GAAG,IAAI,CAAA;IAC9B;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAA;IACjB,eAAe,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAA;IACtD,YAAY,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAA;IAC7C,SAAS,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,MAAM,IAAI,CAAA;IACnD,MAAM,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,IAAI,CAAA;IAC1C;;;;;;;OAOG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,YAAY,GAAG,eAAe,KAAK,MAAM,GAAG,SAAS,CAAA;CACvE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/components/nav/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":""}
package/build/i18n.js ADDED
@@ -0,0 +1,15 @@
1
+ import { addI18nLib } from '@owlmeans/i18n';
2
+ const buttons = {
3
+ en: { buttons: { submit: 'Submit' } },
4
+ pl: { buttons: { submit: 'Wyślij' } },
5
+ ru: { buttons: { submit: 'Отправить' } },
6
+ be: { buttons: { submit: 'Адправіць' } },
7
+ uk: { buttons: { submit: 'Надіслати' } },
8
+ es: { buttons: { submit: 'Enviar' } },
9
+ de: { buttons: { submit: 'Senden' } },
10
+ fr: { buttons: { submit: 'Envoyer' } },
11
+ };
12
+ for (const [language, values] of Object.entries(buttons)) {
13
+ addI18nLib(language, 'client-panel', values);
14
+ }
15
+ //# sourceMappingURL=i18n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE3C,MAAM,OAAO,GAAG;IACd,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;IACrC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;IACrC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE;IACxC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE;IACxC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE;IACxC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;IACrC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;IACrC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE;CAC9B,CAAA;AAEV,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;IACzD,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,CAAC,CAAA;AAC9C,CAAC"}
package/build/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './i18n.js';
1
2
  export * from './components/index.js';
2
3
  export * from './helper/index.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,WAAW,CAAA;AAEzB,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA"}
package/build/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './i18n.js';
1
2
  export * from './components/index.js';
2
3
  export * from './helper/index.js';
3
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,WAAW,CAAA;AAEzB,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/client-panel",
3
- "version": "0.1.18-rc.4",
3
+ "version": "0.1.18-rc.41",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -36,18 +36,19 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@hookform/resolvers": "^5.2.2",
39
- "@owlmeans/client": "^0.1.18-rc.4",
40
- "@owlmeans/client-i18n": "^0.1.18-rc.4",
41
- "@owlmeans/client-entrypoint": "^0.1.18-rc.4",
42
- "@owlmeans/client-route": "^0.1.18-rc.4",
43
- "@owlmeans/error": "^0.1.18-rc.4",
44
- "@owlmeans/entrypoint": "^0.1.18-rc.4"
39
+ "@owlmeans/client": "^0.1.18-rc.35",
40
+ "@owlmeans/client-i18n": "^0.1.18-rc.34",
41
+ "@owlmeans/client-entrypoint": "^0.1.18-rc.32",
42
+ "@owlmeans/client-route": "^0.1.18-rc.33",
43
+ "@owlmeans/error": "^0.1.18-rc.30",
44
+ "@owlmeans/entrypoint": "^0.1.18-rc.31"
45
45
  },
46
46
  "peerDependencies": {
47
- "@owlmeans/auth": "^0.1.18-rc.4",
48
- "@owlmeans/client-auth": "^0.1.18-rc.4",
49
- "@owlmeans/i18n": "^0.1.18-rc.4",
50
- "@owlmeans/router": "^0.1.18-rc.4",
47
+ "@owlmeans/auth": "^0.1.18-rc.30",
48
+ "@owlmeans/client-auth": "^0.1.18-rc.41",
49
+ "@owlmeans/config": "^0.1.18-rc.32",
50
+ "@owlmeans/i18n": "^0.1.18-rc.31",
51
+ "@owlmeans/router": "^0.1.18-rc.27",
51
52
  "ajv": "*",
52
53
  "react": "*",
53
54
  "react-hook-form": "*"
@@ -62,8 +63,8 @@
62
63
  },
63
64
  "devDependencies": {
64
65
  "@owlmeans/dep-config": "workspace:*",
65
- "@owlmeans/auth": "^0.1.18-rc.4",
66
- "@owlmeans/client-auth": "^0.1.18-rc.4",
66
+ "@owlmeans/auth": "^0.1.18-rc.30",
67
+ "@owlmeans/client-auth": "^0.1.18-rc.41",
67
68
  "@types/react": "^19.2.17",
68
69
  "nodemon": "^3.1.14",
69
70
  "react-hook-form": "^7.53.0",
@@ -0,0 +1,56 @@
1
+ {
2
+ "basic-ed25519": {
3
+ "entityId": {
4
+ "label": "Identifiant de l’organisation"
5
+ },
6
+ "address": {
7
+ "label": "DID de l’utilisateur"
8
+ },
9
+ "privateKey": {
10
+ "label": "Clé privée"
11
+ },
12
+ "submit": "S’authentifier"
13
+ },
14
+ "re-captcha": {
15
+ "guideline": "Nous devons vérifier que vous n’êtes pas un robot. Cliquez dans la case blanche ci-dessous pour prouver que vous êtes humain."
16
+ },
17
+ "wallet-provider": {
18
+ "pin": {
19
+ "loading": "Chargement…",
20
+ "guideline": "Utilisez ce code PIN avec toute ressource prenant en charge l’autorisation par portefeuille"
21
+ }
22
+ },
23
+ "wallet-consumer": {
24
+ "pin": {
25
+ "label": "Code PIN",
26
+ "hint": "Scannez le code QR ou générez un code PIN avec votre application OwlMeans ID"
27
+ },
28
+ "submit": "Continuer avec le PIN"
29
+ },
30
+ "oidp-auth": {
31
+ "reset": "Réinitialiser",
32
+ "error": {
33
+ "socket": {
34
+ "timeout": {
35
+ "rely": "La fenêtre d’authentification a expiré. Actualisez la page pour réessayer."
36
+ }
37
+ }
38
+ }
39
+ },
40
+ "oidp-create-id": {
41
+ "error": {
42
+ "socket": {
43
+ "timeout": {
44
+ "call": "Délai d’action dépassé. Pour des raisons de sécurité, les actions d’authentification sont limitées dans le temps. Réessayez.",
45
+ "rely": "Le code d’authentification a expiré. Réessayez."
46
+ }
47
+ },
48
+ "auth": {
49
+ "authorization": {
50
+ "sequence": "Le processus d’authentification a été interrompu par l’application d’authentification utilisée."
51
+ }
52
+ }
53
+ },
54
+ "reset": "Actualiser"
55
+ }
56
+ }
package/src/auth/i18n.ts CHANGED
@@ -7,6 +7,7 @@ import be from './i18n/be.json' with { type: 'json' }
7
7
  import uk from './i18n/uk.json' with { type: 'json' }
8
8
  import es from './i18n/es.json' with { type: 'json' }
9
9
  import de from './i18n/de.json' with { type: 'json' }
10
+ import fr from './i18n/fr.json' with { type: 'json' }
10
11
 
11
12
  addI18nLib('en', 'client-panel-auth', en)
12
13
  addI18nLib('pl', 'client-panel-auth', pl)
@@ -15,3 +16,4 @@ addI18nLib('be', 'client-panel-auth', be)
15
16
  addI18nLib('uk', 'client-panel-auth', uk)
16
17
  addI18nLib('es', 'client-panel-auth', es)
17
18
  addI18nLib('de', 'client-panel-auth', de)
19
+ addI18nLib('fr', 'client-panel-auth', fr)
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
+ }
@@ -2,6 +2,7 @@ export type * from './types.js'
2
2
 
3
3
  export * from './form/index.js'
4
4
  export * from './layout/index.js'
5
+ export * from './nav/index.js'
5
6
  export * from './context.js'
6
7
  export * from './consts.js'
7
8
  export * from './status.js'