@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.
- package/README.md +63 -4
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/client-panel/SKILL.md +118 -11
- package/build/auth/i18n/fr.json +56 -0
- package/build/auth/i18n.js +2 -0
- package/build/auth/i18n.js.map +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/build/components/index.d.ts +1 -0
- package/build/components/index.d.ts.map +1 -1
- package/build/components/index.js +1 -0
- package/build/components/index.js.map +1 -1
- package/build/components/nav/helper.d.ts +21 -0
- package/build/components/nav/helper.d.ts.map +1 -0
- package/build/components/nav/helper.js +111 -0
- package/build/components/nav/helper.js.map +1 -0
- package/build/components/nav/index.d.ts +3 -0
- package/build/components/nav/index.d.ts.map +1 -0
- package/build/components/nav/index.js +2 -0
- package/build/components/nav/index.js.map +1 -0
- package/build/components/nav/types.d.ts +76 -0
- package/build/components/nav/types.d.ts.map +1 -0
- package/build/components/nav/types.js +2 -0
- package/build/components/nav/types.js.map +1 -0
- package/build/i18n.d.ts +2 -0
- package/build/i18n.d.ts.map +1 -0
- package/build/i18n.js +15 -0
- package/build/i18n.js.map +1 -0
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/package.json +14 -13
- package/src/auth/i18n/fr.json +56 -0
- package/src/auth/i18n.ts +2 -0
- 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/src/components/index.ts +1 -0
- package/src/components/nav/helper.ts +136 -0
- package/src/components/nav/index.ts +3 -0
- package/src/components/nav/types.ts +79 -0
- package/src/i18n.ts +16 -0
- package/src/index.ts +2 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/components/nav/types.ts"],"names":[],"mappings":""}
|
package/build/i18n.d.ts
ADDED
|
@@ -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
package/build/index.d.ts.map
CHANGED
|
@@ -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
package/build/index.js.map
CHANGED
|
@@ -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.
|
|
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.
|
|
40
|
-
"@owlmeans/client-i18n": "^0.1.18-rc.
|
|
41
|
-
"@owlmeans/client-entrypoint": "^0.1.18-rc.
|
|
42
|
-
"@owlmeans/client-route": "^0.1.18-rc.
|
|
43
|
-
"@owlmeans/error": "^0.1.18-rc.
|
|
44
|
-
"@owlmeans/entrypoint": "^0.1.18-rc.
|
|
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.
|
|
48
|
-
"@owlmeans/client-auth": "^0.1.18-rc.
|
|
49
|
-
"@owlmeans/
|
|
50
|
-
"@owlmeans/
|
|
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.
|
|
66
|
-
"@owlmeans/client-auth": "^0.1.18-rc.
|
|
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
|
@@ -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
|
+
}
|