@variamosple/variamos-components 0.2.5 → 0.3.0

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 (51) hide show
  1. package/README.md +27 -42
  2. package/dist/Common/Events.d.ts +3 -2
  3. package/dist/Common/Events.js +18 -11
  4. package/dist/Common/fieldOf.d.ts +2 -0
  5. package/dist/Common/fieldOf.js +7 -0
  6. package/dist/Common/isAllowedFor.d.ts +7 -0
  7. package/dist/Common/isAllowedFor.js +9 -0
  8. package/dist/Components/AuthWrapper/index.js +6 -6
  9. package/dist/Components/ConfirmationModal/index.js +3 -3
  10. package/dist/Components/Footer/index.js +1 -1
  11. package/dist/Components/Header/BugFormModal.d.ts +2 -0
  12. package/dist/Components/Header/BugFormModal.js +39 -37
  13. package/dist/Components/Header/HeaderOptions.js +80 -86
  14. package/dist/Components/Header/MenuItem.js +24 -34
  15. package/dist/Components/Header/SubHeader.js +17 -29
  16. package/dist/Components/Header/index.js +25 -13
  17. package/dist/Components/Header/submitBugReport.d.ts +15 -0
  18. package/dist/Components/Header/submitBugReport.js +23 -0
  19. package/dist/Components/NotAuthorized/index.js +1 -1
  20. package/dist/Components/Notifications/NotificationBell.js +10 -10
  21. package/dist/Components/Notifications/NotificationCard.d.ts +9 -0
  22. package/dist/Components/Notifications/NotificationCard.js +81 -0
  23. package/dist/Components/Notifications/NotificationDrawer.js +139 -191
  24. package/dist/Components/Notifications/NotificationSettings.js +32 -28
  25. package/dist/Components/Notifications/mutedEventTypes.js +9 -9
  26. package/dist/Components/Notifications/notificationText.d.ts +7 -0
  27. package/dist/Components/Notifications/notificationText.js +38 -0
  28. package/dist/Components/Paginator/index.js +22 -22
  29. package/dist/Components/ProtectedRoute/index.js +18 -18
  30. package/dist/Components/index.d.ts +1 -1
  31. package/dist/Components/index.js +10 -10
  32. package/dist/Context/AnalyticsContext.d.ts +2 -2
  33. package/dist/Context/AnalyticsContext.js +15 -15
  34. package/dist/Context/MenuContext.js +39 -47
  35. package/dist/Context/NotificationContext.d.ts +2 -14
  36. package/dist/Context/NotificationContext.js +393 -458
  37. package/dist/Context/SessionContext.js +74 -81
  38. package/dist/Context/index.d.ts +1 -1
  39. package/dist/Context/index.js +11 -11
  40. package/dist/Context/notificationAccessToken.js +13 -13
  41. package/dist/Context/notificationApi.d.ts +43 -0
  42. package/dist/Context/notificationApi.js +83 -0
  43. package/dist/Context/useNotificationState.d.ts +35 -0
  44. package/dist/Context/useNotificationState.js +92 -0
  45. package/dist/Hooks/useDebouncedValue.js +20 -16
  46. package/dist/Hooks/usePaginatedQuery.js +14 -20
  47. package/dist/Hooks/useQuery.js +6 -6
  48. package/dist/Model/ResponseModel.d.ts +2 -2
  49. package/dist/Model/ResponseModel.js +9 -4
  50. package/dist/main.js +21 -21
  51. package/package.json +19 -17
package/README.md CHANGED
@@ -1,50 +1,35 @@
1
- # React + TypeScript + Vite
1
+ # @variamosple/variamos-components
2
2
 
3
- This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
3
+ React components and contexts shared by the VariaMos front ends: the session, the menu, the header with its user menu and bug report form, the notification bell and drawer, and a few page helpers (paginator, protected route, confirmation modal).
4
4
 
5
- Currently, two official plugins are available:
5
+ ## Install
6
6
 
7
- - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8
- - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
7
+ ```bash
8
+ npm install @variamosple/variamos-components
9
+ ```
9
10
 
10
- ## Expanding the ESLint configuration
11
+ Peer dependencies: `react` and `react-dom` 18 or 19, `react-bootstrap` 2, `react-bootstrap-icons` 1.
11
12
 
