@owlmeans/web-panel 0.1.18-rc.62 → 0.1.18-rc.63

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 (55) hide show
  1. package/README.md +2 -2
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/web-panel/SKILL.md +7 -3
  4. package/build/auth/entrypoints.d.ts +1 -9
  5. package/build/auth/entrypoints.d.ts.map +1 -1
  6. package/build/consent/index.d.ts +1 -1
  7. package/build/consent/index.d.ts.map +1 -1
  8. package/build/consent/index.js +1 -1
  9. package/build/consent/index.js.map +1 -1
  10. package/build/consent/language.d.ts +7 -17
  11. package/build/consent/language.d.ts.map +1 -1
  12. package/build/consent/language.js +7 -45
  13. package/build/consent/language.js.map +1 -1
  14. package/build/entrypoints.d.ts +1 -7
  15. package/build/entrypoints.d.ts.map +1 -1
  16. package/build/exports.d.ts +1 -0
  17. package/build/exports.d.ts.map +1 -1
  18. package/build/exports.js +1 -0
  19. package/build/exports.js.map +1 -1
  20. package/build/permission/i18n/be.json +13 -0
  21. package/build/permission/i18n/de.json +13 -0
  22. package/build/permission/i18n/en.json +13 -0
  23. package/build/permission/i18n/es.json +13 -0
  24. package/build/permission/i18n/fr.json +13 -0
  25. package/build/permission/i18n/pl.json +13 -0
  26. package/build/permission/i18n/ru.json +13 -0
  27. package/build/permission/i18n/uk.json +13 -0
  28. package/build/permission/i18n.d.ts +2 -0
  29. package/build/permission/i18n.d.ts.map +1 -0
  30. package/build/permission/i18n.js +13 -0
  31. package/build/permission/i18n.js.map +1 -0
  32. package/build/permission/index.d.ts +6 -0
  33. package/build/permission/index.d.ts.map +1 -0
  34. package/build/permission/index.js +27 -0
  35. package/build/permission/index.js.map +1 -0
  36. package/build/permission/toast.d.ts +9 -0
  37. package/build/permission/toast.d.ts.map +1 -0
  38. package/build/permission/toast.js +8 -0
  39. package/build/permission/toast.js.map +1 -0
  40. package/package.json +20 -19
  41. package/src/consent/index.ts +2 -2
  42. package/src/consent/language.ts +7 -48
  43. package/src/exports.ts +1 -0
  44. package/src/permission/i18n/be.json +13 -0
  45. package/src/permission/i18n/de.json +13 -0
  46. package/src/permission/i18n/en.json +13 -0
  47. package/src/permission/i18n/es.json +13 -0
  48. package/src/permission/i18n/fr.json +13 -0
  49. package/src/permission/i18n/pl.json +13 -0
  50. package/src/permission/i18n/ru.json +13 -0
  51. package/src/permission/i18n/uk.json +13 -0
  52. package/src/permission/i18n.ts +13 -0
  53. package/src/permission/index.ts +27 -0
  54. package/src/permission/toast.tsx +25 -0
  55. package/tests/consent-language.spec.ts +18 -69
package/README.md CHANGED
@@ -11,7 +11,7 @@ but never started.
11
11
  ## Installation
12
12
 
13
13
  ```sh
14
- bun add @owlmeans/web-panel@^0.1.18-rc.62
14
+ bun add @owlmeans/web-panel@^0.1.18-rc.63
15
15
  ```
16
16
 
17
17
  Peer requirements (the consuming app provides these): `react`, `react-dom`,
@@ -496,7 +496,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
496
496
  your project's skill store (`.agents/skills/`):
497
497
 
498
498
  ```sh
499
- npx @owlmeans/agent-skills@^0.1.18-rc.39
499
+ npx @owlmeans/agent-skills@^0.1.18-rc.40
500
500
  ```
501
501
 
502
502
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/web-panel",
4
- "version": "0.1.18-rc.62",
5
- "generatedAt": "2026-09-25T15:32:39.588Z",
4
+ "version": "0.1.18-rc.63",
5
+ "generatedAt": "2026-09-25T21:19:38.010Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/web-panel
9
9
 
10
10
  **Layer:** Web (React)
