@keycloakify/keycloak-account-ui 25.0.4-rc.7 → 26.0.0-rc.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 (193) hide show
  1. package/KcAccountUiLoader.d.ts +1 -0
  2. package/KcAccountUiLoader.js +2 -1
  3. package/KcAccountUiLoader.js.map +1 -1
  4. package/README.md +19 -19
  5. package/account-security/AccountRow.js +5 -4
  6. package/account-security/AccountRow.js.map +1 -1
  7. package/account-security/DeviceActivity.js +5 -4
  8. package/account-security/DeviceActivity.js.map +1 -1
  9. package/account-security/LinkedAccounts.js +24 -9
  10. package/account-security/LinkedAccounts.js.map +1 -1
  11. package/account-security/LinkedAccountsToolbar.d.ts +12 -0
  12. package/account-security/LinkedAccountsToolbar.js +24 -0
  13. package/account-security/LinkedAccountsToolbar.js.map +1 -0
  14. package/account-security/SigningIn.js +1 -1
  15. package/account-security/SigningIn.js.map +1 -1
  16. package/api/methods.d.ts +7 -1
  17. package/api/methods.js +9 -2
  18. package/api/methods.js.map +1 -1
  19. package/api/parse-links.js +3 -3
  20. package/api/parse-links.js.map +1 -1
  21. package/api/parse-response.d.ts +2 -0
  22. package/api/parse-response.js +11 -15
  23. package/api/parse-response.js.map +1 -1
  24. package/api/request.js +1 -1
  25. package/api/request.js.map +1 -1
  26. package/api.js +1 -7
  27. package/api.js.map +1 -1
  28. package/applications/Applications.js +5 -4
  29. package/applications/Applications.js.map +1 -1
  30. package/environment.d.ts +1 -0
  31. package/environment.js.map +1 -1
  32. package/messages/messages_ca.properties +105 -12
  33. package/messages/messages_de.properties +23 -1
  34. package/messages/messages_en.properties +16 -1
  35. package/messages/messages_es.properties +1 -1
  36. package/messages/messages_fr.properties +64 -19
  37. package/messages/messages_it.properties +3 -0
  38. package/messages/messages_ka.properties +15 -0
  39. package/messages/messages_nl.properties +2 -0
  40. package/organizations/Organizations.d.ts +2 -0
  41. package/organizations/Organizations.js +19 -0
  42. package/organizations/Organizations.js.map +1 -0
  43. package/package.json +89 -29
  44. package/personal-info/PersonalInfo.js +11 -9
  45. package/personal-info/PersonalInfo.js.map +1 -1
  46. package/public/content.d.ts +4 -0
  47. package/public/content.js +5 -0
  48. package/public/content.js.map +1 -1
  49. package/resources/EditTheResource.js +4 -3
  50. package/resources/EditTheResource.js.map +1 -1
  51. package/resources/PermissionRequest.js +4 -3
  52. package/resources/PermissionRequest.js.map +1 -1
  53. package/resources/ResourcesTab.js +4 -3
  54. package/resources/ResourcesTab.js.map +1 -1
  55. package/resources/ShareTheResource.js +4 -3
  56. package/resources/ShareTheResource.js.map +1 -1
  57. package/root/Header.js +1 -1
  58. package/root/Header.js.map +1 -1
  59. package/root/PageNav.js +1 -1
  60. package/root/PageNav.js.map +1 -1
  61. package/routes.d.ts +1 -0
  62. package/routes.js +6 -0
  63. package/routes.js.map +1 -1
  64. package/src/KcAccountUiLoader.tsx +2 -0
  65. package/src/account-security/AccountRow.tsx +6 -8
  66. package/src/account-security/DeviceActivity.tsx +10 -9
  67. package/src/account-security/LinkedAccounts.tsx +107 -30
  68. package/src/account-security/LinkedAccountsToolbar.tsx +88 -0
  69. package/src/account-security/SigningIn.tsx +1 -1
  70. package/src/api/methods.ts +22 -2
  71. package/src/api/parse-links.ts +3 -3
  72. package/src/api/parse-response.ts +22 -23
  73. package/src/api/request.ts +1 -1
  74. package/src/api.ts +1 -7
  75. package/src/applications/Applications.tsx +19 -11
  76. package/src/environment.ts +1 -0
  77. package/src/organizations/Organizations.tsx +48 -0
  78. package/src/personal-info/PersonalInfo.tsx +10 -8
  79. package/src/public/content.ts +5 -0
  80. package/src/resources/EditTheResource.tsx +8 -7
  81. package/src/resources/PermissionRequest.tsx +5 -3
  82. package/src/resources/ResourcesTab.tsx +8 -7
  83. package/src/resources/ShareTheResource.tsx +9 -8
  84. package/src/root/Header.tsx +0 -1
  85. package/src/root/PageNav.tsx +1 -1
  86. package/src/routes.tsx +7 -0
  87. package/src/ui-shared/alerts/AlertPanel.tsx +43 -0
  88. package/src/ui-shared/alerts/Alerts.tsx +48 -52
  89. package/src/ui-shared/context/environment.ts +1 -1
  90. package/src/ui-shared/controls/KeycloakSpinner.tsx +12 -0
  91. package/src/ui-shared/controls/OrganizationTable.tsx +122 -0
  92. package/src/ui-shared/controls/select-control/SingleSelectControl.tsx +3 -1
  93. package/src/ui-shared/controls/select-control/TypeaheadSelectControl.tsx +5 -3
  94. package/src/ui-shared/controls/table/KeycloakDataTable.tsx +597 -0
  95. package/src/ui-shared/controls/table/ListEmptyState.tsx +86 -0
  96. package/src/ui-shared/controls/table/PaginatingTableToolbar.tsx +106 -0
  97. package/src/ui-shared/controls/table/TableToolbar.tsx +92 -0
  98. package/src/ui-shared/main.ts +35 -1
  99. package/src/ui-shared/masthead/Masthead.tsx +64 -48
  100. package/src/ui-shared/select/SingleSelect.tsx +2 -0
  101. package/src/ui-shared/select/TypeaheadSelect.tsx +2 -0
  102. package/src/ui-shared/user-profile/LocaleSelector.tsx +1 -1
  103. package/src/ui-shared/user-profile/UserProfileFields.tsx +18 -21
  104. package/src/ui-shared/user-profile/UserProfileGroup.tsx +3 -2
  105. package/src/ui-shared/user-profile/utils.ts +12 -6
  106. package/src/ui-shared/utils/ErrorBoundary.tsx +77 -0
  107. package/src/ui-shared/utils/darkMode.ts +19 -0
  108. package/src/ui-shared/utils/errors.ts +55 -0
  109. package/src/ui-shared/utils/generateId.ts +1 -0
  110. package/src/ui-shared/utils/useFetch.ts +44 -0
  111. package/src/ui-shared/utils/useSetTimeout.ts +40 -0
  112. package/src/utils/useAccountAlerts.ts +28 -0
  113. package/src/utils/usePromise.ts +8 -3
  114. package/src/zKcContextLike.ts +1 -0
  115. package/ui-shared/alerts/AlertPanel.d.ts +6 -0
  116. package/ui-shared/alerts/AlertPanel.js +6 -0
  117. package/ui-shared/alerts/AlertPanel.js.map +1 -0
  118. package/ui-shared/alerts/Alerts.d.ts +2 -3
  119. package/ui-shared/alerts/Alerts.js +32 -22
  120. package/ui-shared/alerts/Alerts.js.map +1 -1
  121. package/ui-shared/context/environment.js +1 -1
  122. package/ui-shared/context/environment.js.map +1 -1
  123. package/ui-shared/controls/KeycloakSpinner.d.ts +1 -0
  124. package/ui-shared/controls/KeycloakSpinner.js +8 -0
  125. package/ui-shared/controls/KeycloakSpinner.js.map +1 -0
  126. package/ui-shared/controls/OrganizationTable.d.ts +16 -0
  127. package/ui-shared/controls/OrganizationTable.js +45 -0
  128. package/ui-shared/controls/OrganizationTable.js.map +1 -0
  129. package/ui-shared/controls/select-control/SingleSelectControl.js +3 -1
  130. package/ui-shared/controls/select-control/SingleSelectControl.js.map +1 -1
  131. package/ui-shared/controls/select-control/TypeaheadSelectControl.js +5 -3
  132. package/ui-shared/controls/select-control/TypeaheadSelectControl.js.map +1 -1
  133. package/ui-shared/controls/table/KeycloakDataTable.d.ts +64 -0
  134. package/ui-shared/controls/table/KeycloakDataTable.js +279 -0
  135. package/ui-shared/controls/table/KeycloakDataTable.js.map +1 -0
  136. package/ui-shared/controls/table/ListEmptyState.d.ts +20 -0
  137. package/ui-shared/controls/table/ListEmptyState.js +11 -0
  138. package/ui-shared/controls/table/ListEmptyState.js.map +1 -0
  139. package/ui-shared/controls/table/PaginatingTableToolbar.d.ts +21 -0
  140. package/ui-shared/controls/table/PaginatingTableToolbar.js +27 -0
  141. package/ui-shared/controls/table/PaginatingTableToolbar.js.map +1 -0
  142. package/ui-shared/controls/table/TableToolbar.d.ts +12 -0
  143. package/ui-shared/controls/table/TableToolbar.js +30 -0
  144. package/ui-shared/controls/table/TableToolbar.js.map +1 -0
  145. package/ui-shared/main.d.ts +15 -1
  146. package/ui-shared/main.js +13 -1
  147. package/ui-shared/main.js.map +1 -1
  148. package/ui-shared/masthead/Masthead.d.ts +4 -7
  149. package/ui-shared/masthead/Masthead.js +14 -14
  150. package/ui-shared/masthead/Masthead.js.map +1 -1
  151. package/ui-shared/select/SingleSelect.d.ts +1 -1
  152. package/ui-shared/select/SingleSelect.js +2 -2
  153. package/ui-shared/select/SingleSelect.js.map +1 -1
  154. package/ui-shared/select/TypeaheadSelect.d.ts +1 -1
  155. package/ui-shared/select/TypeaheadSelect.js +2 -2
  156. package/ui-shared/select/TypeaheadSelect.js.map +1 -1
  157. package/ui-shared/user-profile/LocaleSelector.js +1 -1
  158. package/ui-shared/user-profile/LocaleSelector.js.map +1 -1
  159. package/ui-shared/user-profile/UserProfileFields.d.ts +2 -4
  160. package/ui-shared/user-profile/UserProfileFields.js +0 -18
  161. package/ui-shared/user-profile/UserProfileFields.js.map +1 -1
  162. package/ui-shared/user-profile/UserProfileGroup.js.map +1 -1
  163. package/ui-shared/user-profile/utils.js +2 -2
  164. package/ui-shared/user-profile/utils.js.map +1 -1
  165. package/ui-shared/utils/ErrorBoundary.d.ts +26 -0
  166. package/ui-shared/utils/ErrorBoundary.js +29 -0
  167. package/ui-shared/utils/ErrorBoundary.js.map +1 -0
  168. package/ui-shared/utils/darkMode.d.ts +1 -0
  169. package/ui-shared/utils/darkMode.js +16 -0
  170. package/ui-shared/utils/darkMode.js.map +1 -0
  171. package/ui-shared/utils/errors.d.ts +4 -0
  172. package/ui-shared/utils/errors.js +42 -0
  173. package/ui-shared/utils/errors.js.map +1 -0
  174. package/ui-shared/utils/generateId.d.ts +1 -0
  175. package/ui-shared/utils/generateId.js +2 -0
  176. package/ui-shared/utils/generateId.js.map +1 -0
  177. package/ui-shared/utils/useFetch.d.ts +17 -0
  178. package/ui-shared/utils/useFetch.js +38 -0
  179. package/ui-shared/utils/useFetch.js.map +1 -0
  180. package/ui-shared/utils/useSetTimeout.d.ts +1 -0
  181. package/ui-shared/utils/useSetTimeout.js +32 -0
  182. package/ui-shared/utils/useSetTimeout.js.map +1 -0
  183. package/utils/useAccountAlerts.d.ts +4 -0
  184. package/utils/useAccountAlerts.js +19 -0
  185. package/utils/useAccountAlerts.js.map +1 -0
  186. package/utils/usePromise.js +7 -3
  187. package/utils/usePromise.js.map +1 -1
  188. package/zKcContextLike.js +1 -0
  189. package/zKcContextLike.js.map +1 -1
  190. package/src/utils/isRecord.ts +0 -2
  191. package/utils/isRecord.d.ts +0 -1
  192. package/utils/isRecord.js +0 -2
  193. package/utils/isRecord.js.map +0 -1
