@edifice.io/react 2.6.6 → 2.6.7-develop-b2school.20260929181638

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 (104) hide show
  1. package/dist/components/Alert/Alert.js +47 -23
  2. package/dist/components/Form/FormText.js +11 -6
  3. package/dist/components/Input/Input.d.ts +11 -2
  4. package/dist/components/Input/Input.js +43 -21
  5. package/dist/components/Label/Label.js +4 -4
  6. package/dist/components/Layout/Layout.js +30 -15
  7. package/dist/components/LinkPill/LinkPill.d.ts +41 -0
  8. package/dist/components/LinkPill/LinkPill.js +28 -0
  9. package/dist/components/LinkPill/index.d.ts +2 -0
  10. package/dist/components/ModalBeta/ModalBeta.d.ts +47 -0
  11. package/dist/components/ModalBeta/ModalBeta.js +62 -0
  12. package/dist/components/ModalBeta/ModalBetaBody.d.ts +19 -0
  13. package/dist/components/ModalBeta/ModalBetaBody.js +15 -0
  14. package/dist/components/ModalBeta/ModalBetaContext.d.ts +7 -0
  15. package/dist/components/ModalBeta/ModalBetaContext.js +15 -0
  16. package/dist/components/ModalBeta/ModalBetaFooter.d.ts +13 -0
  17. package/dist/components/ModalBeta/ModalBetaFooter.js +7 -0
  18. package/dist/components/ModalBeta/ModalBetaHeader.d.ts +23 -0
  19. package/dist/components/ModalBeta/ModalBetaHeader.js +31 -0
  20. package/dist/components/ModalBeta/index.d.ts +2 -0
  21. package/dist/components/PageLayout/PageLayout.d.ts +4 -0
  22. package/dist/components/PageLayout/PageLayout.js +37 -14
  23. package/dist/components/PageLayout/components/PageLayoutContent.js +5 -1
  24. package/dist/components/PageLayout/components/PageLayoutHelpZone.d.ts +12 -0
  25. package/dist/components/PageLayout/components/PageLayoutHelpZone.js +6 -0
  26. package/dist/components/Table/components/Table.d.ts +2 -2
  27. package/dist/components/Table/components/TableTd.d.ts +1 -1
  28. package/dist/components/Table/components/TableTh.d.ts +1 -1
  29. package/dist/components/index.d.ts +2 -0
  30. package/dist/homepage.js +44 -19
  31. package/dist/hooks/index.d.ts +2 -0
  32. package/dist/hooks/useBackground/index.d.ts +1 -0
  33. package/dist/hooks/useBackground/useBackground.d.ts +7 -0
  34. package/dist/hooks/useBackground/useBackground.js +16 -0
  35. package/dist/hooks/useClickOutside/useClickOutside.d.ts +1 -1
  36. package/dist/hooks/useClickOutside/useClickOutside.js +3 -2
  37. package/dist/hooks/useToast/useToast.d.ts +1 -0
  38. package/dist/hooks/useToast/useToast.js +12 -9
  39. package/dist/hooks/useUserPreferences/index.d.ts +1 -0
  40. package/dist/hooks/useUserPreferences/useUserPreferences.d.ts +9 -0
  41. package/dist/hooks/useUserPreferences/useUserPreferences.js +40 -0
  42. package/dist/hooks/useZendeskGuide/useZendeskGuide.d.ts +16 -2
  43. package/dist/hooks/useZendeskGuide/useZendeskGuide.js +58 -24
  44. package/dist/icons.js +404 -402
  45. package/dist/index.js +168 -160
  46. package/dist/modules/HelpZone/HelpZone.d.ts +13 -0
  47. package/dist/modules/HelpZone/HelpZone.js +52 -0
  48. package/dist/modules/HelpZone/HelpZoneContainer.d.ts +4 -0
  49. package/dist/modules/HelpZone/HelpZoneContainer.js +18 -0
  50. package/dist/modules/HelpZone/index.d.ts +3 -0
  51. package/dist/modules/homepage/components/Communities/Communities.js +35 -0
  52. package/dist/modules/homepage/components/Communities/CommunitiesContainer.d.ts +4 -2
  53. package/dist/modules/homepage/components/Communities/CommunitiesContainer.js +31 -0
  54. package/dist/modules/homepage/components/Communities/CommunitiesSkeleton.js +32 -0
  55. package/dist/modules/homepage/components/Communities/CommunityItem.d.ts +2 -1
  56. package/dist/modules/homepage/components/Communities/CommunityItem.js +28 -0
  57. package/dist/modules/homepage/components/Communities/useCommunities.d.ts +1 -4
  58. package/dist/modules/homepage/components/Communities/useCommunities.js +25 -0
  59. package/dist/modules/homepage/components/Favorites/Favorites.js +2 -2
  60. package/dist/modules/homepage/components/Header/Header.js +11 -4
  61. package/dist/modules/homepage/components/HomeCard/HomeCard.d.ts +1 -1
  62. package/dist/modules/homepage/components/HomeCard/HomeCardHeader.d.ts +15 -1
  63. package/dist/modules/homepage/components/HomeCard/HomeCardHeader.js +2 -1
  64. package/dist/modules/homepage/components/Notifications/components/NotificationFilterModal.js +36 -26
  65. package/dist/modules/homepage/components/Notifications/components/ReportNotificationModal.js +1 -1
  66. package/dist/modules/homepage/components/Notifications/services/queries/notification.js +9 -2
  67. package/dist/modules/homepage/components/UsefulLinks/LinkForm.d.ts +13 -0
  68. package/dist/modules/homepage/components/UsefulLinks/LinkForm.js +67 -0
  69. package/dist/modules/homepage/components/UsefulLinks/UsefulLinks.d.ts +6 -0
  70. package/dist/modules/homepage/components/UsefulLinks/UsefulLinks.js +31 -0
  71. package/dist/modules/homepage/components/UsefulLinks/UsefulLinksContainer.d.ts +1 -0
  72. package/dist/modules/homepage/components/UsefulLinks/UsefulLinksContainer.js +40 -0
  73. package/dist/modules/homepage/components/UsefulLinks/UsefulLinksModal.d.ts +11 -0
  74. package/dist/modules/homepage/components/UsefulLinks/UsefulLinksModal.js +65 -0
  75. package/dist/modules/homepage/components/UsefulLinks/hooks/useUsefulLinksContainer.d.ts +19 -0
  76. package/dist/modules/homepage/components/UsefulLinks/hooks/useUsefulLinksContainer.js +26 -0
  77. package/dist/modules/homepage/components/UsefulLinks/index.d.ts +5 -0
  78. package/dist/modules/homepage/components/UsefulLinks/services/api/index.d.ts +6 -0
  79. package/dist/modules/homepage/components/UsefulLinks/services/api/index.js +5 -0
  80. package/dist/modules/homepage/components/UsefulLinks/services/api/usefulLinksService.d.ts +31 -0
  81. package/dist/modules/homepage/components/UsefulLinks/services/api/usefulLinksService.js +50 -0
  82. package/dist/modules/homepage/components/UsefulLinks/services/queries/usefulLinks.d.ts +20 -0
  83. package/dist/modules/homepage/components/UsefulLinks/services/queries/usefulLinks.js +65 -0
  84. package/dist/modules/homepage/components/UserSpace/UserSpace.d.ts +3 -1
  85. package/dist/modules/homepage/components/UserSpace/UserSpace.js +13 -7
  86. package/dist/modules/homepage/components/UserSpace/UserSpaceContainer.d.ts +4 -2
  87. package/dist/modules/homepage/components/UserSpace/UserSpaceContainer.js +3 -2
  88. package/dist/modules/homepage/components/UserSpace/hooks/useProfileLinks.js +18 -8
  89. package/dist/modules/homepage/components/WidgetsPersonalizationPanel/WidgetToggle.d.ts +16 -0
  90. package/dist/modules/homepage/components/WidgetsPersonalizationPanel/WidgetToggle.js +15 -0
  91. package/dist/modules/homepage/components/WidgetsPersonalizationPanel/WidgetsPersonalizationPanel.d.ts +21 -0
  92. package/dist/modules/homepage/components/WidgetsPersonalizationPanel/WidgetsPersonalizationPanel.js +47 -0
  93. package/dist/modules/homepage/components/WidgetsPersonalizationPanel/index.d.ts +2 -0
  94. package/dist/modules/homepage/components/index.d.ts +3 -0
  95. package/dist/modules/icons/components/IconAddWidget.d.ts +7 -0
  96. package/dist/modules/icons/components/IconAddWidget.js +12 -0
  97. package/dist/modules/icons/components/index.d.ts +1 -0
  98. package/dist/modules/icons/components/logo/IconLogoEdificeFull.d.ts +7 -0
  99. package/dist/modules/icons/components/logo/IconLogoEdificeFull.js +26 -0
  100. package/dist/modules/icons/components/logo/IconLogoEdificeSmall.d.ts +7 -0
  101. package/dist/modules/icons/components/logo/IconLogoEdificeSmall.js +17 -0
  102. package/dist/modules/icons/components/logo/index.d.ts +2 -0
  103. package/dist/modules/widgets/index.d.ts +1 -1
  104. package/package.json +6 -6