11
- **Install:** `"@owlmeans/web-panel": "^0.1.18-rc.62"` in `dependencies`
11
+ **Install:** `"@owlmeans/web-panel": "^0.1.18-rc.63"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
@@ -330,6 +330,11 @@ export const MainLayout: FC<PropsWithChildren> = ({ children }) => <>
330
330
  - `sonner` is a dependency of this package, so nothing is required of the consumer — but an app
331
331
  raising its own toasts should declare `sonner` too, at a range that resolves to the same copy.
332
332
 
333
+ `appendPermissionDeniedToast(context)` plugs into the client entrypoint failure service and
334
+ shows a localized access-denied toast for errors carrying the server IAM marker. Mount `Toaster`
335
+ once as usual. The toast exposes the technical message and incident id in a separate details
336
+ section. The app supplies its locale through its i18n context.
337
+
333
338
  ### Reload prompt — `SocketReloadDialog`
334
339
 
335
340
  `makeContext` calls `appendSocketStatus` from `@owlmeans/client-socket` unconditionally, and
@@ -499,9 +504,8 @@ menu has already mounted) and because React 18 StrictMode double-invokes mount/c
499
504
  | Job | Import from `@owlmeans/web-panel/consent` | Where it goes |
500
505
  |---|---|---|
501
506
  | The consent dialog (and its floating re-open button) | `PanelCookieConsent` | Beside the router — a `PanelApp` child — once |
502
- | Remember the language only while the visitor granted `functional` cookies | `installConsentLanguage()` | Once, in the bootstrap, BEFORE `prepareI18n` — wires `client-i18n`'s persistence guard, and applies a language that arrived with a link from another OwlMeans domain once the grant is saved |
503
507
  | The "Cookie settings" control in a footer or menu | `PanelConsentMenuWidget` (`label`, `className`, `onSelect?` — defaults to `openConsent('reopen')`) | Inside the host's own footer/menu |
504
- | Hiding the floating button while that control is reachable | `useConsentMenuPresence()` | Called by the always-mounted component that renders the control |
508
+ | Hiding the floating button while that control is reachable | `useConsentMenuPresence()` | Called by the always-mounted component that renders the control — mounted ONLY while the control can actually be reached: a component that is merely hidden by CSS (`xl:hidden`) is still mounted and still claims presence, so the floating button vanishes with nothing else to open the dialog; render it conditionally instead (viable's header mounts its hamburger menu only below the full-row breakpoint) |
505
509
  | Registering the presence service | `appendConsentWidgetService(context)` | The app's `context.ts` |
506
510
 
507
511
  A footer is the simplest host, because it is always mounted — the control and the presence call
@@ -1,10 +1,2 @@
1
- export declare const entrypoints: (import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<import("@owlmeans/entrypoint").OpenRequest, import("@owlmeans/entrypoint").OpenValue>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
2
- body: import("@owlmeans/auth").AllowanceRequest;
3
- }, import("@owlmeans/auth").AllowanceResponse>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
4
- body: import("@owlmeans/auth").AuthCredentials;
5
- }, import("@owlmeans/auth").AuthToken>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
6
- query: Partial<import("@owlmeans/auth").AuthToken>;
7
- }, undefined>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
8
- query: import("@owlmeans/auth").AuthToken;
9
- }, undefined>>)[];
1
+ export declare const entrypoints: (import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<any> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<import("@owlmeans/entrypoint").OpenRequest, import("@owlmeans/entrypoint").OpenValue>>)[];
10
2
  //# sourceMappingURL=entrypoints.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../../src/auth/entrypoints.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW;;;;;;;;iBAAuC,CAAA"}
1
+ {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../../src/auth/entrypoints.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,mRAAuC,CAAA"}
@@ -5,6 +5,6 @@ export { installConsentLanguage } from './language.js';
5
5
  export { createConsentWidgetService, appendConsentWidgetService, CONSENT_WIDGET_STATE } from './service.js';
6
6
  export { CONSENT_WIDGET_SERVICE } from './consts.js';
7
7
  export type { ConsentWidgetPresenceRecord, ConsentWidgetService, ConsentWidgetServiceAppend } from './types.js';
8
- export { useConsent, useConsentCategory, consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, DEFAULT_CONSENT_CATEGORIES, CONSENT_KEY, CONSENT_ESSENTIAL, CONSENT_FUNCTIONAL, CONSENT_ANALYTICS, CONSENT_MARKETING, CONSENT_EVENT, CONSENT_LANGUAGE_EVENT, functionalGranted, consentBootstrapScript, } from '@owlmeans/web-consent';
8
+ export { useConsent, useConsentCategory, consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, DEFAULT_CONSENT_CATEGORIES, CONSENT_KEY, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING, CONSENT_EVENT, consentBootstrapScript, } from '@owlmeans/web-consent';
9
9
  export type { ConsentCategory, ConsentMenuWidgetProps, ConsentOptions, ConsentRecord, ConsentSignal, ConsentState, CookieConsentProps, CookiePolicyProps, } from '@owlmeans/web-consent';
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/consent/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAC3G,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACpD,YAAY,EACV,2BAA2B,EAAE,oBAAoB,EAAE,0BAA0B,EAC9E,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EACtE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,0BAA0B,EACnE,WAAW,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,iBAAiB,EACxF,aAAa,EAAE,sBAAsB,EAAE,iBAAiB,EACxD,sBAAsB,GACvB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EAAE,sBAAsB,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EACrF,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,GACpD,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/consent/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAC3G,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACpD,YAAY,EACV,2BAA2B,EAAE,oBAAoB,EAAE,0BAA0B,EAC9E,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EACtE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,0BAA0B,EACnE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EACpE,aAAa,EACb,sBAAsB,GACvB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EAAE,sBAAsB,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EACrF,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,GACpD,MAAM,uBAAuB,CAAA"}
@@ -4,5 +4,5 @@ export * from './presence.js';
4
4
  export { installConsentLanguage } from './language.js';
5
5
  export { createConsentWidgetService, appendConsentWidgetService, CONSENT_WIDGET_STATE } from './service.js';
6
6
  export { CONSENT_WIDGET_SERVICE } from './consts.js';
7
- export { useConsent, useConsentCategory, consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, DEFAULT_CONSENT_CATEGORIES, CONSENT_KEY, CONSENT_ESSENTIAL, CONSENT_FUNCTIONAL, CONSENT_ANALYTICS, CONSENT_MARKETING, CONSENT_EVENT, CONSENT_LANGUAGE_EVENT, functionalGranted, consentBootstrapScript, } from '@owlmeans/web-consent';
7
+ export { useConsent, useConsentCategory, consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, DEFAULT_CONSENT_CATEGORIES, CONSENT_KEY, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING, CONSENT_EVENT, consentBootstrapScript, } from '@owlmeans/web-consent';
8
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/consent/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAC3G,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAIpD,OAAO,EACL,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EACtE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,0BAA0B,EACnE,WAAW,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,iBAAiB,EACxF,aAAa,EAAE,sBAAsB,EAAE,iBAAiB,EACxD,sBAAsB,GACvB,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/consent/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAC3G,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAIpD,OAAO,EACL,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EACtE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,0BAA0B,EACnE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EACpE,aAAa,EACb,sBAAsB,GACvB,MAAM,uBAAuB,CAAA"}
@@ -1,22 +1,12 @@
1
1
  import type { ConsentOptions } from '@owlmeans/web-consent';
2
2
  /**
3
- * Bind the application's language to the visitor's cookie choice: a language is remembered on their
4
- * device only while they have granted `functional` storage.
3
+ * @deprecated The interface language is strictly necessary storage: `@owlmeans/client-i18n` writes
4
+ * it on every switch and reads it at every start, and `@owlmeans/consent` stores a language that
5
+ * arrives with a link — none of it waits for a cookie decision, so there is nothing left to bind.
5
6
  *
6
- * It does three things, and the order they take effect in is the point:
7
- *
8
- * 1. Installs the persistence guard in `@owlmeans/client-i18n` — so the language switcher (which
9
- * calls `setLanguage`) stops writing `owlmeans-lng` until functional is granted, and a stored
10
- * language counts as absent while it is not. **Call it BEFORE `prepareI18n`**: the initial
11
- * language is resolved from storage right there.
12
- * 2. On every consent change (`CONSENT_EVENT`) writes the choice the visitor made while storage was
13
- * refused, if it is allowed now — a person who picked a language, then answered the dialog with
14
- * "accept all", keeps it.
15
- * 3. On `CONSENT_LANGUAGE_EVENT` — a language that arrived with a link and was waiting for the grant —
16
- * switches to it now, unless the person has already picked one in this page's life: what they
17
- * chose outranks what a link carried, and that choice is re-written over the store's own write.
18
- *
19
- * Returns a function that undoes all three. Safe on the server (only the guard is installed).
7
+ * Kept as a no-op (returning a no-op cleanup) only so an application written against the earlier,
8
+ * consent-gated API — a generated target that floats to a newer release — still builds. Remove the
9
+ * call.
20
10
  */
21
- export declare const installConsentLanguage: (opts?: ConsentOptions) => (() => void);
11
+ export declare const installConsentLanguage: (_opts?: ConsentOptions) => (() => void);
22
12
  //# sourceMappingURL=language.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"language.d.ts","sourceRoot":"","sources":["../../src/consent/language.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sBAAsB,UAAW,cAAc,KAAG,CAAC,MAAM,IAAI,CA6BzE,CAAA"}
1
+ {"version":3,"file":"language.d.ts","sourceRoot":"","sources":["../../src/consent/language.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,WAAY,cAAc,KAAG,CAAC,MAAM,IAAI,CAAoB,CAAA"}
@@ -1,49 +1,11 @@
1
- import { getExplicitLanguage, persistLanguage, setLanguage, setLanguagePersistence } from '@owlmeans/client-i18n';
2
- import { CONSENT_EVENT, CONSENT_LANGUAGE_EVENT, functionalGranted } from '@owlmeans/web-consent';
3
1
  /**
4
- * Bind the application's language to the visitor's cookie choice: a language is remembered on their
5
- * device only while they have granted `functional` storage.
2
+ * @deprecated The interface language is strictly necessary storage: `@owlmeans/client-i18n` writes
3
+ * it on every switch and reads it at every start, and `@owlmeans/consent` stores a language that
4
+ * arrives with a link — none of it waits for a cookie decision, so there is nothing left to bind.
6
5
  *
7
- * It does three things, and the order they take effect in is the point:
8
- *
9
- * 1. Installs the persistence guard in `@owlmeans/client-i18n` — so the language switcher (which
10
- * calls `setLanguage`) stops writing `owlmeans-lng` until functional is granted, and a stored
11
- * language counts as absent while it is not. **Call it BEFORE `prepareI18n`**: the initial
12
- * language is resolved from storage right there.
13
- * 2. On every consent change (`CONSENT_EVENT`) writes the choice the visitor made while storage was
14
- * refused, if it is allowed now — a person who picked a language, then answered the dialog with
15
- * "accept all", keeps it.
16
- * 3. On `CONSENT_LANGUAGE_EVENT` — a language that arrived with a link and was waiting for the grant —
17
- * switches to it now, unless the person has already picked one in this page's life: what they
18
- * chose outranks what a link carried, and that choice is re-written over the store's own write.
19
- *
20
- * Returns a function that undoes all three. Safe on the server (only the guard is installed).
6
+ * Kept as a no-op (returning a no-op cleanup) only so an application written against the earlier,
7
+ * consent-gated API — a generated target that floats to a newer release — still builds. Remove the
8
+ * call.
21
9
  */
22
- export const installConsentLanguage = (opts) => {
23
- setLanguagePersistence(() => functionalGranted(opts));
24
- if (typeof window === 'undefined') {
25
- return () => setLanguagePersistence(null);
26
- }
27
- const onConsent = () => {
28
- persistLanguage();
29
- };
30
- const onLanguage = (event) => {
31
- const carried = event.detail?.language;
32
- const chosen = getExplicitLanguage();
33
- const language = chosen ?? carried;
34
- if (typeof language !== 'string' || language === '') {
35
- return;
36
- }
37
- setLanguage(language).catch((error) => {
38
- console.error('[i18n] could not apply the language that was waiting for consent', error);
39
- });
40
- };
41
- window.addEventListener(CONSENT_EVENT, onConsent);
42
- window.addEventListener(CONSENT_LANGUAGE_EVENT, onLanguage);
43
- return () => {
44
- window.removeEventListener(CONSENT_EVENT, onConsent);
45
- window.removeEventListener(CONSENT_LANGUAGE_EVENT, onLanguage);
46
- setLanguagePersistence(null);
47
- };
48
- };
10
+ export const installConsentLanguage = (_opts) => () => undefined;
49
11
  //# sourceMappingURL=language.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"language.js","sourceRoot":"","sources":["../../src/consent/language.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AACjH,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAGhG;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,IAAqB,EAAgB,EAAE;IAC5E,sBAAsB,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAA;IAErD,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,GAAG,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAA;IAC3C,CAAC;IAED,MAAM,SAAS,GAAG,GAAS,EAAE;QAC3B,eAAe,EAAE,CAAA;IACnB,CAAC,CAAA;IACD,MAAM,UAAU,GAAG,CAAC,KAAY,EAAQ,EAAE;QACxC,MAAM,OAAO,GAAI,KAA4C,CAAC,MAAM,EAAE,QAAQ,CAAA;QAC9E,MAAM,MAAM,GAAG,mBAAmB,EAAE,CAAA;QACpC,MAAM,QAAQ,GAAG,MAAM,IAAI,OAAO,CAAA;QAClC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;YACpD,OAAM;QACR,CAAC;QACD,WAAW,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC7C,OAAO,CAAC,KAAK,CAAC,kEAAkE,EAAE,KAAK,CAAC,CAAA;QAC1F,CAAC,CAAC,CAAA;IACJ,CAAC,CAAA;IACD,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;IACjD,MAAM,CAAC,gBAAgB,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAA;IAE3D,OAAO,GAAG,EAAE;QACV,MAAM,CAAC,mBAAmB,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;QACpD,MAAM,CAAC,mBAAmB,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAA;QAC9D,sBAAsB,CAAC,IAAI,CAAC,CAAA;IAC9B,CAAC,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"language.js","sourceRoot":"","sources":["../../src/consent/language.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAsB,EAAgB,EAAE,CAAC,GAAG,EAAE,CAAC,SAAS,CAAA"}
@@ -1,8 +1,2 @@
1
- export declare const entrypoints: (import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<import("@owlmeans/entrypoint").OpenRequest, import("@owlmeans/entrypoint").OpenValue>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
2
- query: import("@owlmeans/auth").AuthToken;
3
- }, undefined>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
4
- query: import("@owlmeans/auth-common").SurrogateQuery;
5
- }, undefined>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
6
- body: import("@owlmeans/auth").AuthToken;
7
- }, import("@owlmeans/auth").AuthToken>>)[];
1
+ export declare const entrypoints: any[];
8
2
  //# sourceMappingURL=entrypoints.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW;;;;;;0CAA4C,CAAA"}
1
+ {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,OAA4C,CAAA"}
@@ -4,6 +4,7 @@ export { config } from '@owlmeans/client-context';
4
4
  export { service } from '@owlmeans/config';
5
5
  export { addWebService } from '@owlmeans/client-config';
6
6
  export { bind, bindAll, bindScreen, provideRequest, stab } from '@owlmeans/client-entrypoint';
7
+ export { appendPermissionDeniedToast, PermissionDeniedToast } from './permission/index.js';
7
8
  export type { ClientEntrypoint as Module } from '@owlmeans/client-entrypoint';
8
9
  export { route as croute } from '@owlmeans/client-route';
9
10
  export { route, frontend } from '@owlmeans/route';
@@ -1 +1 @@
1
- {"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAC5G,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACxF,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAC7F,YAAY,EAAE,gBAAgB,IAAI,MAAM,EAAE,MAAM,6BAA6B,CAAA;AAC7E,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AAEhG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAC/D,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnG,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACtF,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAC5G,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACxF,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAC7F,OAAO,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC1F,YAAY,EAAE,gBAAgB,IAAI,MAAM,EAAE,MAAM,6BAA6B,CAAA;AAC7E,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AAEhG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAC/D,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnG,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACtF,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA"}
package/build/exports.js CHANGED
@@ -3,6 +3,7 @@ export { config } from '@owlmeans/client-context';
3
3
  export { service } from '@owlmeans/config';
4
4
  export { addWebService } from '@owlmeans/client-config';
5
5
  export { bind, bindAll, bindScreen, provideRequest, stab } from '@owlmeans/client-entrypoint';
6
+ export { appendPermissionDeniedToast, PermissionDeniedToast } from './permission/index.js';
6
7
  export { route as croute } from '@owlmeans/client-route';
7
8
  export { route, frontend } from '@owlmeans/route';
8
9
  export { DEFAULT_ALIAS as DAUTH_GUARD, bindExternalAuthentication } from '@owlmeans/client-auth';
@@ -1 +1 @@
1
- {"version":3,"file":"exports.js","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAE5G,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAE7F,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AAEhG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAG/D,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnG,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACtF,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"exports.js","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAE5G,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAC7F,OAAO,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAE1F,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AAEhG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAG/D,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnG,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACtF,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Дзеянне адхілена",
3
+ "description": "Сервер адхіліў дзеянне паводле бягучых правіл доступу. Калі вы чакалі доступу, звярніцеся да адміністратара.",
4
+ "technical-details": "Тэхнічныя падрабязнасці",
5
+ "endpoint": "Канцавы пункт",
6
+ "status": "Статус",
7
+ "incident": "Ідэнтыфікатар інцыдэнту",
8
+ "area-title": "Доступ забаронены",
9
+ "area-description": "Ваш уліковы запіс не можа адкрыць гэты раздзел. Калі вы чакалі доступу, звярніцеся да адміністратара.",
10
+ "area": "Раздзел",
11
+ "required-permissions": "Патрэбныя дазволы",
12
+ "logout": "Выйсці"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Aktion verweigert",
3
+ "description": "Der Server hat diese Aktion gemäß den aktuellen Berechtigungen verweigert. Wenn Sie Zugriff erwartet haben, fragen Sie eine Administratorin oder einen Administrator.",
4
+ "technical-details": "Technische Details",
5
+ "endpoint": "Endpunkt",
6
+ "status": "Status",
7
+ "incident": "Vorfall-ID",
8
+ "area-title": "Zugriff verweigert",
9
+ "area-description": "Ihr Konto kann diesen Bereich nicht öffnen. Wenn Sie Zugriff erwartet haben, wenden Sie sich an die Administration.",
10
+ "area": "Bereich",
11
+ "required-permissions": "Erforderliche Berechtigungen",
12
+ "logout": "Abmelden"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Action denied",
3
+ "description": "The server denied this action under the current permission rules. If you expected access, ask an administrator.",
4
+ "technical-details": "Technical details",
5
+ "endpoint": "Endpoint",
6
+ "status": "Status",
7
+ "incident": "Incident ID",
8
+ "area-title": "Access denied",
9
+ "area-description": "Your account cannot open this area. Ask an administrator if you expected access.",
10
+ "area": "Area",
11
+ "required-permissions": "Required permissions",
12
+ "logout": "Log out"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Acción denegada",
3
+ "description": "El servidor denegó esta acción según las reglas de permisos actuales. Si esperaba tener acceso, consulte a un administrador.",
4
+ "technical-details": "Detalles técnicos",
5
+ "endpoint": "Punto de acceso",
6
+ "status": "Estado",
7
+ "incident": "ID del incidente",
8
+ "area-title": "Acceso denegado",
9
+ "area-description": "Su cuenta no puede abrir esta sección. Si esperaba tener acceso, consulte a un administrador.",
10
+ "area": "Sección",
11
+ "required-permissions": "Permisos necesarios",
12
+ "logout": "Cerrar sesión"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Action refusée",
3
+ "description": "Le serveur a refusé cette action selon les autorisations actuelles. Si vous pensiez avoir accès, contactez un administrateur.",
4
+ "technical-details": "Détails techniques",
5
+ "endpoint": "Point de terminaison",
6
+ "status": "Statut",
7
+ "incident": "ID de l’incident",
8
+ "area-title": "Accès refusé",
9
+ "area-description": "Votre compte ne peut pas ouvrir cette section. Si vous pensiez avoir accès, contactez un administrateur.",
10
+ "area": "Section",
11
+ "required-permissions": "Autorisations requises",
12
+ "logout": "Se déconnecter"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Działanie odrzucone",
3
+ "description": "Serwer odrzucił to działanie zgodnie z obecnymi uprawnieniami. Jeśli oczekujesz dostępu, skontaktuj się z administratorem.",
4
+ "technical-details": "Szczegóły techniczne",
5
+ "endpoint": "Punkt końcowy",
6
+ "status": "Status",
7
+ "incident": "Identyfikator incydentu",
8
+ "area-title": "Odmowa dostępu",
9
+ "area-description": "Twoje konto nie może otworzyć tego obszaru. Jeśli oczekujesz dostępu, skontaktuj się z administratorem.",
10
+ "area": "Obszar",
11
+ "required-permissions": "Wymagane uprawnienia",
12
+ "logout": "Wyloguj się"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Действие отклонено",
3
+ "description": "Сервер отклонил действие согласно текущим правилам доступа. Если вам нужен доступ, обратитесь к администратору.",
4
+ "technical-details": "Технические подробности",
5
+ "endpoint": "Конечная точка",
6
+ "status": "Статус",
7
+ "incident": "Идентификатор инцидента",
8
+ "area-title": "Доступ запрещён",
9
+ "area-description": "Ваша учётная запись не может открыть этот раздел. Если вам нужен доступ, обратитесь к администратору.",
10
+ "area": "Раздел",
11
+ "required-permissions": "Необходимые разрешения",
12
+ "logout": "Выйти"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Дію відхилено",
3
+ "description": "Сервер відхилив цю дію згідно з поточними правилами доступу. Якщо ви очікували доступу, зверніться до адміністратора.",
4
+ "technical-details": "Технічні подробиці",
5
+ "endpoint": "Кінцева точка",
6
+ "status": "Статус",
7
+ "incident": "Ідентифікатор інциденту",
8
+ "area-title": "Доступ заборонено",
9
+ "area-description": "Ваш обліковий запис не може відкрити цей розділ. Якщо ви очікували доступу, зверніться до адміністратора.",
10
+ "area": "Розділ",
11
+ "required-permissions": "Потрібні дозволи",
12
+ "logout": "Вийти"
13
+ }
@@ -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/permission/i18n.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ import { addI18nLib } from '@owlmeans/i18n';
2
+ import en from './i18n/en.json';
3
+ import pl from './i18n/pl.json';
4
+ import ru from './i18n/ru.json';
5
+ import be from './i18n/be.json';
6
+ import uk from './i18n/uk.json';
7
+ import es from './i18n/es.json';
8
+ import de from './i18n/de.json';
9
+ import fr from './i18n/fr.json';
10
+ for (const [language, chunk] of Object.entries({ en, pl, ru, be, uk, es, de, fr })) {
11
+ addI18nLib(language, 'permission-denied', chunk);
12
+ }
13
+ //# sourceMappingURL=i18n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../src/permission/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC/B,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC/B,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC/B,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC/B,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC/B,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC/B,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC/B,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAE/B,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;IACnF,UAAU,CAAC,QAAQ,EAAE,mBAAmB,EAAE,KAAK,CAAC,CAAA;AAClD,CAAC"}
@@ -0,0 +1,6 @@
1
+ import './i18n.js';
2
+ import type { BasicContext } from '@owlmeans/context';
3
+ export { PermissionDeniedToast } from './toast.js';
4
+ /** Opt-in adapter for the context's entrypoint failures; mount one Toaster in the app shell. */
5
+ export declare const appendPermissionDeniedToast: <T extends BasicContext<any>>(context: T) => T;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/permission/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAA;AAGlB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAElD,gGAAgG;AAChG,eAAO,MAAM,2BAA2B,GAAI,CAAC,SAAS,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,KAAG,CAerF,CAAA"}
@@ -0,0 +1,27 @@
1
+ import './i18n.js';
2
+ import { createElement } from 'react';
3
+ import { toast } from 'sonner';
4
+ import { ensureEntrypointFailureService } from '@owlmeans/client-entrypoint';
5
+ import { httpStatusOf, incidentIdOf, isAccessDenied } from '@owlmeans/api/status';
6
+ import { PermissionDeniedToast } from './toast.js';
7
+ export { PermissionDeniedToast } from './toast.js';
8
+ /** Opt-in adapter for the context's entrypoint failures; mount one Toaster in the app shell. */
9
+ export const appendPermissionDeniedToast = (context) => {
10
+ const seen = new Map();
11
+ ensureEntrypointFailureService(context).registerPlugin({
12
+ alias: 'web-panel:permission-denied-toast',
13
+ onFailure: ({ alias, error }) => {
14
+ if (!isAccessDenied(error))
15
+ return;
16
+ const now = Date.now();
17
+ if (now - (seen.get(alias) ?? 0) < 2000)
18
+ return;
19
+ seen.set(alias, now);
20
+ toast.warning(createElement(PermissionDeniedToast, {
21
+ alias, status: httpStatusOf(error), incidentId: incidentIdOf(error),
22
+ }));
23
+ },
24
+ });
25
+ return context;
26
+ };
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/permission/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAA;AAClB,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAE9B,OAAO,EAAE,8BAA8B,EAAE,MAAM,6BAA6B,CAAA;AAC5E,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAElD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAElD,gGAAgG;AAChG,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAA8B,OAAU,EAAK,EAAE;IACxF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAA;IACtC,8BAA8B,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC;QACrD,KAAK,EAAE,mCAAmC;QAC1C,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;YAC9B,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC;gBAAE,OAAM;YAClC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YACtB,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI;gBAAE,OAAM;YAC/C,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;YACpB,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,qBAAqB,EAAE;gBACjD,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,YAAY,CAAC,KAAK,CAAC;aACpE,CAAC,CAAC,CAAA;QACL,CAAC;KACF,CAAC,CAAA;IACF,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
@@ -0,0 +1,9 @@
1
+ import type { FC } from 'react';
2
+ export interface PermissionDeniedToastProps {
3
+ alias: string;
4
+ status: number | null;
5
+ incidentId: string | null;
6
+ }
7
+ /** A refusal is actionable context for the user, with diagnostics outside the explanation. */
8
+ export declare const PermissionDeniedToast: FC<PermissionDeniedToastProps>;
9
+ //# sourceMappingURL=toast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../src/permission/toast.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAG/B,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,8FAA8F;AAC9F,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC,0BAA0B,CAchE,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useI18nLib } from '@owlmeans/client-i18n';
3
+ /** A refusal is actionable context for the user, with diagnostics outside the explanation. */
4
+ export const PermissionDeniedToast = ({ alias, status, incidentId }) => {
5
+ const t = useI18nLib('permission-denied');
6
+ return _jsxs("div", { className: "space-y-2 max-w-sm text-sm", children: [_jsx("p", { className: "font-semibold", children: t('title') }), _jsx("p", { children: t('description') }), _jsxs("details", { className: "text-xs", children: [_jsx("summary", { className: "cursor-pointer", children: t('technical-details') }), _jsxs("dl", { className: "mt-2 grid grid-cols-[auto_1fr] gap-x-2 break-all", children: [_jsx("dt", { children: t('endpoint') }), _jsx("dd", { children: alias }), status != null && _jsxs(_Fragment, { children: [_jsx("dt", { children: t('status') }), _jsx("dd", { children: status })] }), incidentId != null && _jsxs(_Fragment, { children: [_jsx("dt", { children: t('incident') }), _jsx("dd", { children: incidentId })] })] })] })] });
7
+ };
8
+ //# sourceMappingURL=toast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toast.js","sourceRoot":"","sources":["../../src/permission/toast.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAQlD,8FAA8F;AAC9F,MAAM,CAAC,MAAM,qBAAqB,GAAmC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE;IACrG,MAAM,CAAC,GAAG,UAAU,CAAC,mBAAmB,CAAC,CAAA;IACzC,OAAO,eAAK,SAAS,EAAC,4BAA4B,aAChD,YAAG,SAAS,EAAC,eAAe,YAAE,CAAC,CAAC,OAAO,CAAC,GAAK,EAC7C,sBAAI,CAAC,CAAC,aAAa,CAAC,GAAK,EACzB,mBAAS,SAAS,EAAC,SAAS,aAC1B,kBAAS,SAAS,EAAC,gBAAgB,YAAE,CAAC,CAAC,mBAAmB,CAAC,GAAW,EACtE,cAAI,SAAS,EAAC,kDAAkD,aAC9D,uBAAK,CAAC,CAAC,UAAU,CAAC,GAAM,EAAA,uBAAK,KAAK,GAAM,EACvC,MAAM,IAAI,IAAI,IAAI,8BAAE,uBAAK,CAAC,CAAC,QAAQ,CAAC,GAAM,EAAA,uBAAK,MAAM,GAAM,IAAG,EAC9D,UAAU,IAAI,IAAI,IAAI,8BAAE,uBAAK,CAAC,CAAC,UAAU,CAAC,GAAM,EAAA,uBAAK,UAAU,GAAM,IAAG,IACtE,IACG,IACN,CAAA;AACR,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/web-panel",
3
- "version": "0.1.18-rc.62",
3
+ "version": "0.1.18-rc.63",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -51,20 +51,21 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@hookform/resolvers": "^5.2.2",
54
- "@owlmeans/api-config-client": "^0.1.18-rc.36",
54
+ "@owlmeans/api-config-client": "^0.1.18-rc.37",
55
55
  "@owlmeans/auth": "^0.1.18-rc.36",
56
- "@owlmeans/auth-common": "^0.1.18-rc.38",
57
- "@owlmeans/basic-envelope": "^0.1.18-rc.37",
58
- "@owlmeans/client": "^0.1.18-rc.42",
59
- "@owlmeans/client-auth": "^0.1.18-rc.49",
56
+ "@owlmeans/auth-common": "^0.1.18-rc.39",
57
+ "@owlmeans/basic-envelope": "^0.1.18-rc.38",
58
+ "@owlmeans/client": "^0.1.18-rc.43",
59
+ "@owlmeans/client-auth": "^0.1.18-rc.50",
60
60
  "@owlmeans/client-config": "^0.1.18-rc.38",
61
- "@owlmeans/client-context": "^0.1.18-rc.39",
62
- "@owlmeans/client-entrypoint": "^0.1.18-rc.38",
63
- "@owlmeans/client-flow": "^0.1.18-rc.45",
64
- "@owlmeans/client-i18n": "^0.1.18-rc.41",
65
- "@owlmeans/client-panel": "^0.1.18-rc.49",
66
- "@owlmeans/client-route": "^0.1.18-rc.39",
67
- "@owlmeans/client-socket": "^0.1.18-rc.40",
61
+ "@owlmeans/client-context": "^0.1.18-rc.40",
62
+ "@owlmeans/client-entrypoint": "^0.1.18-rc.39",
63
+ "@owlmeans/api": "^0.1.18-rc.39",
64
+ "@owlmeans/client-flow": "^0.1.18-rc.46",
65
+ "@owlmeans/client-i18n": "^0.1.18-rc.42",
66
+ "@owlmeans/client-panel": "^0.1.18-rc.50",
67
+ "@owlmeans/client-route": "^0.1.18-rc.40",
68
+ "@owlmeans/client-socket": "^0.1.18-rc.41",
68
69
  "@owlmeans/config": "^0.1.18-rc.38",
69
70
  "@owlmeans/context": "^0.1.18-rc.32",
70
71
  "@owlmeans/entrypoint": "^0.1.18-rc.37",
@@ -73,11 +74,11 @@
73
74
  "@owlmeans/i18n": "^0.1.18-rc.37",
74
75
  "@owlmeans/route": "^0.1.18-rc.33",
75
76
  "@owlmeans/state": "^0.1.18-rc.35",
76
- "@owlmeans/web-client": "^0.1.18-rc.52",
77
- "@owlmeans/web-consent": "^0.1.18-rc.33",
78
- "@owlmeans/web-db": "^0.1.18-rc.39",
79
- "@owlmeans/web-flow": "^0.1.18-rc.44",
80
- "@owlmeans/web-router": "^0.1.18-rc.40",
77
+ "@owlmeans/web-client": "^0.1.18-rc.53",
78
+ "@owlmeans/web-consent": "^0.1.18-rc.34",
79
+ "@owlmeans/web-db": "^0.1.18-rc.40",
80
+ "@owlmeans/web-flow": "^0.1.18-rc.45",
81
+ "@owlmeans/web-router": "^0.1.18-rc.41",
81
82
  "i18next-browser-languagedetector": "^8.0.0",
82
83
  "qrcode.react": "^4.2.0",
83
84
  "react-google-recaptcha": "^3.1.0",
@@ -103,7 +104,7 @@
103
104
  },
104
105
  "devDependencies": {
105
106
  "@owlmeans/dep-config": "workspace:*",
106
- "@owlmeans/test-ui": "^0.1.18-rc.40",
107
+ "@owlmeans/test-ui": "^0.1.18-rc.41",
107
108
  "@radix-ui/react-alert-dialog": "^1.1.15",
108
109
  "@radix-ui/react-dialog": "^1.1.15",
109
110
  "@radix-ui/react-label": "^2.1.0",
@@ -10,8 +10,8 @@ export type {
10
10
  export {
11
11
  useConsent, useConsentCategory, consentStore, openConsent, isConsented,
12
12
  readConsent, writeConsent, clearConsent, DEFAULT_CONSENT_CATEGORIES,
13
- CONSENT_KEY, CONSENT_ESSENTIAL, CONSENT_FUNCTIONAL, CONSENT_ANALYTICS, CONSENT_MARKETING,
14
- CONSENT_EVENT, CONSENT_LANGUAGE_EVENT, functionalGranted,
13
+ CONSENT_KEY, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING,
14
+ CONSENT_EVENT,
15
15
  consentBootstrapScript,
16
16
  } from '@owlmeans/web-consent'
17
17
  export type {
@@ -1,53 +1,12 @@
1
- import { getExplicitLanguage, persistLanguage, setLanguage, setLanguagePersistence } from '@owlmeans/client-i18n'
2
- import { CONSENT_EVENT, CONSENT_LANGUAGE_EVENT, functionalGranted } from '@owlmeans/web-consent'
3
1
  import type { ConsentOptions } from '@owlmeans/web-consent'
4
2
 
5
3
  /**
6
- * Bind the application's language to the visitor's cookie choice: a language is remembered on their
7
- * device only while they have granted `functional` storage.
4
+ * @deprecated The interface language is strictly necessary storage: `@owlmeans/client-i18n` writes
5
+ * it on every switch and reads it at every start, and `@owlmeans/consent` stores a language that
6
+ * arrives with a link — none of it waits for a cookie decision, so there is nothing left to bind.
8
7
  *
9
- * It does three things, and the order they take effect in is the point:
10
- *
11
- * 1. Installs the persistence guard in `@owlmeans/client-i18n` — so the language switcher (which
12
- * calls `setLanguage`) stops writing `owlmeans-lng` until functional is granted, and a stored
13
- * language counts as absent while it is not. **Call it BEFORE `prepareI18n`**: the initial
14
- * language is resolved from storage right there.
15
- * 2. On every consent change (`CONSENT_EVENT`) writes the choice the visitor made while storage was
16
- * refused, if it is allowed now — a person who picked a language, then answered the dialog with
17
- * "accept all", keeps it.
18
- * 3. On `CONSENT_LANGUAGE_EVENT` — a language that arrived with a link and was waiting for the grant —
19
- * switches to it now, unless the person has already picked one in this page's life: what they
20
- * chose outranks what a link carried, and that choice is re-written over the store's own write.
21
- *
22
- * Returns a function that undoes all three. Safe on the server (only the guard is installed).
8
+ * Kept as a no-op (returning a no-op cleanup) only so an application written against the earlier,
9
+ * consent-gated API — a generated target that floats to a newer release — still builds. Remove the
10
+ * call.
23
11
  */
24
- export const installConsentLanguage = (opts?: ConsentOptions): (() => void) => {
25
- setLanguagePersistence(() => functionalGranted(opts))
26
-
27
- if (typeof window === 'undefined') {
28
- return () => setLanguagePersistence(null)
29
- }
30
-
31
- const onConsent = (): void => {
32
- persistLanguage()
33
- }
34
- const onLanguage = (event: Event): void => {
35
- const carried = (event as CustomEvent<{ language?: string }>).detail?.language
36
- const chosen = getExplicitLanguage()
37
- const language = chosen ?? carried
38
- if (typeof language !== 'string' || language === '') {
39
- return
40
- }
41
- setLanguage(language).catch((error: unknown) => {
42
- console.error('[i18n] could not apply the language that was waiting for consent', error)
43
- })
44
- }
45
- window.addEventListener(CONSENT_EVENT, onConsent)
46
- window.addEventListener(CONSENT_LANGUAGE_EVENT, onLanguage)
47
-
48
- return () => {
49
- window.removeEventListener(CONSENT_EVENT, onConsent)
50
- window.removeEventListener(CONSENT_LANGUAGE_EVENT, onLanguage)
51
- setLanguagePersistence(null)
52
- }
53
- }
12
+ export const installConsentLanguage = (_opts?: ConsentOptions): (() => void) => () => undefined
package/src/exports.ts CHANGED
@@ -5,6 +5,7 @@ export { config } from '@owlmeans/client-context'
5
5
  export { service } from '@owlmeans/config'
6
6
  export { addWebService } from '@owlmeans/client-config'
7
7
  export { bind, bindAll, bindScreen, provideRequest, stab } from '@owlmeans/client-entrypoint'
8
+ export { appendPermissionDeniedToast, PermissionDeniedToast } from './permission/index.js'
8
9
  export type { ClientEntrypoint as Module } from '@owlmeans/client-entrypoint'
9
10
  export { route as croute } from '@owlmeans/client-route'
10
11
  export { route, frontend } from '@owlmeans/route'
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Дзеянне адхілена",
3
+ "description": "Сервер адхіліў дзеянне паводле бягучых правіл доступу. Калі вы чакалі доступу, звярніцеся да адміністратара.",
4
+ "technical-details": "Тэхнічныя падрабязнасці",
5
+ "endpoint": "Канцавы пункт",
6
+ "status": "Статус",
7
+ "incident": "Ідэнтыфікатар інцыдэнту",
8
+ "area-title": "Доступ забаронены",
9
+ "area-description": "Ваш уліковы запіс не можа адкрыць гэты раздзел. Калі вы чакалі доступу, звярніцеся да адміністратара.",
10
+ "area": "Раздзел",
11
+ "required-permissions": "Патрэбныя дазволы",
12
+ "logout": "Выйсці"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Aktion verweigert",
3
+ "description": "Der Server hat diese Aktion gemäß den aktuellen Berechtigungen verweigert. Wenn Sie Zugriff erwartet haben, fragen Sie eine Administratorin oder einen Administrator.",
4
+ "technical-details": "Technische Details",
5
+ "endpoint": "Endpunkt",
6
+ "status": "Status",
7
+ "incident": "Vorfall-ID",
8
+ "area-title": "Zugriff verweigert",
9
+ "area-description": "Ihr Konto kann diesen Bereich nicht öffnen. Wenn Sie Zugriff erwartet haben, wenden Sie sich an die Administration.",
10
+ "area": "Bereich",
11
+ "required-permissions": "Erforderliche Berechtigungen",
12
+ "logout": "Abmelden"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Action denied",
3
+ "description": "The server denied this action under the current permission rules. If you expected access, ask an administrator.",
4
+ "technical-details": "Technical details",
5
+ "endpoint": "Endpoint",
6
+ "status": "Status",
7
+ "incident": "Incident ID",
8
+ "area-title": "Access denied",
9
+ "area-description": "Your account cannot open this area. Ask an administrator if you expected access.",
10
+ "area": "Area",
11
+ "required-permissions": "Required permissions",
12
+ "logout": "Log out"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Acción denegada",
3
+ "description": "El servidor denegó esta acción según las reglas de permisos actuales. Si esperaba tener acceso, consulte a un administrador.",
4
+ "technical-details": "Detalles técnicos",
5
+ "endpoint": "Punto de acceso",
6
+ "status": "Estado",
7
+ "incident": "ID del incidente",
8
+ "area-title": "Acceso denegado",
9
+ "area-description": "Su cuenta no puede abrir esta sección. Si esperaba tener acceso, consulte a un administrador.",
10
+ "area": "Sección",
11
+ "required-permissions": "Permisos necesarios",
12
+ "logout": "Cerrar sesión"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Action refusée",
3
+ "description": "Le serveur a refusé cette action selon les autorisations actuelles. Si vous pensiez avoir accès, contactez un administrateur.",
4
+ "technical-details": "Détails techniques",
5
+ "endpoint": "Point de terminaison",
6
+ "status": "Statut",
7
+ "incident": "ID de l’incident",
8
+ "area-title": "Accès refusé",
9
+ "area-description": "Votre compte ne peut pas ouvrir cette section. Si vous pensiez avoir accès, contactez un administrateur.",
10
+ "area": "Section",
11
+ "required-permissions": "Autorisations requises",
12
+ "logout": "Se déconnecter"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Działanie odrzucone",
3
+ "description": "Serwer odrzucił to działanie zgodnie z obecnymi uprawnieniami. Jeśli oczekujesz dostępu, skontaktuj się z administratorem.",
4
+ "technical-details": "Szczegóły techniczne",
5
+ "endpoint": "Punkt końcowy",
6
+ "status": "Status",
7
+ "incident": "Identyfikator incydentu",
8
+ "area-title": "Odmowa dostępu",
9
+ "area-description": "Twoje konto nie może otworzyć tego obszaru. Jeśli oczekujesz dostępu, skontaktuj się z administratorem.",
10
+ "area": "Obszar",
11
+ "required-permissions": "Wymagane uprawnienia",
12
+ "logout": "Wyloguj się"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Действие отклонено",
3
+ "description": "Сервер отклонил действие согласно текущим правилам доступа. Если вам нужен доступ, обратитесь к администратору.",
4
+ "technical-details": "Технические подробности",
5
+ "endpoint": "Конечная точка",
6
+ "status": "Статус",
7
+ "incident": "Идентификатор инцидента",
8
+ "area-title": "Доступ запрещён",
9
+ "area-description": "Ваша учётная запись не может открыть этот раздел. Если вам нужен доступ, обратитесь к администратору.",
10
+ "area": "Раздел",
11
+ "required-permissions": "Необходимые разрешения",
12
+ "logout": "Выйти"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Дію відхилено",
3
+ "description": "Сервер відхилив цю дію згідно з поточними правилами доступу. Якщо ви очікували доступу, зверніться до адміністратора.",
4
+ "technical-details": "Технічні подробиці",
5
+ "endpoint": "Кінцева точка",
6
+ "status": "Статус",
7
+ "incident": "Ідентифікатор інциденту",
8
+ "area-title": "Доступ заборонено",
9
+ "area-description": "Ваш обліковий запис не може відкрити цей розділ. Якщо ви очікували доступу, зверніться до адміністратора.",
10
+ "area": "Розділ",
11
+ "required-permissions": "Потрібні дозволи",
12
+ "logout": "Вийти"
13
+ }
@@ -0,0 +1,13 @@
1
+ import { addI18nLib } from '@owlmeans/i18n'
2
+ import en from './i18n/en.json'
3
+ import pl from './i18n/pl.json'
4
+ import ru from './i18n/ru.json'
5
+ import be from './i18n/be.json'
6
+ import uk from './i18n/uk.json'
7
+ import es from './i18n/es.json'
8
+ import de from './i18n/de.json'
9
+ import fr from './i18n/fr.json'
10
+
11
+ for (const [language, chunk] of Object.entries({ en, pl, ru, be, uk, es, de, fr })) {
12
+ addI18nLib(language, 'permission-denied', chunk)
13
+ }
@@ -0,0 +1,27 @@
1
+ import './i18n.js'
2
+ import { createElement } from 'react'
3
+ import { toast } from 'sonner'
4
+ import type { BasicContext } from '@owlmeans/context'
5
+ import { ensureEntrypointFailureService } from '@owlmeans/client-entrypoint'
6
+ import { httpStatusOf, incidentIdOf, isAccessDenied } from '@owlmeans/api/status'
7
+ import { PermissionDeniedToast } from './toast.js'
8
+
9
+ export { PermissionDeniedToast } from './toast.js'
10
+
11
+ /** Opt-in adapter for the context's entrypoint failures; mount one Toaster in the app shell. */
12
+ export const appendPermissionDeniedToast = <T extends BasicContext<any>>(context: T): T => {
13
+ const seen = new Map<string, number>()
14
+ ensureEntrypointFailureService(context).registerPlugin({
15
+ alias: 'web-panel:permission-denied-toast',
16
+ onFailure: ({ alias, error }) => {
17
+ if (!isAccessDenied(error)) return
18
+ const now = Date.now()
19
+ if (now - (seen.get(alias) ?? 0) < 2000) return
20
+ seen.set(alias, now)
21
+ toast.warning(createElement(PermissionDeniedToast, {
22
+ alias, status: httpStatusOf(error), incidentId: incidentIdOf(error),
23
+ }))
24
+ },
25
+ })
26
+ return context
27
+ }
@@ -0,0 +1,25 @@
1
+ import type { FC } from 'react'
2
+ import { useI18nLib } from '@owlmeans/client-i18n'
3
+
4
+ export interface PermissionDeniedToastProps {
5
+ alias: string
6
+ status: number | null
7
+ incidentId: string | null
8
+ }
9
+
10
+ /** A refusal is actionable context for the user, with diagnostics outside the explanation. */
11
+ export const PermissionDeniedToast: FC<PermissionDeniedToastProps> = ({ alias, status, incidentId }) => {
12
+ const t = useI18nLib('permission-denied')
13
+ return <div className="space-y-2 max-w-sm text-sm">
14
+ <p className="font-semibold">{t('title')}</p>
15
+ <p>{t('description')}</p>
16
+ <details className="text-xs">
17
+ <summary className="cursor-pointer">{t('technical-details')}</summary>
18
+ <dl className="mt-2 grid grid-cols-[auto_1fr] gap-x-2 break-all">
19
+ <dt>{t('endpoint')}</dt><dd>{alias}</dd>
20
+ {status != null && <><dt>{t('status')}</dt><dd>{status}</dd></>}
21
+ {incidentId != null && <><dt>{t('incident')}</dt><dd>{incidentId}</dd></>}
22
+ </dl>
23
+ </details>
24
+ </div>
25
+ }
@@ -1,82 +1,31 @@
1
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
- import { persistLanguage, setLanguage, setLanguagePersistence } from '@owlmeans/client-i18n'
3
- import { CONSENT_EVENT, CONSENT_KEY, CONSENT_LANGUAGE_EVENT } from '@owlmeans/web-consent'
1
+ import { beforeEach, describe, expect, test } from 'bun:test'
2
+ import { setLanguage } from '@owlmeans/client-i18n'
4
3
  import { installConsentLanguage } from '../src/consent/language.js'
5
4
 
6
- /** A window that is just an event bus, and a localStorage that is a map: enough for the binding. */
7
- const env = () => {
5
+ describe('installConsentLanguage — a deprecated no-op', () => {
8
6
  const store = new Map<string, string>()
9
- const listeners = new Map<string, Set<(e: any) => void>>()
10
- ;(globalThis as any).localStorage = {
11
- getItem: (k: string) => store.get(k) ?? null,
12
- setItem: (k: string, v: string) => { store.set(k, v) },
13
- removeItem: (k: string) => { store.delete(k) },
14
- }
15
- ;(globalThis as any).window = {
16
- addEventListener: (t: string, f: any) => { (listeners.get(t) ?? listeners.set(t, new Set()).get(t)!).add(f) },
17
- removeEventListener: (t: string, f: any) => { listeners.get(t)?.delete(f) },
18
- dispatchEvent: (e: any) => { listeners.get(e.type)?.forEach(f => f(e)) },
19
- }
20
- ;(globalThis as any).CustomEvent = class { constructor(public type: string, public init: any) {} get detail() { return this.init?.detail } }
21
7
 
22
- return { store, listeners, emit: (type: string, detail?: unknown) => (globalThis as any).window.dispatchEvent(new (globalThis as any).CustomEvent(type, { detail })) }
23
- }
24
-
25
- describe('installConsentLanguage', () => {
26
- let e: ReturnType<typeof env>
27
- let off: () => void
28
- const record = (functional: boolean) => e.store.set(CONSENT_KEY, JSON.stringify({ v: 2, essential: true, functional, analytics: false, marketing: false }))
29
-
30
- beforeEach(() => { e = env(); off = installConsentLanguage() })
31
- afterEach(() => { off(); setLanguagePersistence(null); delete (globalThis as any).window })
32
-
33
- test('the switcher\'s choice is not remembered without a functional grant', async () => {
34
- await setLanguage('pl')
35
-
36
- expect(e.store.has('owlmeans-lng')).toBe(false)
8
+ beforeEach(() => {
9
+ store.clear()
10
+ ;(globalThis as any).localStorage = {
11
+ getItem: (k: string) => store.get(k) ?? null,
12
+ setItem: (k: string, v: string) => { store.set(k, v) },
13
+ removeItem: (k: string) => { store.delete(k) },
14
+ }
37
15
  })
38
16
 
39
- test('…and is written when the visitor grants functional, on the consent event', async () => {
40
- await setLanguage('pl')
41
- record(true)
42
- e.emit(CONSENT_EVENT, { record: {} })
17
+ test('returns a cleanup that can be called, more than once', () => {
18
+ const off = installConsentLanguage()
43
19
 
44
- expect(e.store.get('owlmeans-lng')).toBe('pl')
20
+ expect(typeof off).toBe('function')
21
+ expect(off()).toBeUndefined()
22
+ expect(off()).toBeUndefined()
45
23
  })
46
24
 
47
- test('a refusal writes nothing on the consent event', async () => {
25
+ test('changes nothing about the language: a choice is stored either way, whatever the decision', async () => {
26
+ installConsentLanguage()
48
27
  await setLanguage('pl')
49
- record(false)
50
- e.emit(CONSENT_EVENT, { record: {} })
51
-
52
- expect(e.store.has('owlmeans-lng')).toBe(false)
53
- })
54
-
55
- test('a language that was waiting for the grant is applied and stored when it arrives', async () => {
56
- record(true)
57
- e.emit(CONSENT_LANGUAGE_EVENT, { language: 'de' })
58
- await new Promise(resolve => setTimeout(resolve, 0))
59
-
60
- expect(e.store.get('owlmeans-lng')).toBe('de')
61
- })
62
-
63
- test('what the person picked in this page life outranks the language the link carried', async () => {
64
- await setLanguage('fr')
65
- record(true)
66
- e.emit(CONSENT_EVENT, { record: {} })
67
- e.emit(CONSENT_LANGUAGE_EVENT, { language: 'de' })
68
- await new Promise(resolve => setTimeout(resolve, 0))
69
-
70
- expect(e.store.get('owlmeans-lng')).toBe('fr')
71
- })
72
-
73
- test('the returned function removes the listeners and the guard', async () => {
74
- off()
75
- expect(e.listeners.get(CONSENT_EVENT)?.size ?? 0).toBe(0)
76
- expect(e.listeners.get(CONSENT_LANGUAGE_EVENT)?.size ?? 0).toBe(0)
77
28
 
78
- await setLanguage('es')
79
- expect(e.store.get('owlmeans-lng')).toBe('es')
80
- expect(persistLanguage()).toBe(false)
29
+ expect(store.get('owlmeans-lng')).toBe('pl')
81
30
  })
82
31
  })