@@ -1,3 +1,8 @@
1
+ import {
2
+ ContinueCancelModal,
3
+ label,
4
+ useEnvironment,
5
+ } from "@keycloakify/keycloak-account-ui/ui-shared";
1
6
  import {
2
7
  Button,
3
8
  DataList,
@@ -22,16 +27,13 @@ import {
22
27
  } from "@patternfly/react-icons";
23
28
  import { useState } from "react";
24
29
  import { useTranslation } from "react-i18next";
25
- import {
26
- ContinueCancelModal,
27
- useAlerts,
28
- useEnvironment,
29
- } from "@keycloakify/keycloak-account-ui/ui-shared";
30
+
30
31
  import { deleteConsent, getApplications } from "@keycloakify/keycloak-account-ui/api/methods";
31
32
  import { ClientRepresentation } from "@keycloakify/keycloak-account-ui/api/representations";
32
33
  import { Page } from "@keycloakify/keycloak-account-ui/components/page/Page";
33
34
  import { TFuncKey } from "@keycloakify/keycloak-account-ui/i18n";
34
35
  import { formatDate } from "@keycloakify/keycloak-account-ui/utils/formatDate";
36
+ import { useAccountAlerts } from "@keycloakify/keycloak-account-ui/utils/useAccountAlerts";
35
37
  import { usePromise } from "@keycloakify/keycloak-account-ui/utils/usePromise";
36
38
 
37
39
  type Application = ClientRepresentation & {
@@ -41,7 +43,7 @@ type Application = ClientRepresentation & {
41
43
  export const Applications = () => {
42
44
  const { t } = useTranslation();
43
45
  const context = useEnvironment();
44
- const { addAlert, addError } = useAlerts();
46
+ const { addAlert, addError } = useAccountAlerts();
45
47
 
46
48
  const [applications, setApplications] = useState<Application[]>();
47
49
  const [key, setKey] = useState(1);
@@ -67,7 +69,7 @@ export const Applications = () => {
67
69
  refresh();
68
70
  addAlert(t("removeConsentSuccess"));
69
71
  } catch (error) {
70
- addError(t("removeConsentError", { error }).toString());
72
+ addError("removeConsentError", error);
71
73
  }
72
74
  };
73
75
 
@@ -141,14 +143,20 @@ export const Applications = () => {
141
143
  variant="link"
142
144
  onClick={() => window.open(application.effectiveUrl)}
143
145
  >
144
- {application.clientName || application.clientId}{" "}
146
+ {label(
147
+ t,
148
+ application.clientName || application.clientId,
149
+ )}{" "}
145
150
  <ExternalLinkAltIcon />
146
151
  </Button>
147
152
  )}
148
153
  {!application.effectiveUrl && (
149
- <span>
150
- {application.clientName || application.clientId}
151
- </span>
154
+ <>
155
+ {label(
156
+ t,
157
+ application.clientName || application.clientId,
158
+ )}
159
+ </>
152
160
  )}
153
161
  </DataListCell>,
154
162
  <DataListCell
@@ -25,6 +25,7 @@ export type Feature = {
25
25
  updateEmailFeatureEnabled: boolean;
26
26
  updateEmailActionEnabled: boolean;
27
27
  isViewGroupsEnabled: boolean;
28
+ isViewOrganizationsEnabled: boolean;
28
29
  isOid4VciEnabled: boolean;
29
30
  };
30
31
 
@@ -0,0 +1,48 @@
1
+ import OrganizationRepresentation from "@keycloak/keycloak-admin-client/lib/defs/organizationRepresentation";
2
+ import {
3
+ ErrorBoundaryProvider,
4
+ KeycloakSpinner,
5
+ ListEmptyState,
6
+ OrganizationTable,
7
+ useEnvironment,
8
+ } from "@keycloakify/keycloak-account-ui/ui-shared";
9
+ import { useState } from "react";
10
+ import { useTranslation } from "react-i18next";
11
+ import { getUserOrganizations } from "@keycloakify/keycloak-account-ui/api/methods";
12
+ import { Page } from "@keycloakify/keycloak-account-ui/components/page/Page";
13
+ import { Environment } from "@keycloakify/keycloak-account-ui/environment";
14
+ import { usePromise } from "@keycloakify/keycloak-account-ui/utils/usePromise";
15
+
16
+ export const Organizations = () => {
17
+ const { t } = useTranslation();
18
+ const context = useEnvironment<Environment>();
19
+
20
+ const [userOrgs, setUserOrgs] = useState<OrganizationRepresentation[]>([]);
21
+
22
+ usePromise(
23
+ (signal) => getUserOrganizations({ signal, context }),
24
+ setUserOrgs,
25
+ );
26
+
27
+ if (!userOrgs) {
28
+ return <KeycloakSpinner />;
29
+ }
30
+
31
+ return (
32
+ <Page title={t("organizations")} description={t("organizationDescription")}>
33
+ <ErrorBoundaryProvider>
34
+ <OrganizationTable
35
+ link={({ children }) => <span>{children}</span>}
36
+ loader={userOrgs}
37
+ >
38
+ <ListEmptyState
39
+ message={t("emptyUserOrganizations")}
40
+ instructions={t("emptyUserOrganizationsInstructions")}
41
+ />
42
+ </OrganizationTable>
43
+ </ErrorBoundaryProvider>
44
+ </Page>
45
+ );
46
+ };
47
+
48
+ export default Organizations;
@@ -4,12 +4,12 @@ import {
4
4
  beerify,
5
5
  debeerify,
6
6
  setUserProfileServerError,
7
- useAlerts,
8
7
  useEnvironment,
9
8
  } from "@keycloakify/keycloak-account-ui/ui-shared";
10
9
  import {
11
10
  ActionGroup,
12
11
  Alert,
12
+ AlertVariant,
13
13
  Button,
14
14
  ExpandableSection,
15
15
  Form,
@@ -33,6 +33,7 @@ import {
33
33
  import { Page } from "@keycloakify/keycloak-account-ui/components/page/Page";
34
34
  import type { Environment } from "@keycloakify/keycloak-account-ui/environment";
35
35
  import { TFuncKey } from "@keycloakify/keycloak-account-ui/i18n";
36
+ import { useAccountAlerts } from "@keycloakify/keycloak-account-ui/utils/useAccountAlerts";
36
37
  import { usePromise } from "@keycloakify/keycloak-account-ui/utils/usePromise";
37
38
 
38
39
  export const PersonalInfo = () => {
@@ -43,7 +44,7 @@ export const PersonalInfo = () => {
43
44
  const [supportedLocales, setSupportedLocales] = useState<string[]>([]);
44
45
  const form = useForm<UserRepresentation>({ mode: "onChange" });
45
46
  const { handleSubmit, reset, setValue, setError } = form;
46
- const { addAlert, addError } = useAlerts();
47
+ const { addAlert } = useAccountAlerts();
47
48
 
48
49
  usePromise(
49
50
  (signal) =>
@@ -71,15 +72,16 @@ export const PersonalInfo = () => {
71
72
  );
72
73
  await savePersonalInfo(context, { ...user, attributes });
73
74
  const locale = attributes["locale"]?.toString();
74
- getI18n().changeLanguage(locale, (error) => {
75
- if (error) {
76
- console.warn("Error(s) loading locale", locale, error);
77
- }
78
- });
75
+ if (locale)
76
+ getI18n().changeLanguage(locale, (error) => {
77
+ if (error) {
78
+ console.warn("Error(s) loading locale", locale, error);
79
+ }
80
+ });
79
81
  context.keycloak.updateToken();
80
82
  addAlert(t("accountUpdatedMessage"));
81
83
  } catch (error) {
82
- addError(t("accountUpdatedError").toString());
84
+ addAlert(t("accountUpdatedError"), AlertVariant.danger);
83
85
 
84
86
  setUserProfileServerError(
85
87
  { responseData: { errors: error as any } },
@@ -30,6 +30,11 @@ const content = [
30
30
  "path": "groups",
31
31
  "isVisible": "isViewGroupsEnabled"
32
32
  },
33
+ {
34
+ "label": "organizations",
35
+ "path": "organizations",
36
+ "isVisible": "isViewOrganizationsEnabled"
37
+ },
33
38
  {
34
39
  "label": "resources",
35
40
  "path": "resources",
@@ -1,15 +1,16 @@
1
- import { Button, Form, Modal } from "@patternfly/react-core";
2
- import { Fragment, useEffect } from "react";
3
- import { FormProvider, useFieldArray, useForm } from "react-hook-form";
4
- import { useTranslation } from "react-i18next";
5
1
  import {
6
2
  SelectControl,
7
3
  TextControl,
8
- useAlerts,
9
4
  useEnvironment,
10
5
  } from "@keycloakify/keycloak-account-ui/ui-shared";
6
+ import { Button, Form, Modal } from "@patternfly/react-core";
7
+ import { Fragment, useEffect } from "react";
8
+ import { FormProvider, useFieldArray, useForm } from "react-hook-form";
9
+ import { useTranslation } from "react-i18next";
10
+
11
11
  import { updatePermissions } from "@keycloakify/keycloak-account-ui/api";
12
12
  import type { Permission, Resource } from "@keycloakify/keycloak-account-ui/api/representations";
13
+ import { useAccountAlerts } from "@keycloakify/keycloak-account-ui/utils/useAccountAlerts";
13
14
 
14
15
  type EditTheResourceProps = {
15
16
  resource: Resource;
@@ -28,7 +29,7 @@ export const EditTheResource = ({
28
29
  }: EditTheResourceProps) => {
29
30
  const { t } = useTranslation();
30
31
  const context = useEnvironment();
31
- const { addAlert, addError } = useAlerts();
32
+ const { addAlert, addError } = useAccountAlerts();
32
33
 
33
34
  const form = useForm<FormValues>();
34
35
  const { control, reset, handleSubmit } = form;
@@ -50,7 +51,7 @@ export const EditTheResource = ({
50
51
  addAlert(t("updateSuccess"));
51
52
  onClose();
52
53
  } catch (error) {
53
- addError(t("updateError", { error }).toString());
54
+ addError("updateError", error);
54
55
  }
55
56
  };
56
57
 
@@ -1,3 +1,4 @@
1
+ import { useEnvironment } from "@keycloakify/keycloak-account-ui/ui-shared";
1
2
  import {
2
3
  Badge,
3
4
  Button,
@@ -11,9 +12,10 @@ import { UserCheckIcon } from "@patternfly/react-icons";
11
12
  import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
12
13
  import { useState } from "react";
13
14
  import { useTranslation } from "react-i18next";
14
- import { useAlerts, useEnvironment } from "@keycloakify/keycloak-account-ui/ui-shared";
15
+
15
16
  import { fetchPermission, updateRequest } from "@keycloakify/keycloak-account-ui/api";
16
17
  import { Permission, Resource } from "@keycloakify/keycloak-account-ui/api/representations";
18
+ import { useAccountAlerts } from "@keycloakify/keycloak-account-ui/utils/useAccountAlerts";
17
19
 
18
20
  type PermissionRequestProps = {
19
21
  resource: Resource;
@@ -26,7 +28,7 @@ export const PermissionRequest = ({
26
28
  }: PermissionRequestProps) => {
27
29
  const { t } = useTranslation();
28
30
  const context = useEnvironment();
29
- const { addAlert, addError } = useAlerts();
31
+ const { addAlert, addError } = useAccountAlerts();
30
32
 
31
33
  const [open, setOpen] = useState(false);
32
34
 
@@ -54,7 +56,7 @@ export const PermissionRequest = ({
54
56
  toggle();
55
57
  refresh();
56
58
  } catch (error) {
57
- addError(t("shareError", { error }).toString());
59
+ addError("shareError", error);
58
60
  }
59
61
  };
60
62
 
@@ -1,3 +1,7 @@
1
+ import {
2
+ ContinueCancelModal,
3
+ useEnvironment,
4
+ } from "@keycloakify/keycloak-account-ui/ui-shared";
1
5
  import {
2
6
  Button,
3
7
  Chip,
@@ -32,15 +36,12 @@ import {
32
36
  } from "@patternfly/react-table";
33
37
  import { useState } from "react";
34
38
  import { useTranslation } from "react-i18next";
35
- import {
36
- ContinueCancelModal,
37
- useAlerts,
38
- useEnvironment,
39
- } from "@keycloakify/keycloak-account-ui/ui-shared";
39
+
40
40
  import { fetchPermission, fetchResources, updatePermissions } from "@keycloakify/keycloak-account-ui/api";
41
41
  import { getPermissionRequests } from "@keycloakify/keycloak-account-ui/api/methods";
42
42
  import { Links } from "@keycloakify/keycloak-account-ui/api/parse-links";
43
43
  import { Permission, Resource } from "@keycloakify/keycloak-account-ui/api/representations";
44
+ import { useAccountAlerts } from "@keycloakify/keycloak-account-ui/utils/useAccountAlerts";
44
45
  import { usePromise } from "@keycloakify/keycloak-account-ui/utils/usePromise";
45
46
  import { EditTheResource } from "@keycloakify/keycloak-account-ui/resources/EditTheResource";
46
47
  import { PermissionRequest } from "@keycloakify/keycloak-account-ui/resources/PermissionRequest";
@@ -63,7 +64,7 @@ type ResourcesTabProps = {
63
64
  export const ResourcesTab = ({ isShared = false }: ResourcesTabProps) => {
64
65
  const { t } = useTranslation();
65
66
  const context = useEnvironment();
66
- const { addAlert, addError } = useAlerts();
67
+ const { addAlert, addError } = useAccountAlerts();
67
68
 
68
69
  const [params, setParams] = useState<Record<string, string>>({
69
70
  first: "0",
@@ -128,7 +129,7 @@ export const ResourcesTab = ({ isShared = false }: ResourcesTabProps) => {
128
129
  setDetails({});
129
130
  addAlert(t("unShareSuccess"));
130
131
  } catch (error) {
131
- addError(t("unShareError", { error }).toString());
132
+ addError("unShareError", error);
132
133
  }
133
134
  };
134
135
 
@@ -1,3 +1,8 @@
1
+ import {
2
+ FormErrorText,
3
+ SelectControl,
4
+ useEnvironment,
5
+ } from "@keycloakify/keycloak-account-ui/ui-shared";
1
6
  import {
2
7
  Button,
3
8
  Chip,
@@ -18,14 +23,10 @@ import {
18
23
  useWatch,
19
24
  } from "react-hook-form";
20
25
  import { useTranslation } from "react-i18next";
21
- import {
22
- FormErrorText,
23
- SelectControl,
24
- useAlerts,
25
- useEnvironment,
26
- } from "@keycloakify/keycloak-account-ui/ui-shared";
26
+
27
27
  import { updateRequest } from "@keycloakify/keycloak-account-ui/api";
28
28
  import { Permission, Resource } from "@keycloakify/keycloak-account-ui/api/representations";
29
+ import { useAccountAlerts } from "@keycloakify/keycloak-account-ui/utils/useAccountAlerts";
29
30
  import { SharedWith } from "@keycloakify/keycloak-account-ui/resources/SharedWith";
30
31
 
31
32
  type ShareTheResourceProps = {
@@ -48,7 +49,7 @@ export const ShareTheResource = ({
48
49
  }: ShareTheResourceProps) => {
49
50
  const { t } = useTranslation();
50
51
  const context = useEnvironment();
51
- const { addAlert, addError } = useAlerts();
52
+ const { addAlert, addError } = useAccountAlerts();
52
53
  const form = useForm<FormValues>();
53
54
  const {
54
55
  control,
@@ -92,7 +93,7 @@ export const ShareTheResource = ({
92
93
  addAlert(t("shareSuccess"));
93
94
  onClose();
94
95
  } catch (error) {
95
- addError(t("shareError", { error }).toString());
96
+ addError("shareError", error);
96
97
  }
97
98
  reset({});
98
99
  };
@@ -49,7 +49,6 @@ export const Header = () => {
49
49
  data-testid="page-header"
50
50
  keycloak={keycloak}
51
51
  features={{ hasManageAccount: false }}
52
- showNavToggle
53
52
  brand={{
54
53
  href: indexHref,
55
54
  src: joinPath(environment.resourceUrl, brandImage),
@@ -137,7 +137,7 @@ export const NavLink = ({
137
137
  isActive,
138
138
  children,
139
139
  }: PropsWithChildren<NavLinkProps>) => {
140
- const menuItemPath = getFullUrl(path);
140
+ const menuItemPath = getFullUrl(path) + `?${location.search}`;
141
141
  const href = useHref(menuItemPath);
142
142
  const handleClick = useLinkClickHandler(menuItemPath);
143
143
 
package/src/routes.tsx CHANGED
@@ -2,6 +2,7 @@ import { lazy } from "react";
2
2
  import type { IndexRouteObject, RouteObject } from "react-router-dom";
3
3
 
4
4
  import { environment } from "@keycloakify/keycloak-account-ui/environment";
5
+ import { Organizations } from "@keycloakify/keycloak-account-ui/organizations/Organizations";
5
6
  import { ErrorPage } from "@keycloakify/keycloak-account-ui/root/ErrorPage";
6
7
  import { Root } from "@keycloakify/keycloak-account-ui/root/Root";
7
8
 
@@ -59,6 +60,11 @@ export const PersonalInfoRoute: IndexRouteObject = {
59
60
  element: <PersonalInfo />,
60
61
  };
61
62
 
63
+ export const OrganizationsRoute: RouteObject = {
64
+ path: "organizations",
65
+ element: <Organizations />,
66
+ };
67
+
62
68
  export const Oid4VciRoute: RouteObject = {
63
69
  path: "oid4vci",
64
70
  element: <Oid4Vci />,
@@ -75,6 +81,7 @@ export const RootRoute: RouteObject = {
75
81
  SigningInRoute,
76
82
  ApplicationsRoute,
77
83
  GroupsRoute,
84
+ OrganizationsRoute,
78
85
  PersonalInfoRoute,
79
86
  ResourcesRoute,
80
87
  ContentRoute,
@@ -0,0 +1,43 @@
1
+ import {
2
+ AlertGroup,
3
+ Alert,
4
+ AlertActionCloseButton,
5
+ AlertVariant,
6
+ } from "@patternfly/react-core";
7
+
8
+ import type { AlertEntry } from "@keycloakify/keycloak-account-ui/ui-shared/alerts/Alerts";
9
+
10
+ export type AlertPanelProps = {
11
+ alerts: AlertEntry[];
12
+ onCloseAlert: (id: number) => void;
13
+ };
14
+
15
+ export function AlertPanel({ alerts, onCloseAlert }: AlertPanelProps) {
16
+ return (
17
+ <AlertGroup
18
+ data-testid="global-alerts"
19
+ isToast
20
+ style={{ whiteSpace: "pre-wrap" }}
21
+ >
22
+ {alerts.map(({ id, variant, message, description }, index) => (
23
+ <Alert
24
+ key={id}
25
+ data-testid={index === 0 ? "last-alert" : undefined}
26
+ isLiveRegion
27
+ variant={AlertVariant[variant]}
28
+ component="p"
29
+ variantLabel=""
30
+ title={message}
31
+ actionClose={
32
+ <AlertActionCloseButton
33
+ title={message}
34
+ onClose={() => onCloseAlert(id)}
35
+ />
36
+ }
37
+ >
38
+ {description && <p>{description}</p>}
39
+ </Alert>
40
+ ))}
41
+ </AlertGroup>
42
+ );
43
+ }
@@ -1,10 +1,15 @@
1
- import {
2
- Alert,
3
- AlertActionCloseButton,
4
- AlertGroup,
5
- AlertVariant,
6
- } from "@patternfly/react-core";
7
- import { createContext, PropsWithChildren, useContext, useState } from "react";
1
+ import { AlertVariant } from "@patternfly/react-core";
2
+ import { PropsWithChildren, useCallback, useMemo, useState } from "react";
3
+ import { useTranslation } from "react-i18next";
4
+
5
+ import { createNamedContext } from "@keycloakify/keycloak-account-ui/ui-shared/utils/createNamedContext";
6
+ import { getErrorDescription, getErrorMessage } from "@keycloakify/keycloak-account-ui/ui-shared/utils/errors";
7
+ import { generateId } from "@keycloakify/keycloak-account-ui/ui-shared/utils/generateId";
8
+ import { useRequiredContext } from "@keycloakify/keycloak-account-ui/ui-shared/utils/useRequiredContext";
9
+ import { useSetTimeout } from "@keycloakify/keycloak-account-ui/ui-shared/utils/useSetTimeout";
10
+ import { AlertPanel } from "@keycloakify/keycloak-account-ui/ui-shared/alerts/AlertPanel";
11
+
12
+ const ALERT_TIMEOUT = 8000;
8
13
 
9
14
  export type AddAlertFunction = (
10
15
  message: string,
@@ -12,18 +17,21 @@ export type AddAlertFunction = (
12
17
  description?: string,
13
18
  ) => void;
14
19
 
15
- export type AddErrorFunction = (message: string) => void;
20
+ export type AddErrorFunction = (messageKey: string, error: unknown) => void;
16
21
 
17
22
  export type AlertProps = {
18
23
  addAlert: AddAlertFunction;
19
24
  addError: AddErrorFunction;
20
25
  };
21
26
 
22
- export const AlertContext = createContext<AlertProps | undefined>(undefined);
27
+ const AlertContext = createNamedContext<AlertProps | undefined>(
28
+ "AlertContext",
29
+ undefined,
30
+ );
23
31
 
24
- export const useAlerts = () => useContext(AlertContext)!;
32
+ export const useAlerts = () => useRequiredContext(AlertContext);
25
33
 
26
- export type AlertType = {
34
+ export type AlertEntry = {
27
35
  id: number;
28
36
  message: string;
29
37
  variant: AlertVariant;
@@ -31,55 +39,43 @@ export type AlertType = {
31
39
  };
32
40
 
33
41
  export const AlertProvider = ({ children }: PropsWithChildren) => {
34
- const [alerts, setAlerts] = useState<AlertType[]>([]);
42
+ const { t } = useTranslation();
43
+ const setTimeout = useSetTimeout();
44
+ const [alerts, setAlerts] = useState<AlertEntry[]>([]);
35
45
 
36
- const hideAlert = (id: number) => {
46
+ const removeAlert = (id: number) =>
37
47
  setAlerts((alerts) => alerts.filter((alert) => alert.id !== id));
38
- };
39
-
40
- const addAlert = (
41
- message: string,
42
- variant: AlertVariant = AlertVariant.success,
43
- description?: string,
44
- ) => {
45
- setAlerts([
46
- {
47
- id: Math.random() * 100,
48
+
49
+ const addAlert = useCallback<AddAlertFunction>(
50
+ (message, variant = AlertVariant.success, description) => {
51
+ const alert: AlertEntry = {
52
+ id: generateId(),
48
53
  message,
49
54
  variant,
50
55
  description,
51
- },
52
- ...alerts,
53
- ]);
54
- };
56
+ };
57
+
58
+ setAlerts((alerts) => [alert, ...alerts]);
59
+ setTimeout(() => removeAlert(alert.id), ALERT_TIMEOUT);
60
+ },
61
+ [setTimeout],
62
+ );
63
+
64
+ const addError = useCallback<AddErrorFunction>(
65
+ (messageKey, error) => {
66
+ const message = t(messageKey, { error: getErrorMessage(error) });
67
+ const description = getErrorDescription(error);
68
+
69
+ addAlert(message, AlertVariant.danger, description);
70
+ },
71
+ [addAlert, t],
72
+ );
55
73
 
56
- const addError = (message: string) => {
57
- addAlert(message, AlertVariant.danger);
58
- };
74
+ const value = useMemo(() => ({ addAlert, addError }), [addAlert, addError]);
59
75
 
60
76
  return (
61
- <AlertContext.Provider value={{ addAlert, addError }}>
62
- <AlertGroup isToast data-testid="alerts">
63
- {alerts.map(({ id, variant, message, description }) => (
64
- <Alert
65
- key={id}
66
- isLiveRegion
67
- variant={AlertVariant[variant]}
68
- variantLabel=""
69
- title={message}
70
- actionClose={
71
- <AlertActionCloseButton
72
- title={message}
73
- onClose={() => hideAlert(id)}
74
- />
75
- }
76
- timeout
77
- onTimeout={() => hideAlert(id)}
78
- >
79
- {description && <p>{description}</p>}
80
- </Alert>
81
- ))}
82
- </AlertGroup>
77
+ <AlertContext.Provider value={value}>
78
+ <AlertPanel alerts={alerts} onCloseAlert={removeAlert} />
83
79
  {children}
84
80
  </AlertContext.Provider>
85
81
  );
@@ -44,7 +44,7 @@ export function getInjectedEnvironment<T>(): T {
44
44
 
45
45
  try {
46
46
  return JSON.parse(contents);
47
- } catch (error) {
47
+ } catch {
48
48
  throw new Error("Unable to parse environment variables as JSON.");
49
49
  }
50
50
  }
@@ -0,0 +1,12 @@
1
+ import { Bullseye, Spinner } from "@patternfly/react-core";
2
+ import { useTranslation } from "react-i18next";
3
+
4
+ export const KeycloakSpinner = () => {
5
+ const { t } = useTranslation();
6
+
7
+ return (
8
+ <Bullseye>
9
+ <Spinner aria-label={t("spinnerLoading")} />
10
+ </Bullseye>
11
+ );
12
+ };