@@ -3,9 +3,10 @@ export interface CommunityItemProps {
3
3
  communityImage: string;
4
4
  onActionClick: () => void;
5
5
  nbNotifications?: number;
6
+ width?: string;
6
7
  }
7
8
  declare const CommunityItem: {
8
- ({ title, communityImage, nbNotifications, onActionClick, }: CommunityItemProps): import("react/jsx-runtime").JSX.Element;
9
+ ({ title, communityImage, nbNotifications, onActionClick, width, }: CommunityItemProps): import("react/jsx-runtime").JSX.Element;
9
10
  displayName: string;
10
11
  };
11
12
  export default CommunityItem;
@@ -0,0 +1,28 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import clsx from "clsx";
3
+ import Badge from "../../../../components/Badge/Badge.js";
4
+ import Image from "../../../../components/Image/Image.js";
5
+ const CommunityItem = ({
6
+ title,
7
+ communityImage,
8
+ nbNotifications,
9
+ onActionClick,
10
+ width = "25%"
11
+ }) => {
12
+ const hasNotifications = (nbNotifications ?? 0) > 0;
13
+ return /* @__PURE__ */ jsxs("div", { role: "button", tabIndex: 0, onClick: onActionClick, className: clsx("communities-item", {
14
+ "communities-item-has-notifications": hasNotifications
15
+ }), style: {
16
+ width
17
+ }, children: [
18
+ hasNotifications && /* @__PURE__ */ jsx(Badge, { className: "communities-item-badge", variant: {
19
+ level: "danger",
20
+ type: "notification"
21
+ }, children: nbNotifications }),
22
+ /* @__PURE__ */ jsx(Image, { className: "communities-item-image", src: communityImage, alt: title }),
23
+ /* @__PURE__ */ jsx("p", { className: "communities-item-title text-truncate text-truncate-2", children: title })
24
+ ] });
25
+ };
26
+ export {
27
+ CommunityItem as default
28
+ };
@@ -1,10 +1,7 @@
1
1
  export interface CommunitiesModel {
2
2
  id: number | string;
3
3
  title: string;
4
- communityImage?: string;
5
- icon?: string;
6
- nbNotifications?: number;
7
- notifications?: number;
4
+ image?: string;
8
5
  }
9
6
  export declare function useCommunities(): {
10
7
  communities: CommunitiesModel[];
@@ -0,0 +1,25 @@
1
+ import { odeServices } from "@edifice.io/client";
2
+ import { useQuery, queryOptions } from "@tanstack/react-query";
3
+ function useCommunities() {
4
+ const {
5
+ data,
6
+ isLoading,
7
+ error
8
+ } = useQuery(queryOptions({
9
+ queryKey: ["communities", "preview"],
10
+ queryFn: async () => {
11
+ const http = odeServices.http(), response = await http.get("/communities/api/communities?page=1&size=4");
12
+ if (http.isResponseError())
13
+ throw new Error(http.latestResponse.statusText);
14
+ return response.items;
15
+ }
16
+ }));
17
+ return {
18
+ communities: data ?? [],
19
+ isLoading,
20
+ error
21
+ };
22
+ }
23
+ export {
24
+ useCommunities
25
+ };
@@ -16,10 +16,10 @@ function Favorites({
16
16
  sensitivity: "base"
17
17
  })), [apps, t]);
