@variamosple/variamos-components 0.2.4 → 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 (53) 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/addressWithoutToken.d.ts +9 -0
  9. package/dist/Components/AuthWrapper/addressWithoutToken.js +7 -0
  10. package/dist/Components/AuthWrapper/index.js +18 -17
  11. package/dist/Components/ConfirmationModal/index.js +3 -3
  12. package/dist/Components/Footer/index.js +1 -1
  13. package/dist/Components/Header/BugFormModal.d.ts +2 -0
  14. package/dist/Components/Header/BugFormModal.js +39 -37
  15. package/dist/Components/Header/HeaderOptions.js +80 -86
  16. package/dist/Components/Header/MenuItem.js +24 -34
  17. package/dist/Components/Header/SubHeader.js +17 -29
  18. package/dist/Components/Header/index.js +25 -13
  19. package/dist/Components/Header/submitBugReport.d.ts +15 -0
  20. package/dist/Components/Header/submitBugReport.js +23 -0
  21. package/dist/Components/NotAuthorized/index.js +1 -1
  22. package/dist/Components/Notifications/NotificationBell.js +10 -10
  23. package/dist/Components/Notifications/NotificationCard.d.ts +9 -0
  24. package/dist/Components/Notifications/NotificationCard.js +81 -0
  25. package/dist/Components/Notifications/NotificationDrawer.js +139 -191
  26. package/dist/Components/Notifications/NotificationSettings.js +32 -28
  27. package/dist/Components/Notifications/mutedEventTypes.js +9 -9
  28. package/dist/Components/Notifications/notificationText.d.ts +7 -0
  29. package/dist/Components/Notifications/notificationText.js +38 -0
  30. package/dist/Components/Paginator/index.js +22 -22
  31. package/dist/Components/ProtectedRoute/index.js +18 -18
  32. package/dist/Components/index.d.ts +1 -1
  33. package/dist/Components/index.js +10 -10
  34. package/dist/Context/AnalyticsContext.d.ts +2 -2
  35. package/dist/Context/AnalyticsContext.js +15 -15
  36. package/dist/Context/MenuContext.js +39 -47
  37. package/dist/Context/NotificationContext.d.ts +2 -14
  38. package/dist/Context/NotificationContext.js +393 -458
  39. package/dist/Context/SessionContext.js +74 -81
  40. package/dist/Context/index.d.ts +1 -1
  41. package/dist/Context/index.js +11 -11
  42. package/dist/Context/notificationAccessToken.js +13 -13
  43. package/dist/Context/notificationApi.d.ts +43 -0
  44. package/dist/Context/notificationApi.js +83 -0
  45. package/dist/Context/useNotificationState.d.ts +35 -0
  46. package/dist/Context/useNotificationState.js +92 -0
  47. package/dist/Hooks/useDebouncedValue.js +20 -16
  48. package/dist/Hooks/usePaginatedQuery.js +14 -20
  49. package/dist/Hooks/useQuery.js +6 -6
  50. package/dist/Model/ResponseModel.d.ts +2 -2
  51. package/dist/Model/ResponseModel.js +9 -4
  52. package/dist/main.js +21 -21
  53. package/package.json +22 -15
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
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The address without its authToken parameter, or null when it has none.
3
+ *
4
+ * The token is never read from an address: a link could otherwise force a
5
+ * session chosen by someone else (session fixation). The session lives in
6
+ * the httpOnly cookie set by the admin; a token left by an older link is
7
+ * only removed from the address bar and the history.
8
+ */
9
+ export declare const addressWithoutToken: (href: string) => string | null;
@@ -0,0 +1,7 @@
1
+ const t = (r) => {
2
+ const e = new URL(r);
3
+ return e.searchParams.has("authToken") ? (e.searchParams.delete("authToken"), e.toString()) : null;
4
+ };
5
+ export {
6
+ t as addressWithoutToken
7
+ };
@@ -1,36 +1,37 @@
1
- import { jsx as o, Fragment as u } from "react/jsx-runtime";
2
- import d, { useEffect as m } from "react";
3
- import { Spinner as f } from "react-bootstrap";
1
+ import { jsx as o, Fragment as f } from "react/jsx-runtime";
2
+ import { useState as p, useEffect as m } from "react";
3
+ import { Spinner as h } from "react-bootstrap";
4
4
  import "../../Context/AnalyticsContext.js";
5
5
  import "../../Context/MenuContext.js";
6
- import { useRouter as p } from "../../Context/RouterContext.js";
7
- import { useSession as l } from "../../Context/SessionContext.js";
8
6
  import "../../Context/NotificationContext.js";
9
- const v = ({
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
+ const j = ({
10
11
  redirectPath: i,
11
12
  children: c
12
13
  }) => {
13
- const [a, h] = d.useState(!1), { isAuthenticated: t, isLoading: r } = l(), { navigate: n } = p();
14
+ const [n, d] = p(!1), { isAuthenticated: t, isLoading: r } = l(), { navigate: s } = u();
14
15
  return m(() => {
15
- if (!r && !t && !a) {
16
- const e = window.location.href, s = new URLSearchParams();
17
- s.set("redirectTo", encodeURIComponent(e)), h(!0), setTimeout(
18
- () => n(`${i}?${s.toString()}`),
16
+ if (!r && !t && !n) {
17
+ const e = window.location.href, a = new URLSearchParams();
18
+ a.set("redirectTo", encodeURIComponent(e)), d(!0), setTimeout(
19
+ () => s(`${i}?${a.toString()}`),
19
20
  300
20
21
  );
21
22
  }
22
- }, [t, r, i, n, a]), m(() => {
23
- const e = new URL(window.location.href);
24
- e.searchParams.has("authToken") && (localStorage.setItem("authToken", e.searchParams.get("authToken")), e.searchParams.delete("authToken"), window.history.replaceState({}, "", e));
23
+ }, [t, r, i, s, n]), m(() => {
24
+ const e = w(window.location.href);
25
+ e && window.history.replaceState({}, "", e);
25
26
  }, []), r || !t ? /* @__PURE__ */ o("div", { className: "d-flex justify-content-center align-items-center w-100 h-100", children: /* @__PURE__ */ o(
26
- f,
27
+ h,
27
28
  {
28
29
  animation: "border",
29
30
  variant: "primary",
30
31
  style: { width: "3rem", height: "3rem", borderWidth: "0.5rem" }
31
32
  }
32
- ) }) : /* @__PURE__ */ o(u, { children: c });
33
+ ) }) : /* @__PURE__ */ o(f, { children: c });
33
34
  };
34
35
  export {
35
- v as AuthWrapper
36
+ j as AuthWrapper
36
37
  };
@@ -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
  };