12
- If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
13
+ ```tsx
14
+ import { Header, SessionProvider } from "@variamosple/variamos-components";
15
+ ```
13
16
 
14
- - Configure the top-level `parserOptions` property like this:
17
+ The user menu of the header has the class `variamos-header-options`; its id is generated, so tests and styles should use the class.
15
18
 
16
- ```js
17
- export default tseslint.config({
18
- languageOptions: {
19
- // other options...
20
- parserOptions: {
21
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
22
- tsconfigRootDir: import.meta.dirname,
23
- },
24
- },
25
- })
26
- ```
19
+ ## Development
27
20
 
28
- - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
29
- - Optionally add `...tseslint.configs.stylisticTypeChecked`
30
- - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
31
-
32
- ```js
33
- // eslint.config.js
34
- import react from 'eslint-plugin-react'
35
-
36
- export default tseslint.config({
37
- // Set the react version
38
- settings: { react: { version: '18.3' } },
39
- plugins: {
40
- // Add the react plugin
41
- react,
42
- },
43
- rules: {
44
- // other rules...
45
- // Enable its recommended rules
46
- ...react.configs.recommended.rules,
47
- ...react.configs['jsx-runtime'].rules,
48
- },
49
- })
50
- ```
21
+ Requires Node 26 (`.nvmrc`, `.mise.toml`). `npm install` sets up the Git hooks: Biome and cspell on the staged files before a commit, the conventional commit format, and typecheck, lint and tests before a push.
22
+
23
+ | Command | What it does |
24
+ | --- | --- |
25
+ | `npm run dev` | Demo page (`src/`), built from the library sources |
26
+ | `npm run typecheck` | Types of the library, the demo and the Vite configuration |
27
+ | `npm run lint` | Biome (shared configuration of the VariaMos admin module) and the check that no rule is silenced in a comment |
28
+ | `npm run format` | Formats the code with Biome |
29
+ | `npm run spell` | Spelling (cspell) |
30
+ | `npm run find-deadcode` | Unused files, exports and dependencies (knip) |
31
+ | `npm test` | Unit tests (Vitest) |
32
+ | `npm run build` | Builds `dist/` |
33
+ | `npm run check-package` | What `npm pack` would publish, then publint |
34
+
35
+ The CI runs the same commands on every pull request. The package is published to npm by hand.
@@ -1,5 +1,6 @@
1
- declare function subscribe<T>(eventName: string, listener: (event: CustomEvent<T>) => void): void;
2
- declare function unsubscribe<T>(eventName: string, listener: (event: CustomEvent<T>) => void): void;
1
+ type Listener<T> = (event: CustomEvent<T>) => void;
2
+ declare function subscribe<T>(eventName: string, listener: Listener<T>): void;
3
+ declare function unsubscribe<T>(eventName: string, listener: Listener<T>): void;
3
4
  declare function publish<T>(eventName: string, data: T): void;