18
18
  return /* @__PURE__ */ jsxs(HomeCard, { variant: "secondary", children: [
19
- /* @__PURE__ */ jsx(HomeCard.Header, { title: t("homepage.favorites.title", "Favoris"), actionLabel: t("homepage.favorites.all", "Mes applis"), onActionClick: onSeeAllClick, actionRightIcon: /* @__PURE__ */ jsx(SvgIconArrowRight, {}) }),
19
+ /* @__PURE__ */ jsx(HomeCard.Header, { title: t("homepage.favorites.title"), actionLabel: t("homepage.favorites.all"), onActionClick: onSeeAllClick, actionRightIcon: /* @__PURE__ */ jsx(SvgIconArrowRight, {}) }),
20
20
  /* @__PURE__ */ jsx(HomeCard.Content, { children: /* @__PURE__ */ jsx("div", { className: "favorites-content", children: visibleApps.length === 0 ? /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "12", children: [
21
21
  /* @__PURE__ */ jsx("img", { src: illuEmptyFavorite, alt: "", width: 50, height: 50 }),
22
- /* @__PURE__ */ jsx("span", { className: "favorites-empty-text", children: t("homepage.favorites.empty", "Ajouter des applications à vos favoris pour les retrouver ici et y accéder rapidement !") })
22
+ /* @__PURE__ */ jsx("span", { className: "favorites-empty-text", children: t("homepage.favorites.empty") })
23
23
  ] }) : /* @__PURE__ */ jsx(Flex, { wrap: "wrap", gap: "8 16", children: visibleApps.map((app) => {
24
24
  const appName = getAppName(app), opensInNewTab = app.isExternal || app.target === "_blank";
25
25
  return /* @__PURE__ */ jsx("a", { href: app.address, "aria-label": appName, title: appName, target: opensInNewTab ? "_blank" : void 0, rel: opensInNewTab ? "noopener noreferrer" : void 0, children: /* @__PURE__ */ jsx(AppIcon, { app, size: "40", variant: "square" }) }, app.name);
@@ -6,6 +6,7 @@ import { Navbar } from "../../../../components/Layout/components/Navbar.js";
6
6
  import { NavItem } from "../../../../components/Layout/components/NavItem.js";
7
7
  import { NavLink } from "../../../../components/Layout/components/NavLink.js";
8
8
  import useHeader from "../../../../components/Layout/hooks/useHeader.js";
9
+ import SvgIconSetBackground from "../../../icons/components/IconSetBackground.js";
9
10
  import SvgIconCommunitiesBeta from "../../../icons/components/nav/IconCommunitiesBeta.js";
10
11
  import SvgIconDisconnect from "../../../icons/components/nav/IconDisconnect.js";
11
12
  import SvgIconHomeBeta from "../../../icons/components/nav/IconHomeBeta.js";
@@ -86,10 +87,16 @@ const Header = ({
86
87
  ] }),
87
88
  /* @__PURE__ */ jsxs(NavItem, { className: "position-relative", ref: userRef, id: popoverUserId, "aria-haspopup": "true", "aria-expanded": isUserHovered, "data-testid": "header-user-menu-button", children: [
88
89
  /* @__PURE__ */ jsx(NavLink, { link: "/userbook/mon-compte", translate: t("navbar.myaccount"), "data-testid": "header-user-profile-button", children: /* @__PURE__ */ jsx(Avatar, { alt: userName, size: "sm", src: userAvatar, variant: "circle", className: "bg-white", width: "32", height: "32" }) }),
89
- /* @__PURE__ */ jsx(Popover, { align: "end", className: "widget", id: popoverUserId, isVisible: isUserHovered, children: /* @__PURE__ */ jsx(PopoverBody, { children: /* @__PURE__ */ jsxs("a", { href: "/auth/logout?callback=" + ((theme == null ? void 0 : theme.logoutCallback) ?? ""), className: "nav-link logout d-flex align-items-center gap-8", "data-testid": "header-logout-button", children: [
90
- /* @__PURE__ */ jsx(SvgIconDisconnect, { className: "icon logout" }),
91
- /* @__PURE__ */ jsx("span", { id: "logout-label", className: "nav-text", children: t("navbar.disconnect") })
92
- ] }) }) })
90
+ /* @__PURE__ */ jsx(Popover, { align: "end", className: "widget", id: popoverUserId, isVisible: isUserHovered, children: /* @__PURE__ */ jsxs(PopoverBody, { children: [
91
+ /* @__PURE__ */ jsxs("a", { href: "/timeline/customize?callback=" + encodeURIComponent(`${window.location.pathname}${window.location.search}`), className: "nav-link customize d-flex align-items-center gap-8", "data-testid": "header-customize-button", children: [
92
+ /* @__PURE__ */ jsx(SvgIconSetBackground, { className: "icon" }),
93
+ /* @__PURE__ */ jsx("span", { id: "cutomize-label", className: "nav-text", children: t("navbar.customize") })
94
+ ] }),
95
+ /* @__PURE__ */ jsxs("a", { href: "/auth/logout?callback=" + ((theme == null ? void 0 : theme.logoutCallback) ?? ""), className: "nav-link logout d-flex align-items-center gap-8", "data-testid": "header-logout-button", children: [
96
+ /* @__PURE__ */ jsx(SvgIconDisconnect, { className: "icon logout" }),
97
+ /* @__PURE__ */ jsx("span", { id: "logout-label", className: "nav-text", children: t("navbar.disconnect") })
98
+ ] })
99
+ ] }) })
93
100
  ] })
94
101
  ] })
95
102
  ] }) });
@@ -30,7 +30,7 @@ declare const HomeCard: {
30
30
  displayName: string;
31
31
  } & {
32
32
  Header: {
33
- ({ title, actionLabel, onActionClick, actionLeftIcon, actionRightIcon, className, ...rest }: HomeCardHeaderProps): import("react/jsx-runtime").JSX.Element;
33
+ ({ title, actionLabel, onActionClick, actionLeftIcon, actionRightIcon, actionProps, className, ...rest }: HomeCardHeaderProps): import("react/jsx-runtime").JSX.Element;
34
34
  displayName: string;
35
35
  };
36
36
  Content: {
@@ -1,4 +1,5 @@
1
1
  import { ComponentPropsWithoutRef, MouseEventHandler, ReactNode } from 'react';
2
+ import { ButtonBeta } from '../../../../components';
2
3
  export interface HomeCardHeaderProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title'> {
3
4
  /** Title displayed on the left side of the header. */
4
5
  title: ReactNode;
@@ -10,9 +11,22 @@ export interface HomeCardHeaderProps extends Omit<ComponentPropsWithoutRef<'div'
10
11
  actionLeftIcon?: ReactNode;
11
12
  /** Optional right icon of the action button. */
12
13
  actionRightIcon?: ReactNode;
14
+ /**
15
+ * Extra props spread onto the action button (e.g. `data-testid`) — its
16
+ * default `data-testid` (`home-card-header-action`) isn't unique across
17
+ * the several `HomeCard`s a page can render, so consumers doing that
18
+ * should override it here. The `data-*` index signature covers
19
+ * attributes like `data-testid` that `ButtonBetaProps` doesn't declare:
20
+ * TypeScript's built-in allowance for undeclared `data-*`/`aria-*`
21
+ * attributes only applies to attributes written directly on a JSX
22
+ * element, not to a plain object literal passed as a prop value.
23
+ */
24
+ actionProps?: ComponentPropsWithoutRef<typeof ButtonBeta> & {
25
+ [key: `data-${string}`]: string;
26
+ };
13
27
  }
14
28
  declare const HomeCardHeader: {
15
- ({ title, actionLabel, onActionClick, actionLeftIcon, actionRightIcon, className, ...rest }: HomeCardHeaderProps): import("react/jsx-runtime").JSX.Element;
29
+ ({ title, actionLabel, onActionClick, actionLeftIcon, actionRightIcon, actionProps, className, ...rest }: HomeCardHeaderProps): import("react/jsx-runtime").JSX.Element;
16
30
  displayName: string;
17
31
  };
18
32
  export default HomeCardHeader;
@@ -8,13 +8,14 @@ const HomeCardHeader = ({
8
8
  onActionClick,
9
9
  actionLeftIcon,
10
10
  actionRightIcon,
11
+ actionProps,
11
12
  className,
12
13
  ...rest
13
14
  }) => {
14
15
  const hasAction = !!actionLabel && !!onActionClick;
15
16
  return /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "between", gap: "8", className: clsx("home-card-header", className), ...rest, children: [
16
17
  /* @__PURE__ */ jsx("h3", { className: "home-card-header-title", children: title }),
17
- hasAction && /* @__PURE__ */ jsx(ButtonBeta, { color: "tertiary", size: "sm", variant: "ghost", onClick: onActionClick, leftIcon: actionLeftIcon, rightIcon: actionRightIcon, "data-testid": "home-card-header-action", children: actionLabel })
18
+ hasAction && /* @__PURE__ */ jsx(ButtonBeta, { color: "tertiary", size: "sm", variant: "ghost", onClick: onActionClick, leftIcon: actionLeftIcon, rightIcon: actionRightIcon, "data-testid": "home-card-header-action", ...actionProps, children: actionLabel })
18
19
  ] });
19
20
  };