4
5
  export declare const Events: Readonly<{
5
6
  subscribe: typeof subscribe;
@@ -1,18 +1,25 @@
1
- function c(e, n) {
2
- document.addEventListener(e, n);
1
+ const o = /* @__PURE__ */ new WeakMap();
2
+ function s(t) {
3
+ let e = o.get(t);
4
+ return e || (e = (n) => {
5
+ n instanceof CustomEvent && t(n);
6
+ }, o.set(t, e)), e;
3
7
  }
4
- function s(e, n) {
5
- document.removeEventListener(e, n);
8
+ function c(t, e) {
9
+ document.addEventListener(t, s(e));
6
10
  }
7
- function o(e, n) {
8
- const t = new CustomEvent(e, { detail: n });
9
- document.dispatchEvent(t);
11
+ function i(t, e) {
12
+ document.removeEventListener(t, s(e));
10
13
  }
11
- const u = Object.freeze({
14
+ function u(t, e) {
15
+ const n = new CustomEvent(t, { detail: e });
16
+ document.dispatchEvent(n);
17
+ }
18
+ const d = Object.freeze({
12
19
  subscribe: c,
13
- unsubscribe: s,
14
- publish: o
20
+ unsubscribe: i,
21
+ publish: u
15
22
  });
16
23
  export {
17
- u as Events
24
+ d as Events
18
25
  };
@@ -0,0 +1,2 @@
1
+ /** A field of a JSON value read from the network; undefined if absent. */
2
+ export declare const fieldOf: (value: unknown, key: string) => unknown;
@@ -0,0 +1,7 @@
1
+ const f = (t, i) => {
2
+ var n;
3
+ return typeof t == "object" && t !== null && i in t ? (n = Object.entries(t).find(([o]) => o === i)) == null ? void 0 : n[1] : void 0;
4
+ };
5
+ export {
6
+ f as fieldOf
7
+ };
@@ -0,0 +1,7 @@
1
+ import { MenuItemBase, SessionUser } from '../Model';
2
+ /**
3
+ * Whether the user may see a menu entry: an entry without roles nor
4
+ * permissions is for everyone; otherwise the user needs one of the roles
5
+ * listed and one of the permissions listed.
6
+ */
7
+ export declare function isAllowedFor(item: Pick<MenuItemBase, "allowedRoles" | "allowedPermissions">, user: Pick<SessionUser, "roles" | "permissions"> | null | undefined): boolean;
@@ -0,0 +1,9 @@
1
+ function m(s, n) {
2
+ const { allowedRoles: o, allowedPermissions: t } = s;
3
+ if (!(o != null && o.length) && !(t != null && t.length)) return !0;
4
+ const c = (n == null ? void 0 : n.roles) ?? [], e = (n == null ? void 0 : n.permissions) ?? [], h = !o || o.some((i) => c.includes(i)), l = !t || t.some((i) => e.includes(i));
5
+ return h && l;
6
+ }
7
+ export {
8
+ m as isAllowedFor
9
+ };
@@ -1,17 +1,17 @@
1
1
  import { jsx as o, Fragment as f } from "react/jsx-runtime";
2
- import p, { useEffect as m } from "react";
2
+ import { useState as p, useEffect as m } from "react";
3
3
  import { Spinner as h } from "react-bootstrap";
4
4
  import "../../Context/AnalyticsContext.js";
5
5
  import "../../Context/MenuContext.js";
6
- import { useRouter as u } from "../../Context/RouterContext.js";
7
- import { useSession as g } from "../../Context/SessionContext.js";
8
6
  import "../../Context/NotificationContext.js";
9
- import { addressWithoutToken as l } from "./addressWithoutToken.js";
7
+ import { useRouter as u } from "../../Context/RouterContext.js";
8
+ import { useSession as l } from "../../Context/SessionContext.js";
9
+ import { addressWithoutToken as w } from "./addressWithoutToken.js";
10
10
  const j = ({
11
11
  redirectPath: i,
12
12
  children: c
13
13
  }) => {
14
- const [n, d] = p.useState(!1), { isAuthenticated: t, isLoading: r } = g(), { navigate: s } = u();
14
+ const [n, d] = p(!1), { isAuthenticated: t, isLoading: r } = l(), { navigate: s } = u();
15
15
  return m(() => {
16
16
  if (!r && !t && !n) {
17
17
  const e = window.location.href, a = new URLSearchParams();
@@ -21,7 +21,7 @@ const j = ({
21
21
  );
22
22
  }
23
23
  }, [t, r, i, s, n]), m(() => {
24
- const e = l(window.location.href);
24
+ const e = w(window.location.href);
25
25
  e && window.history.replaceState({}, "", e);
26
26
  }, []), r || !t ? /* @__PURE__ */ o("div", { className: "d-flex justify-content-center align-items-center w-100 h-100", children: /* @__PURE__ */ o(
27
27
  h,
@@ -13,8 +13,8 @@ function u({
13
13
  onCancel: r,
14
14
  onConfirm: t,
15
15
  message: c,
16
- confirmLabel: l = "Accept",
17
- confirmButtonVariant: d = "primary",
16
+ confirmLabel: d = "Accept",
17
+ confirmButtonVariant: l = "primary",
18
18
  cancelButtonVariant: s = "secondary",
19
19
  cancelLabel: f = "Cancel"
20
20
  }) {
@@ -22,7 +22,7 @@ function u({
22
22
  /* @__PURE__ */ o(n.Body, { children: c }),
23
23
  /* @__PURE__ */ e(n.Footer, { children: [
24
24
  /* @__PURE__ */ o(i, { variant: s, onClick: r, children: f }),
25
- /* @__PURE__ */ o(i, { variant: d, onClick: t, children: l })
25
+ /* @__PURE__ */ o(i, { variant: l, onClick: t, children: d })
26
26
  ] })
27
27
  ] });
28
28
  }
@@ -4,7 +4,7 @@ import '../../assets/index.css';const e = "_footer_cvhpi_1", s = {
4
4
  }, i = ({ version: o = "" }) => /* @__PURE__ */ r("footer", { className: `container-fluid ${s.footer}`, children: /* @__PURE__ */ r("div", { className: "row copyright", children: /* @__PURE__ */ t("p", { className: "mb-0", children: [
5
5
  "©",
6
6
  " Copyright 2023 VariaMos.",
7
- o && ` ${o}`
7
+ o ? ` ${o}` : null
8
8
  ] }) }) });
9
9
  export {
10
10
  i as Footer
@@ -9,6 +9,8 @@ interface BugFormModalProps {
9
9
  }, file?: File) => void;
10
10
  categories: string[];
11
11
  isSubmitting: boolean;
12
+ /** Why the last submission failed, shown above the form. */
13
+ errorMessage?: string | null;
12
14
  }
13
15
  export declare const BugFormModal: FC<BugFormModalProps>;
14
16
  export {};
@@ -1,23 +1,24 @@
1
- import { jsxs as l, jsx as r } from "react/jsx-runtime";
2
- import { useState as a, useEffect as B } from "react";
3
- import { Modal as o, Form as t, Button as y } from "react-bootstrap";
4
- const q = ({
1
+ import { jsxs as l, jsx as e } from "react/jsx-runtime";
2
+ import { useState as a, useEffect as N } from "react";
3
+ import { Modal as o, Form as t, Alert as k, Button as y } from "react-bootstrap";
4
+ const G = ({
5
5
  show: i,
6
6
  onHide: d,
7
7
  onSubmit: C,
8
8
  categories: n,
9
- isSubmitting: c
9
+ isSubmitting: c,
10
+ errorMessage: s
10
11
  }) => {
11
- const [s, u] = a(""), [h, p] = a(""), [m, g] = a(""), [v, b] = a(null), [F, f] = a(!1);
12
- B(() => {
13
- i && (u(""), p(""), g(n[0] || "Other"), b(null), f(!1));
12
+ const [u, h] = a(""), [p, m] = a(""), [g, b] = a(""), [F, f] = a(null), [x, v] = a(!1);
13
+ N(() => {
14
+ i && (h(""), m(""), b(n[0] || "Other"), f(null), v(!1));
14
15
  }, [i, n]);
15
- const x = (e) => {
16
- if (e.preventDefault(), e.currentTarget.checkValidity() === !1) {
17
- e.stopPropagation(), f(!0);
16
+ const B = (r) => {
17
+ if (r.preventDefault(), r.currentTarget.checkValidity() === !1) {
18
+ r.stopPropagation(), v(!0);
18
19
  return;
19
20
  }
20
- C({ title: s, description: h, category: m }, v || void 0);
21
+ C({ title: u, description: p, category: g }, F ?? void 0);
21
22
  };
22
23
  return /* @__PURE__ */ l(
23
24
  o,
@@ -28,67 +29,68 @@ const q = ({
28
29
  keyboard: !1,
29
30
  centered: !0,
30
31
  children: [
31
- /* @__PURE__ */ r(o.Header, { closeButton: !0, children: /* @__PURE__ */ r(o.Title, { children: "Report a New Bug" }) }),
32
- /* @__PURE__ */ l(t, { noValidate: !0, validated: F, onSubmit: x, children: [
32
+ /* @__PURE__ */ e(o.Header, { closeButton: !0, children: /* @__PURE__ */ e(o.Title, { children: "Report a New Bug" }) }),
33
+ /* @__PURE__ */ l(t, { noValidate: !0, validated: x, onSubmit: B, children: [
33
34
  /* @__PURE__ */ l(o.Body, { children: [
35
+ s ? /* @__PURE__ */ e(k, { variant: "danger", role: "alert", children: s }) : null,
34
36
  /* @__PURE__ */ l(t.Group, { className: "mb-3", controlId: "bugTitle", children: [
35
- /* @__PURE__ */ r(t.Label, { children: "Title *" }),
36
- /* @__PURE__ */ r(
37
+ /* @__PURE__ */ e(t.Label, { children: "Title *" }),
38
+ /* @__PURE__ */ e(
37
39
  t.Control,
38
40
  {
39
41
  required: !0,
40
42
  type: "text",
41
43
  placeholder: "e.g. Build error on auth routes",
42
- value: s,
43
- onChange: (e) => u(e.target.value),
44
+ value: u,
45
+ onChange: (r) => h(r.target.value),
44
46
  maxLength: 100
45
47
  }
46
48
  ),
47
- /* @__PURE__ */ r(t.Control.Feedback, { type: "invalid", children: "Title is required (max 100 characters)" })
49
+ /* @__PURE__ */ e(t.Control.Feedback, { type: "invalid", children: "Title is required (max 100 characters)" })
48
50
  ] }),
49
51
  /* @__PURE__ */ l(t.Group, { className: "mb-3", controlId: "bugDescription", children: [
50
- /* @__PURE__ */ r(t.Label, { children: "Description *" }),
51
- /* @__PURE__ */ r(
52
+ /* @__PURE__ */ e(t.Label, { children: "Description *" }),
53
+ /* @__PURE__ */ e(
52
54
  t.Control,
53
55
  {
54
56
  required: !0,
55
57
  as: "textarea",
56
58
  rows: 4,
57
59
  placeholder: "Describe step-by-step how to reproduce the bug...",
58
- value: h,
59
- onChange: (e) => p(e.target.value)
60
+ value: p,
61
+ onChange: (r) => m(r.target.value)
60
62
  }
61
63
  ),
62
- /* @__PURE__ */ r(t.Control.Feedback, { type: "invalid", children: "Description is required" })
64
+ /* @__PURE__ */ e(t.Control.Feedback, { type: "invalid", children: "Description is required" })
63
65
  ] }),
64
- /* @__PURE__ */ r("div", { className: "row", children: /* @__PURE__ */ r("div", { className: "col-md-12", children: /* @__PURE__ */ l(t.Group, { className: "mb-3", controlId: "bugCategory", children: [
65
- /* @__PURE__ */ r(t.Label, { children: "Category *" }),
66
- /* @__PURE__ */ r(
66
+ /* @__PURE__ */ e("div", { className: "row", children: /* @__PURE__ */ e("div", { className: "col-md-12", children: /* @__PURE__ */ l(t.Group, { className: "mb-3", controlId: "bugCategory", children: [
67
+ /* @__PURE__ */ e(t.Label, { children: "Category *" }),
68
+ /* @__PURE__ */ e(
67
69
  t.Select,
68
70
  {
69
71
  required: !0,
70
- value: m,
71
- onChange: (e) => g(e.target.value),
72
- children: n.map((e) => /* @__PURE__ */ r("option", { value: e, children: e }, e))
72
+ value: g,
73
+ onChange: (r) => b(r.target.value),
74
+ children: n.map((r) => /* @__PURE__ */ e("option", { value: r, children: r }, r))
73
75
  }
74
76
  )
75
77
  ] }) }) }),
76
78
  /* @__PURE__ */ l(t.Group, { className: "mb-3", controlId: "bugFile", children: [
77
- /* @__PURE__ */ r(t.Label, { children: "Attachment (Optional)" }),
78
- /* @__PURE__ */ r(
79
+ /* @__PURE__ */ e(t.Label, { children: "Attachment (Optional)" }),
80
+ /* @__PURE__ */ e(
79
81
  t.Control,
80
82
  {
81
83
  type: "file",
82
- onChange: (e) => {
83
- e.target.files && e.target.files.length > 0 && b(e.target.files[0]);
84
+ onChange: (r) => {
85
+ r.target.files && r.target.files.length > 0 && f(r.target.files[0]);
84
86
  }
85
87
  }
86
88
  )
87
89
  ] })
88
90
  ] }),
89
91
  /* @__PURE__ */ l(o.Footer, { children: [
90
- /* @__PURE__ */ r(y, { variant: "secondary", onClick: d, disabled: c, children: "Cancel" }),
91
- /* @__PURE__ */ r(y, { variant: "primary", type: "submit", disabled: c, children: c ? "Creating..." : "Report Bug" })
92
+ /* @__PURE__ */ e(y, { variant: "secondary", onClick: d, disabled: c, children: "Cancel" }),
93
+ /* @__PURE__ */ e(y, { variant: "primary", type: "submit", disabled: c, children: c ? "Creating..." : "Report Bug" })
92
94
  ] })
93
95
  ] })
94
96
  ]
@@ -96,5 +98,5 @@ const q = ({
96
98
  );
97
99
  };
98
100
  export {
99
- q as BugFormModal
101
+ G as BugFormModal
100
102
  };
@@ -1,96 +1,90 @@
1
- import { jsxs as S, Fragment as F, jsx as p } from "react/jsx-runtime";
2
- import { useState as h, useEffect as T, useMemo as j } from "react";
3
- import { NavDropdown as g } from "react-bootstrap";
1
+ import { jsxs as f, Fragment as T, jsx as r } from "react/jsx-runtime";
2
+ import { useId as k, useState as m, useCallback as v, useEffect as C, useMemo as O } from "react";
3
+ import { NavDropdown as i } from "react-bootstrap";
4
+ import { Events as h } from "../../Common/Events.js";
5
+ import { isAllowedFor as U } from "../../Common/isAllowedFor.js";
4
6
  import "../../Context/AnalyticsContext.js";
5
- import { useMenu as C } from "../../Context/MenuContext.js";
6
- import { useRouter as b } from "../../Context/RouterContext.js";
7
- import { useSession as x } from "../../Context/SessionContext.js";
7
+ import { useMenu as j } from "../../Context/MenuContext.js";
8
8
  import "../../Context/NotificationContext.js";
9
- import { mapToWebTarget as L } from "../../Model/Router.js";
10
- import { Events as y } from "../../Common/Events.js";
11
- import { BugFormModal as U } from "./BugFormModal.js";
12
- const u = {}, Q = ({ adminApiUrl: B }) => {
13
- const { user: o, isAuthenticated: m, logout: P } = x(), { options: l } = C(), { navigate: A } = b(), [R, c] = h(!1), [k] = h([
14
- "Editor",
15
- "Model",
16
- "Language",
17
- "Project",
18
- "Simulation",
19
- "Account/Security",
20
- "Other"
21
- ]), [v, f] = h(!1), D = B || (u == null ? void 0 : u.VITE_ADMIN_API_URL) || "/v1";
22
- T(() => {
23
- const t = () => {
24
- c(!0);
25
- };
26
- return y.subscribe("openReportBugModal", t), () => {
27
- y.unsubscribe("openReportBugModal", t);
28
- };
9
+ import { useRouter as x } from "../../Context/RouterContext.js";
10
+ import { useSession as D } from "../../Context/SessionContext.js";
11
+ import { mapToWebTarget as F } from "../../Model/Router.js";
12
+ import { BugFormModal as L } from "./BugFormModal.js";
13
+ import { submitBugReport as N } from "./submitBugReport.js";
14
+ const c = {}, y = [
15
+ "Editor",
16
+ "Model",
17
+ "Language",
18
+ "Project",
19
+ "Simulation",
20
+ "Account/Security",
21
+ "Other"
22
+ ], $ = ({
23
+ adminApiUrl: b
24
+ }) => {
25
+ const { user: o, isAuthenticated: n, logout: S } = D(), { options: d } = j(), { navigate: M } = x(), I = k(), [B, s] = m(!1), [E, p] = m(!1), [A, l] = m(null), w = b || (c == null ? void 0 : c.VITE_ADMIN_API_URL) || "/v1", e = v(() => {
26
+ l(null), s(!0);
29
27
  }, []);
30
- const E = async (t, d) => {
31
- f(!0);
32
- try {
33
- const e = new FormData();
34
- e.append("title", t.title), e.append("description", t.description), e.append("priority", "medium"), e.append("category", t.category), o != null && o.email && e.append("reporterEmail", o.email), d && e.append("file", d);
35
- let r = D.replace(/\/v1$/, "");
36
- r.endsWith("/") && (r = r.slice(0, -1));
37
- const s = {}, n = localStorage.getItem("authToken");
38
- n && (s.Authorization = `Bearer ${n}`);
39
- const a = await fetch(`${r}/bugs`, {
40
- method: "POST",
41
- headers: s,
42
- body: e,
43
- credentials: "include"
44
- }), w = await a.json();
45
- f(!1), a.ok && !w.errorCode ? c(!1) : alert(w.message || "Failed to submit bug. Please try again.");
46
- } catch (e) {
47
- f(!1), alert("Failed to submit bug. Please try again."), console.error(e);
48
- }
49
- }, i = j(() => {
50
- const t = (o == null ? void 0 : o.permissions) || [], d = (o == null ? void 0 : o.roles) || [];
51
- return l != null && l.length ? l.filter((e) => {
52
- var s, n;
53
- if (!((s = e == null ? void 0 : e.allowedPermissions) != null && s.length) && !((n = e == null ? void 0 : e.allowedRoles) != null && n.length))
54
- return !0;
55
- let r = !0;
56
- return e.allowedRoles && (r = r && e.allowedRoles.some((a) => d.includes(a))), e.allowedPermissions && (r = r && e.allowedPermissions.some(
57
- (a) => t.includes(a)
58
- )), r;
59
- }) : [];
60
- }, [l, o]);
61
- return !(i != null && i.length) && !m ? null : /* @__PURE__ */ S(F, { children: [
62
- /* @__PURE__ */ S(g, { title: (o == null ? void 0 : o.name) || "Options", id: "nav-dropdown", children: [
63
- i == null ? void 0 : i.map((t) => /* @__PURE__ */ p(
64
- g.Item,
65
- {
66
- onClick: () => {
67
- if (t.location.endsWith("#report-bug")) {
68
- c(!0);
69
- return;
70
- }
71
- A(t.location, {
72
- target: L(t.target)
73
- });
74
- },
75
- children: t.title
76
- },
77
- t.title
78
- )),
79
- m && !!(i != null && i.length) && /* @__PURE__ */ p(g.Divider, {}),
80
- m && /* @__PURE__ */ p(g.Item, { onClick: P, children: "Logout" })
81
- ] }),
82
- /* @__PURE__ */ p(
83
- U,
28
+ C(() => (h.subscribe("openReportBugModal", e), () => {
29
+ h.unsubscribe(
30
+ "openReportBugModal",
31
+ e
32
+ );
33
+ }), [e]);
34
+ const R = async (t, u) => {
35
+ p(!0), l(null);
36
+ const g = await N(t, u, {
37
+ adminApiUrl: w,
38
+ reporterEmail: o == null ? void 0 : o.email,
39
+ authToken: localStorage.getItem("authToken")
40
+ });
41
+ p(!1), g ? l(g) : s(!1);
42
+ }, a = O(
43
+ () => (d ?? []).filter((t) => U(t, o)),
44
+ [d, o]
45
+ );
46
+ return a.length === 0 && !n ? null : /* @__PURE__ */ f(T, { children: [
47
+ /* @__PURE__ */ f(
48
+ i,
84
49
  {
85
- show: R,
86
- onHide: () => c(!1),
87
- onSubmit: E,
88
- categories: k,
89
- isSubmitting: v
50
+ title: (o == null ? void 0 : o.name) || "Options",
51
+ id: I,
52
+ className: "variamos-header-options",
53
+ children: [
54
+ a.map((t) => /* @__PURE__ */ r(
55
+ i.Item,
56
+ {
57
+ onClick: () => {
58
+ if (t.location.endsWith("#report-bug")) {
59
+ e();
60
+ return;
61
+ }
62
+ M(t.location, {
63
+ target: F(t.target)
64
+ });
65
+ },
66
+ children: t.title
67
+ },
68
+ t.title
69
+ )),
70
+ n && a.length > 0 ? /* @__PURE__ */ r(i.Divider, {}) : null,
71
+ n ? /* @__PURE__ */ r(i.Item, { onClick: S, children: "Logout" }) : null
72
+ ]
73
+ }
74
+ ),
75
+ /* @__PURE__ */ r(
76
+ L,
77
+ {
78
+ show: B,
79
+ onHide: () => s(!1),
80
+ onSubmit: (t, u) => void R(t, u),
81
+ categories: y,
82
+ isSubmitting: E,
83
+ errorMessage: A
90
84
  }
91
85
  )
92
86
  ] });
93
87
  };
94
88
  export {
95
- Q as HeaderOptions
89
+ $ as HeaderOptions
96
90
  };
@@ -1,51 +1,41 @@
1
- import { jsx as s } from "react/jsx-runtime";
2
- import { useMemo as h } from "react";
3
- import { Nav as v, NavDropdown as d } from "react-bootstrap";
1
+ import { jsx as i } from "react/jsx-runtime";
2
+ import { useMemo as c } from "react";
3
+ import { Nav as g, NavDropdown as p } from "react-bootstrap";
4
+ import { isAllowedFor as k } from "../../Common/isAllowedFor.js";
4
5
  import "../../Context/AnalyticsContext.js";
5
6
  import "../../Context/MenuContext.js";
6
- import { useRouter as C } from "../../Context/RouterContext.js";
7
- import { useSession as N } from "../../Context/SessionContext.js";
8
7
  import "../../Context/NotificationContext.js";
9
- import { mapToWebTarget as m } from "../../Model/Router.js";
10
- const z = ({
11
- title: e,
12
- type: w,
13
- location: g,
14
- target: k,
8
+ import { useRouter as u } from "../../Context/RouterContext.js";
9
+ import { useSession as d } from "../../Context/SessionContext.js";
10
+ import { mapToWebTarget as s } from "../../Model/Router.js";
11
+ const D = ({
12
+ title: n,
13
+ type: f,
14
+ location: e,
15
+ target: l,
15
16
  children: t
16
17
  }) => {
17
- const { user: r } = N(), { navigate: p } = C(), a = h(() => {
18
- const i = (r == null ? void 0 : r.permissions) || [], R = (r == null ? void 0 : r.roles) || [];
19
- return t ? t.filter((o) => {
20
- var c, f;
21
- if (!((c = o == null ? void 0 : o.allowedPermissions) != null && c.length) && !((f = o == null ? void 0 : o.allowedRoles) != null && f.length))
22
- return !0;
23
- let l = !0;
24
- return o.allowedRoles && (l = l && o.allowedRoles.some((n) => R.includes(n))), o.allowedPermissions && (l = l && o.allowedPermissions.some(
25
- (n) => i.includes(n)
26
- )), l;
27
- }) : [];
28
- }, [t, r]);
29
- return w === "location" ? /* @__PURE__ */ s(
30
- v.Link,
18
+ const { user: m } = d(), { navigate: a } = u(), r = c(() => t ? t.filter((o) => k(o, m)) : [], [t, m]);
19
+ return f === "location" && e ? /* @__PURE__ */ i(
20
+ g.Link,
31
21
  {
32
22
  as: "button",
33
23
  className: "btn btn-link",
34
- onClick: () => p(g, { target: m(k) }),
35
- children: e
24
+ onClick: () => a(e, { target: s(l) }),
25
+ children: n
36
26
  }
37
- ) : /* @__PURE__ */ s(d, { title: e, children: a == null ? void 0 : a.map((i) => /* @__PURE__ */ s(
38
- d.Item,
27
+ ) : /* @__PURE__ */ i(p, { title: n, children: r == null ? void 0 : r.map((o) => /* @__PURE__ */ i(
28
+ p.Item,
39
29
  {
40
30
  as: "button",
41
- onClick: () => p(i.location, {
42
- target: m(i.target)
31
+ onClick: () => a(o.location, {
32
+ target: s(o.target)
43
33
  }),
44
- children: i.title
34
+ children: o.title
45
35
  },
46
- i.title
36
+ o.title
47
37
  )) });
48
38
  };
49
39
  export {
50
- z as MenuItemComponent
40
+ D as MenuItemComponent
51
41
  };