20
21
  export {
@@ -1,12 +1,11 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import clsx from "clsx";
3
- import { useState } from "react";
3
+ import { useState, useRef, useMemo, useEffect } from "react";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import SvgIconCheck from "../../../../icons/components/IconCheck.js";
6
6
  import { getAppCodeAndI18nKey } from "./notificationAdapter.js";
7
7
  import Modal from "../../../../../components/Modal/Modal.js";
8
8
  import Flex from "../../../../../components/Flex/Flex.js";
9
- import Checkbox from "../../../../../components/Checkbox/Checkbox.js";
10
9
  import AppIcon from "../../../../../components/AppIcon/AppIcon.js";
11
10
  import ButtonBeta from "../../../../../components/ButtonBeta/ButtonBeta.js";
12
11
  const NotificationFilterModal = ({
@@ -17,48 +16,59 @@ const NotificationFilterModal = ({
17
16
  onApply
18
17
  }) => {
19
18
  const {
20
- t
21
- } = useTranslation(), [selected, setSelected] = useState(appliedTypes), toggleType = (type) => {
19
+ t,
20
+ i18n
21
+ } = useTranslation(), [selected, setSelected] = useState(appliedTypes), refSelectAllCheckbox = useRef(null), allTypesSorted = useMemo(() => allTypes.map((type) => {
22
+ const [appCode, appI18nKey] = getAppCodeAndI18nKey(type), label = t(appI18nKey, {
23
+ defaultValue: appCode
24
+ });
25
+ return {
26
+ type,
27
+ appCode,
28
+ appI18nKey,
29
+ label
30
+ };
31
+ }).sort((a, b) => a.label.localeCompare(b.label, i18n.language, {
32
+ sensitivity: "base"
33
+ })), [allTypes, t, i18n.language]), toggleType = (type) => {
22
34
  setSelected((current) => current.includes(type) ? current.filter((value) => value !== type) : [...current, type]);
23
35
  }, toggleAll = () => {
24
36
  setSelected(selected.length === allTypes.length ? [] : allTypes);
25
37
  };
26
- return /* @__PURE__ */ jsxs(Modal, { id: "notification-filter-modal", size: "lg", scrollable: !0, isOpen, onModalClose: onCancel, children: [
27
- /* @__PURE__ */ jsx(Modal.Header, { onModalClose: onCancel, children: t("homepage.notifications.filter-modal.title", {
28
- defaultValue: "Filtrer les notifications"
29
- }) }),
30
- /* @__PURE__ */ jsx(Modal.Subtitle, { children: t("homepage.notifications.filter-modal.subtitle", {
31
- defaultValue: "Sélectionnez seulement les notifications importantes pour vous"
32
- }) }),
38
+ return useEffect(() => {
39
+ refSelectAllCheckbox.current && (refSelectAllCheckbox.current.indeterminate = selected.length > 0 && selected.length < allTypes.length);
40
+ }, [selected.length, allTypes.length]), /* @__PURE__ */ jsxs(Modal, { id: "notification-filter-modal", size: "lg", scrollable: !0, isOpen, onModalClose: onCancel, children: [
41
+ /* @__PURE__ */ jsx(Modal.Header, { onModalClose: onCancel, children: t("homepage.notifications.filter-modal.title") }),
42
+ /* @__PURE__ */ jsx(Modal.Subtitle, { children: t("homepage.notifications.filter-modal.subtitle") }),
33
43
  /* @__PURE__ */ jsxs(Modal.Body, { children: [
34
- /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "8", className: "mb-16", children: [
35
- /* @__PURE__ */ jsx("div", { className: "notification-filter-select-all", children: /* @__PURE__ */ jsx(Checkbox, { label: t("homepage.notifications.filter-modal.select-all", {
36
- defaultValue: "Tout sélectionner"
37
- }), checked: selected.length === allTypes.length, indeterminate: selected.length > 0 && selected.length < allTypes.length, onChange: toggleAll }) }),
44
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "8", className: "mb-24", children: [
45
+ /* @__PURE__ */ jsxs("label", { className: "notification-filter-select-all notification-filter-chip", children: [
46
+ /* @__PURE__ */ jsx("span", { children: t("homepage.notifications.filter-modal.select-all") }),
47
+ /* @__PURE__ */ jsx("input", { type: "checkbox", ref: refSelectAllCheckbox, checked: selected.length === allTypes.length, className: "notification-filter-checkbox", onChange: toggleAll })
48
+ ] }),
38
49
  /* @__PURE__ */ jsx("span", { className: "notification-filter-count", children: t("homepage.notifications.filter-modal.count", {
39
50
  selected: selected.length,
40
- total: allTypes.length,
41
- defaultValue: "[[selected]] / [[total]] sélectionnées"
51
+ total: allTypes.length
42
52
  }) })
43
53
  ] }),
44
- /* @__PURE__ */ jsx(Flex, { wrap: "wrap", gap: "8", className: "notification-filter-options", children: allTypes.map((type) => {
45
- const [appCode, appI18nKey] = getAppCodeAndI18nKey(type), checked = selected.includes(type);
54
+ /* @__PURE__ */ jsx(Flex, { wrap: "wrap", gap: "16", className: "notification-filter-options", children: allTypesSorted.map(({
55
+ type,
56
+ label,
57
+ appCode
58
+ }) => {
59
+ const checked = selected.includes(type);
46
60
  return /* @__PURE__ */ jsxs("label", { className: clsx("notification-filter-chip", {
47
61
  "is-checked": checked
48
62
  }), children: [
49
63
  /* @__PURE__ */ jsx(AppIcon, { app: appCode, size: "24", iconFit: "contain" }),
50
- /* @__PURE__ */ jsx("span", { children: t(appI18nKey, {
51
- defaultValue: appCode
52
- }) }),
53
- /* @__PURE__ */ jsx("input", { type: "checkbox", className: "form-check-input", checked, onChange: () => toggleType(type) })
64
+ /* @__PURE__ */ jsx("span", { children: label }),
65
+ /* @__PURE__ */ jsx("input", { type: "checkbox", className: "notification-filter-checkbox", checked, onChange: () => toggleType(type) })
54
66
  ] }, type);
55
67
  }) })
56
68
  ] }),
57
69
  /* @__PURE__ */ jsxs(Modal.Footer, { children: [
58
70
  /* @__PURE__ */ jsx(ButtonBeta, { color: "tertiary", onClick: onCancel, type: "button", variant: "ghost", children: t("cancel") }),
59
- /* @__PURE__ */ jsx(ButtonBeta, { color: "default", onClick: () => onApply(selected), type: "button", variant: "filled", rightIcon: /* @__PURE__ */ jsx(SvgIconCheck, {}), children: t("homepage.notifications.filter-modal.confirm", {
60
- defaultValue: "Enregistrer"
61
- }) })
71
+ /* @__PURE__ */ jsx(ButtonBeta, { color: "default", onClick: () => onApply(selected), type: "button", variant: "filled", rightIcon: /* @__PURE__ */ jsx(SvgIconCheck, {}), children: t("homepage.notifications.filter-modal.confirm") })
62
72
  ] })
63
73
  ] });
64
74
  };
@@ -11,7 +11,7 @@ const ReportNotificationModal = ({
11
11
  const {
12
12
  t
13
13
  } = useTranslation();
14
- return /* @__PURE__ */ jsxs(Modal, { id: "report-notification-modal", size: "lg", isOpen, onModalClose: onCancel, children: [
14
+ return /* @__PURE__ */ jsxs(Modal, { id: "report-notification-modal", size: "sm", isOpen, onModalClose: onCancel, children: [
15
15
  /* @__PURE__ */ jsx(Modal.Header, { onModalClose: onCancel, children: t("homepage.notifications.report-modal.title", {
16
16
  defaultValue: "Signaler une notification"
17
17
  }) }),
@@ -1,6 +1,8 @@
1
1
  import { odeServices } from "@edifice.io/client";
2
2
  import { useQuery, useInfiniteQuery, useMutation, useQueryClient, queryOptions, infiniteQueryOptions } from "@tanstack/react-query";
3
+ import { useTranslation } from "react-i18next";
3
4
  import { notificationService } from "../api/index.js";
5
+ import useToast from "../../../../../../hooks/useToast/useToast.js";
4
6
  const notificationQueryKeys = {
5
7
  all: () => ["notifications"],
6
8
  notifications: (types = []) => [...notificationQueryKeys.all(), "list", types],
@@ -52,7 +54,9 @@ const notificationQueryKeys = {
52
54
  }, useReportNotification = () => useMutation({
53
55
  mutationFn: (id) => notificationService.reportNotification(id)
54
56
  }), useDeleteNotification = () => {
55
- const queryClient = useQueryClient();
57
+ const {
58
+ t
59
+ } = useTranslation(), toast = useToast(), queryClient = useQueryClient();
56
60
  return useMutation({
57
61
  mutationFn: (id) => notificationService.deleteNotification(id),
58
62
  onSuccess: (_data, id) => {
@@ -61,7 +65,10 @@ const notificationQueryKeys = {
61
65
  }, (data) => data && {
62
66
  ...data,
63
67
  pages: data.pages.map((page) => page.filter((notification) => notification._id !== id))
64
- });
68
+ }), toast.success(t("homepage.notifications.options.delete.success"));
69
+ },
70
+ onError: () => {
71
+ toast.error(t("homepage.notifications.options.delete.error"));
65
72
  }
66
73
  });
67
74
  };
@@ -0,0 +1,13 @@
1
+ import { UsefulLink, UsefulLinkPayload } from '@edifice.io/client';
2
+ export interface LinkFormProps {
3
+ mode: 'add' | 'edit';
4
+ /** Link being edited. Ignored (and required to be undefined) in "add" mode. */
5
+ link?: UsefulLink;
6
+ isSubmitting: boolean;
7
+ /** Closes the form and goes back to the management modal, without saving. */
8
+ onCancel: () => void;
9
+ /** Closes the whole modal stack. */
10
+ onClose: () => void;
11
+ onSubmit: (payload: UsefulLinkPayload) => void;
12
+ }
13
+ export declare function LinkForm({ mode, link, isSubmitting, onCancel, onClose, onSubmit, }: LinkFormProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,67 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useTranslation } from "react-i18next";
3
+ import { useForm } from "react-hook-form";
4
+ import ModalBeta from "../../../../components/ModalBeta/ModalBeta.js";
5
+ import FormControl from "../../../../components/Form/FormControl.js";
6
+ import Label from "../../../../components/Label/Label.js";
7
+ import Input from "../../../../components/Input/Input.js";
8
+ import ButtonBeta from "../../../../components/ButtonBeta/ButtonBeta.js";
9
+ const NAME_MAX_LENGTH = 80, URL_PATTERN = /^https?:\/\/.+/i;
10
+ function LinkForm({
11
+ mode,
12
+ link,
13
+ isSubmitting,
14
+ onCancel,
15
+ onClose,
16
+ onSubmit
17
+ }) {
18
+ const {
19
+ t
20
+ } = useTranslation(), formId = "useful-link-form", {
21
+ register,
22
+ handleSubmit,
23
+ formState: {
24
+ errors,
25
+ isDirty,
26
+ isValid
27
+ }
28
+ } = useForm({
29
+ mode: "onChange",
30
+ defaultValues: {
31
+ name: (link == null ? void 0 : link.name) ?? "",
32
+ url: (link == null ? void 0 : link.url) ?? ""
33
+ }
34
+ }), title = t(mode === "add" ? "homepage.usefulLinks.form.addTitle" : "homepage.usefulLinks.form.editTitle"), handleFormSubmit = (data) => {
35
+ onSubmit({
36
+ name: data.name.trim(),
37
+ url: data.url.trim()
38
+ });
39
+ };
40
+ return /* @__PURE__ */ jsxs(ModalBeta, { id: "useful-link-modal", isOpen: !0, size: "l", onModalClose: onClose, children: [
41
+ /* @__PURE__ */ jsx(ModalBeta.Header, { onModalClose: onClose, children: title }),
42
+ /* @__PURE__ */ jsx(ModalBeta.Body, { children: /* @__PURE__ */ jsxs("form", { id: formId, onSubmit: handleSubmit(handleFormSubmit), children: [
43
+ /* @__PURE__ */ jsxs(FormControl, { id: "useful-link-name", className: "mb-16", isRequired: !0, children: [
44
+ /* @__PURE__ */ jsx(Label, { children: t("homepage.usefulLinks.form.name") }),
45
+ /* @__PURE__ */ jsx(Input, { type: "text", size: "md", maxLength: NAME_MAX_LENGTH, showCounter: !0, clearable: !0, "data-testid": "usefullinks-input-name", placeholder: t("homepage.usefulLinks.form.name.placeholder"), ...register("name", {
46
+ required: !0,
47
+ maxLength: NAME_MAX_LENGTH
48
+ }) })
49
+ ] }),
50
+ /* @__PURE__ */ jsxs(FormControl, { id: "useful-link-url", isRequired: !0, status: errors.url ? "invalid" : void 0, children: [
51
+ /* @__PURE__ */ jsx(Label, { children: t("homepage.usefulLinks.form.url") }),
52
+ /* @__PURE__ */ jsx(Input, { type: "text", size: "md", "data-testid": "usefullinks-input-url", placeholder: t("homepage.usefulLinks.form.url.placeholder"), ...register("url", {
53
+ required: !0,
54
+ pattern: URL_PATTERN
55
+ }) }),
56
+ errors.url && /* @__PURE__ */ jsx(FormControl.Text, { children: t("homepage.usefulLinks.form.url.error") })
57
+ ] })
58
+ ] }) }),
59
+ /* @__PURE__ */ jsxs(ModalBeta.Footer, { children: [
60
+ /* @__PURE__ */ jsx(ButtonBeta, { type: "button", variant: "ghost", color: "tertiary", "data-testid": "usefullinks-button-cancel", onClick: onCancel, children: t("homepage.usefulLinks.form.cancel") }),
61
+ /* @__PURE__ */ jsx(ButtonBeta, { form: formId, type: "submit", isLoading: isSubmitting, disabled: !isValid || !isDirty || isSubmitting, "data-testid": "usefullinks-button-save", children: t("homepage.usefulLinks.form.save") })
62
+ ] })
63
+ ] });
64
+ }
65
+ export {
66
+ LinkForm
67
+ };
@@ -0,0 +1,6 @@
1
+ import { UsefulLink } from '@edifice.io/client';
2
+ export interface UsefulLinksProps {
3
+ links: UsefulLink[];
4
+ onEditClick: () => void;
5
+ }
6
+ export declare function UsefulLinks({ links, onEditClick }: UsefulLinksProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,31 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useTranslation } from "react-i18next";
3
+ import illuEmptyUsefulLinks from "@edifice.io/bootstrap/dist/images/homepage/illu-empty-useful-links.png";
4
+ import SvgIconEdit from "../../../icons/components/IconEdit.js";
5
+ import HomeCard from "../HomeCard/HomeCard.js";
6
+ import Image from "../../../../components/Image/Image.js";
7
+ import Flex from "../../../../components/Flex/Flex.js";
8
+ import LinkPill from "../../../../components/LinkPill/LinkPill.js";
9
+ function UsefulLinks({
10
+ links,
11
+ onEditClick
12
+ }) {
13
+ const {
14
+ t
15
+ } = useTranslation();
16
+ return /* @__PURE__ */ jsxs(HomeCard, { variant: "primary", children: [
17
+ /* @__PURE__ */ jsx(HomeCard.Header, { title: t("homepage.usefulLinks.title"), actionLabel: t("homepage.usefulLinks.edit"), actionLeftIcon: /* @__PURE__ */ jsx(SvgIconEdit, {}), onActionClick: onEditClick, actionProps: {
18
+ "data-testid": "usefullinks-button-edit"
19
+ } }),
20
+ /* @__PURE__ */ jsx(HomeCard.Content, { children: links.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "useful-links-widget__empty", children: [
21
+ /* @__PURE__ */ jsx(Image, { src: illuEmptyUsefulLinks, alt: "", "aria-hidden": "true", style: {
22
+ width: 160,
23
+ height: 87
24
+ } }),
25
+ /* @__PURE__ */ jsx("p", { className: "useful-links-widget__empty-text", children: t("homepage.usefulLinks.empty") })
26
+ ] }) : /* @__PURE__ */ jsx(Flex, { direction: "column", gap: "8", children: links.map((link) => /* @__PURE__ */ jsx(LinkPill, { href: link.url, label: link.name, "data-testid": `usefullinks-link-${link.id}` }, link.id)) }) })
27
+ ] });
28
+ }
29
+ export {
30
+ UsefulLinks
31
+ };
@@ -0,0 +1 @@
1
+ export declare function UsefulLinksContainer(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,40 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { useUsefulLinksContainer } from "./hooks/useUsefulLinksContainer.js";
4
+ import { LinkForm } from "./LinkForm.js";
5
+ import { UsefulLinks } from "./UsefulLinks.js";
6
+ import { UsefulLinksModal } from "./UsefulLinksModal.js";
7
+ function UsefulLinksContainer() {
8
+ const [view, setView] = useState({
9
+ name: "closed"
10
+ }), {
11
+ links,
12
+ canAddLink,
13
+ createLink,
14
+ isCreating,
15
+ updateLink,
16
+ isUpdating,
17
+ deleteLink
18
+ } = useUsefulLinksContainer(), closeAll = () => setView({
19
+ name: "closed"
20
+ }), backToManage = () => setView({
21
+ name: "manage"
22
+ }), handleSubmit = async (payload) => {
23
+ view.name === "edit" ? await updateLink(view.link.id, payload) : await createLink(payload), backToManage();
24
+ };
25
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
26
+ /* @__PURE__ */ jsx(UsefulLinks, { links, onEditClick: () => setView({
27
+ name: "manage"
28
+ }) }),
29
+ /* @__PURE__ */ jsx(UsefulLinksModal, { isOpen: view.name === "manage", links, canAddLink, onClose: closeAll, onAddClick: () => setView({
30
+ name: "add"
31
+ }), onEditLink: (link) => setView({
32
+ name: "edit",
33
+ link
34
+ }), onDeleteLink: deleteLink }),
35
+ (view.name === "add" || view.name === "edit") && /* @__PURE__ */ jsx(LinkForm, { mode: view.name, link: view.name === "edit" ? view.link : void 0, isSubmitting: view.name === "edit" ? isUpdating : isCreating, onCancel: backToManage, onClose: closeAll, onSubmit: handleSubmit })
36
+ ] });
37
+ }
38
+ export {
39
+ UsefulLinksContainer
40
+ };
@@ -0,0 +1,11 @@
1
+ import { UsefulLink } from '@edifice.io/client';
2
+ export interface UsefulLinksModalProps {
3
+ isOpen: boolean;
4
+ links: UsefulLink[];
5
+ canAddLink: boolean;
6
+ onClose: () => void;
7
+ onAddClick: () => void;
8
+ onEditLink: (link: UsefulLink) => void;
9
+ onDeleteLink: (id: string) => void;
10
+ }
11
+ export declare function UsefulLinksModal({ isOpen, links, canAddLink, onClose, onAddClick, onEditLink, onDeleteLink, }: UsefulLinksModalProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,65 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { useTranslation } from "react-i18next";
3
+ import illuEmptyUsefulLinks from "@edifice.io/bootstrap/dist/images/homepage/illu-empty-useful-links.png";
4
+ import SvgIconClose from "../../../icons/components/IconClose.js";
5
+ import SvgIconEdit from "../../../icons/components/IconEdit.js";
6
+ import SvgIconPlus from "../../../icons/components/IconPlus.js";
7
+ import useBreakpoint from "../../../../hooks/useBreakpoint/useBreakpoint.js";
8
+ import ButtonBeta from "../../../../components/ButtonBeta/ButtonBeta.js";
9
+ import ModalBeta from "../../../../components/ModalBeta/ModalBeta.js";
10
+ import Flex from "../../../../components/Flex/Flex.js";
11
+ import Image from "../../../../components/Image/Image.js";
12
+ import Table from "../../../../components/Table/components/Table.js";
13
+ function UsefulLinksModal({
14
+ isOpen,
15
+ links,
16
+ canAddLink,
17
+ onClose,
18
+ onAddClick,
19
+ onEditLink,
20
+ onDeleteLink
21
+ }) {
22
+ const {
23
+ t
24
+ } = useTranslation(), {
25
+ md
26
+ } = useBreakpoint(), addLinkButton = /* @__PURE__ */ jsx(ButtonBeta, { type: "button", variant: "outline", leftIcon: /* @__PURE__ */ jsx(SvgIconPlus, {}), onClick: onAddClick, disabled: !canAddLink, "data-testid": "usefullinks-button-add", children: t("homepage.usefulLinks.modal.add") });
27
+ return /* @__PURE__ */ jsxs(ModalBeta, { id: "useful-links-modal", isOpen, size: "l", onModalClose: onClose, children: [
28
+ /* @__PURE__ */ jsx(ModalBeta.Header, { onModalClose: onClose, children: t("homepage.usefulLinks.modal.title") }),
29
+ /* @__PURE__ */ jsx(ModalBeta.Body, { children: links.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
30
+ /* @__PURE__ */ jsx(Flex, { justify: "end", className: "mb-16", children: addLinkButton }),
31
+ /* @__PURE__ */ jsxs("div", { className: "useful-links-modal__empty", children: [
32
+ /* @__PURE__ */ jsx(Image, { src: illuEmptyUsefulLinks, alt: "", "aria-hidden": "true", style: {
33
+ width: 200,
34
+ height: 109
35
+ } }),
36
+ /* @__PURE__ */ jsx("p", { className: "useful-links-modal__empty-title", children: t("homepage.usefulLinks.modal.empty.title") }),
37
+ /* @__PURE__ */ jsx("p", { className: "useful-links-modal__empty-text", children: t("homepage.usefulLinks.modal.empty.text") })
38
+ ] })
39
+ ] }) : /* @__PURE__ */ jsx("div", { className: "useful-links-table", children: /* @__PURE__ */ jsxs(Table, { children: [
40
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
41
+ /* @__PURE__ */ jsx(Table.Th, { className: "visually-hidden", children: t("homepage.usefulLinks.modal.table.name") }),
42
+ /* @__PURE__ */ jsx(Table.Th, { className: "visually-hidden", children: t("homepage.usefulLinks.modal.table.url") }),
43
+ /* @__PURE__ */ jsx(Table.Th, { className: "visually-hidden", children: t("homepage.usefulLinks.modal.table.actions") })
44
+ ] }) }),
45
+ /* @__PURE__ */ jsxs(Table.Tbody, { children: [
46
+ /* @__PURE__ */ jsx(Table.Tr, { className: "useful-links-table__action-row", children: /* @__PURE__ */ jsx(Table.Td, { colSpan: md ? 3 : 2, children: /* @__PURE__ */ jsx(Flex, { justify: "end", children: addLinkButton }) }) }),
47
+ links.map((link) => /* @__PURE__ */ jsxs(Table.Tr, { "data-testid": `usefullinks-row-${link.id}`, children: [
48
+ /* @__PURE__ */ jsx(Table.Td, { className: "useful-links-table__name", children: link.name }),
49
+ /* @__PURE__ */ jsx(Table.Td, { className: "useful-links-table__url", children: link.url }),
50
+ /* @__PURE__ */ jsx(Table.Td, { className: "useful-links-table__actions", children: /* @__PURE__ */ jsxs(Flex, { gap: "4", justify: "end", children: [
51
+ /* @__PURE__ */ jsx(ButtonBeta, { type: "button", variant: "ghost", color: "tertiary", size: "sm", leftIcon: /* @__PURE__ */ jsx(SvgIconEdit, {}), "aria-label": t("homepage.usefulLinks.modal.table.edit", {
52
+ name: link.name
53
+ }), "data-testid": `usefullinks-button-edit-${link.id}`, onClick: () => onEditLink(link) }),
54
+ /* @__PURE__ */ jsx(ButtonBeta, { type: "button", variant: "ghost", color: "tertiary", size: "sm", leftIcon: /* @__PURE__ */ jsx(SvgIconClose, {}), "aria-label": t("homepage.usefulLinks.modal.table.delete", {
55
+ name: link.name
56
+ }), "data-testid": `usefullinks-button-delete-${link.id}`, onClick: () => onDeleteLink(link.id) })
57
+ ] }) })
58
+ ] }, link.id))
59
+ ] })
60
+ ] }) }) })
61
+ ] });
62
+ }
63
+ export {
64
+ UsefulLinksModal
65
+ };
@@ -0,0 +1,19 @@
1
+ import { UsefulLink, UsefulLinkPayload } from '@edifice.io/client';
2
+ /** Maximum number of useful links a user can save. */
3
+ export declare const MAX_USEFUL_LINKS = 10;
4
+ export interface UseUsefulLinksContainerReturn {
5
+ links: UsefulLink[];
6
+ isLoading: boolean;
7
+ error: Error | null;
8
+ /** Whether the user can still add a new link (fewer than MAX_USEFUL_LINKS). */
9
+ canAddLink: boolean;
10
+ createLink: (payload: UsefulLinkPayload) => Promise<UsefulLink>;
11
+ isCreating: boolean;
12
+ updateLink: (id: string, payload: UsefulLinkPayload) => Promise<UsefulLink>;
13
+ isUpdating: boolean;
14
+ deleteLink: (id: string) => void;
15
+ }
16
+ /**
17
+ * Custom hook that provides useful links data and CRUD handlers.
18
+ */
19
+ export declare const useUsefulLinksContainer: () => UseUsefulLinksContainerReturn;
@@ -0,0 +1,26 @@
1
+ import { useUsefulLinks, useCreateUsefulLink, useUpdateUsefulLink, useDeleteUsefulLink } from "../services/queries/usefulLinks.js";
2
+ const MAX_USEFUL_LINKS = 10, useUsefulLinksContainer = () => {
3
+ const {
4
+ data: links,
5
+ isLoading,
6
+ error
7
+ } = useUsefulLinks(), createMutation = useCreateUsefulLink(), updateMutation = useUpdateUsefulLink(), deleteMutation = useDeleteUsefulLink();
8
+ return {
9
+ links: links ?? [],
10
+ isLoading,
11
+ error,
12
+ canAddLink: ((links == null ? void 0 : links.length) ?? 0) < MAX_USEFUL_LINKS,
13
+ createLink: (payload) => createMutation.mutateAsync(payload),
14
+ isCreating: createMutation.isPending,
15
+ updateLink: (id, payload) => updateMutation.mutateAsync({
16
+ id,
17
+ payload
18
+ }),
19
+ isUpdating: updateMutation.isPending,
20
+ deleteLink: (id) => deleteMutation.mutate(id)
21
+ };
22
+ };
23
+ export {
24
+ MAX_USEFUL_LINKS,
25
+ useUsefulLinksContainer
26
+ };
@@ -0,0 +1,5 @@
1
+ export * from './LinkForm';
2
+ export * from './UsefulLinks';
3
+ export * from './UsefulLinksContainer';
4
+ export * from './UsefulLinksModal';
5
+ export * from './hooks/useUsefulLinksContainer';