@oussemasahbeni/keycloakify-login-shadcn 250004.0.3 → 250004.0.7

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 (169) hide show
  1. package/keycloak-theme/login/KcContext.ts +23 -23
  2. package/keycloak-theme/login/KcPage.tsx +47 -47
  3. package/keycloak-theme/login/assets/img/auth-logo.svg +100 -100
  4. package/keycloak-theme/login/assets/img/shape.svg +71 -71
  5. package/keycloak-theme/login/components/LogoutOtherSessions.tsx +26 -26
  6. package/keycloak-theme/login/components/PasswordWrapper.tsx +35 -35
  7. package/keycloak-theme/login/components/Template/Template.tsx +227 -227
  8. package/keycloak-theme/login/components/Template/index.ts +1 -1
  9. package/keycloak-theme/login/components/Template/useInitializeTemplate.ts +61 -61
  10. package/keycloak-theme/login/components/UserProfileFormFields/AddRemoveButtonsMultiValuedAttribute.tsx +61 -61
  11. package/keycloak-theme/login/components/UserProfileFormFields/DO_MAKE_USER_CONFIRM_PASSWORD.ts +2 -2
  12. package/keycloak-theme/login/components/UserProfileFormFields/FieldErrors.tsx +28 -28
  13. package/keycloak-theme/login/components/UserProfileFormFields/GroupLabel.tsx +70 -70
  14. package/keycloak-theme/login/components/UserProfileFormFields/InputFieldByType.tsx +58 -58
  15. package/keycloak-theme/login/components/UserProfileFormFields/InputTag.tsx +116 -116
  16. package/keycloak-theme/login/components/UserProfileFormFields/InputTagSelects.tsx +135 -135
  17. package/keycloak-theme/login/components/UserProfileFormFields/SelectTag.tsx +114 -114
  18. package/keycloak-theme/login/components/UserProfileFormFields/TextareaTag.tsx +42 -42
  19. package/keycloak-theme/login/components/UserProfileFormFields/UserProfileFormFields.tsx +127 -127
  20. package/keycloak-theme/login/components/UserProfileFormFields/index.ts +1 -1
  21. package/keycloak-theme/login/i18n.ts +47 -47
  22. package/keycloak-theme/login/mocks/getKcContextMock.ts +22 -22
  23. package/keycloak-theme/login/pages/PageIndex.tsx +134 -134
  24. package/keycloak-theme/login/pages/code/Page.stories.tsx +54 -66
  25. package/keycloak-theme/login/pages/code/Page.tsx +89 -89
  26. package/keycloak-theme/login/pages/code/index.ts +3 -3
  27. package/keycloak-theme/login/pages/delete-account-confirm/Page.stories.tsx +39 -46
  28. package/keycloak-theme/login/pages/delete-account-confirm/Page.tsx +63 -63
  29. package/keycloak-theme/login/pages/delete-account-confirm/index.ts +3 -3
  30. package/keycloak-theme/login/pages/delete-credential/Page.stories.tsx +26 -30
  31. package/keycloak-theme/login/pages/delete-credential/Page.tsx +51 -51
  32. package/keycloak-theme/login/pages/delete-credential/index.ts +3 -3
  33. package/keycloak-theme/login/pages/error/Page.stories.tsx +47 -58
  34. package/keycloak-theme/login/pages/error/Page.tsx +42 -42
  35. package/keycloak-theme/login/pages/error/index.ts +3 -3
  36. package/keycloak-theme/login/pages/frontchannel-logout/Page.stories.tsx +25 -32
  37. package/keycloak-theme/login/pages/frontchannel-logout/Page.tsx +84 -84
  38. package/keycloak-theme/login/pages/frontchannel-logout/index.ts +3 -3
  39. package/keycloak-theme/login/pages/idp-review-user-profile/Page.stories.tsx +46 -58
  40. package/keycloak-theme/login/pages/idp-review-user-profile/Page.tsx +52 -52
  41. package/keycloak-theme/login/pages/idp-review-user-profile/index.ts +3 -3
  42. package/keycloak-theme/login/pages/info/Page.stories.tsx +50 -60
  43. package/keycloak-theme/login/pages/info/Page.tsx +92 -92
  44. package/keycloak-theme/login/pages/link-idp-action/Page.stories.tsx +32 -16
  45. package/keycloak-theme/login/pages/link-idp-action/Page.tsx +43 -43
  46. package/keycloak-theme/login/pages/link-idp-action/index.ts +3 -3
  47. package/keycloak-theme/login/pages/login/Form.tsx +242 -242
  48. package/keycloak-theme/login/pages/login/Info.tsx +29 -29
  49. package/keycloak-theme/login/pages/login/Page.stories.tsx +345 -365
  50. package/keycloak-theme/login/pages/login/Page.tsx +44 -44
  51. package/keycloak-theme/login/pages/login/SocialProviders.tsx +107 -107
  52. package/keycloak-theme/login/pages/login/index.ts +3 -3
  53. package/keycloak-theme/login/pages/login/providers/apple.svg +3 -3
  54. package/keycloak-theme/login/pages/login/providers/bitbucket.svg +11 -11
  55. package/keycloak-theme/login/pages/login/providers/discord.svg +4 -4
  56. package/keycloak-theme/login/pages/login/providers/facebook.svg +5 -5
  57. package/keycloak-theme/login/pages/login/providers/github.svg +3 -3
  58. package/keycloak-theme/login/pages/login/providers/gitlab.svg +7 -7
  59. package/keycloak-theme/login/pages/login/providers/google.svg +7 -7
  60. package/keycloak-theme/login/pages/login/providers/instagram.svg +31 -31
  61. package/keycloak-theme/login/pages/login/providers/linkedin.svg +3 -3
  62. package/keycloak-theme/login/pages/login/providers/microsoft.svg +6 -6
  63. package/keycloak-theme/login/pages/login/providers/oidc.svg +5 -5
  64. package/keycloak-theme/login/pages/login/providers/openshift.svg +7 -7
  65. package/keycloak-theme/login/pages/login/providers/paypal.svg +6 -6
  66. package/keycloak-theme/login/pages/login/providers/slack.svg +11 -11
  67. package/keycloak-theme/login/pages/login/providers/stackoverflow.svg +5 -5
  68. package/keycloak-theme/login/pages/login/providers/x.svg +3 -3
  69. package/keycloak-theme/login/pages/login/useProviderLogos.tsx +39 -39
  70. package/keycloak-theme/login/pages/login/useScript.tsx +62 -62
  71. package/keycloak-theme/login/pages/login-config-totp/Page.stories.tsx +45 -59
  72. package/keycloak-theme/login/pages/login-config-totp/Page.tsx +240 -240
  73. package/keycloak-theme/login/pages/login-config-totp/index.ts +3 -3
  74. package/keycloak-theme/login/pages/login-idp-link-confirm/Page.stories.tsx +30 -34
  75. package/keycloak-theme/login/pages/login-idp-link-confirm/Page.tsx +43 -43
  76. package/keycloak-theme/login/pages/login-idp-link-confirm/index.ts +3 -3
  77. package/keycloak-theme/login/pages/login-idp-link-confirm-override/Page.stories.tsx +16 -22
  78. package/keycloak-theme/login/pages/login-idp-link-confirm-override/Page.tsx +47 -47
  79. package/keycloak-theme/login/pages/login-idp-link-confirm-override/index.ts +3 -3
  80. package/keycloak-theme/login/pages/login-idp-link-email/Page.stories.tsx +54 -62
  81. package/keycloak-theme/login/pages/login-idp-link-email/Page.tsx +54 -54
  82. package/keycloak-theme/login/pages/login-idp-link-email/index.ts +3 -3
  83. package/keycloak-theme/login/pages/login-oauth-grant/Page.stories.tsx +39 -45
  84. package/keycloak-theme/login/pages/login-oauth-grant/Page.tsx +126 -126
  85. package/keycloak-theme/login/pages/login-oauth-grant/index.ts +3 -3
  86. package/keycloak-theme/login/pages/login-oauth2-device-verify-user-code/Page.stories.tsx +38 -48
  87. package/keycloak-theme/login/pages/login-oauth2-device-verify-user-code/Page.tsx +58 -58
  88. package/keycloak-theme/login/pages/login-oauth2-device-verify-user-code/index.ts +3 -3
  89. package/keycloak-theme/login/pages/login-otp/Page.stories.tsx +82 -96
  90. package/keycloak-theme/login/pages/login-otp/Page.tsx +108 -108
  91. package/keycloak-theme/login/pages/login-otp/index.ts +3 -3
  92. package/keycloak-theme/login/pages/login-page-expired/Page.stories.tsx +28 -36
  93. package/keycloak-theme/login/pages/login-page-expired/Page.tsx +47 -47
  94. package/keycloak-theme/login/pages/login-page-expired/index.ts +3 -3
  95. package/keycloak-theme/login/pages/login-passkeys-conditional-authenticate/Page.stories.tsx +20 -0
  96. package/keycloak-theme/login/pages/login-passkeys-conditional-authenticate/Page.tsx +233 -233
  97. package/keycloak-theme/login/pages/login-passkeys-conditional-authenticate/index.ts +3 -3
  98. package/keycloak-theme/login/pages/login-passkeys-conditional-authenticate/useScript.tsx +63 -63
  99. package/keycloak-theme/login/pages/login-password/Page.stories.tsx +55 -56
  100. package/keycloak-theme/login/pages/login-password/Page.tsx +149 -149
  101. package/keycloak-theme/login/pages/login-password/index.ts +3 -3
  102. package/keycloak-theme/login/pages/login-password/useScript.tsx +63 -63
  103. package/keycloak-theme/login/pages/login-recovery-authn-code-config/Page.stories.tsx +28 -36
  104. package/keycloak-theme/login/pages/login-recovery-authn-code-config/Page.tsx +181 -181
  105. package/keycloak-theme/login/pages/login-recovery-authn-code-config/index.ts +3 -3
  106. package/keycloak-theme/login/pages/login-recovery-authn-code-config/useScript.tsx +145 -145
  107. package/keycloak-theme/login/pages/login-recovery-authn-code-input/Page.stories.tsx +16 -22
  108. package/keycloak-theme/login/pages/login-recovery-authn-code-input/Page.tsx +70 -70
  109. package/keycloak-theme/login/pages/login-recovery-authn-code-input/index.ts +3 -3
  110. package/keycloak-theme/login/pages/login-reset-otp/Page.stories.tsx +62 -74
  111. package/keycloak-theme/login/pages/login-reset-otp/Page.tsx +86 -86
  112. package/keycloak-theme/login/pages/login-reset-otp/index.ts +3 -3
  113. package/keycloak-theme/login/pages/login-reset-password/Form.tsx +68 -68
  114. package/keycloak-theme/login/pages/login-reset-password/Page.stories.tsx +44 -54
  115. package/keycloak-theme/login/pages/login-reset-password/Page.tsx +27 -27
  116. package/keycloak-theme/login/pages/login-reset-password/index.ts +3 -3
  117. package/keycloak-theme/login/pages/login-update-password/Page.stories.tsx +40 -50
  118. package/keycloak-theme/login/pages/login-update-password/Page.tsx +111 -111
  119. package/keycloak-theme/login/pages/login-update-password/index.ts +3 -3
  120. package/keycloak-theme/login/pages/login-update-profile/Page.stories.tsx +28 -36
  121. package/keycloak-theme/login/pages/login-update-profile/Page.tsx +68 -68
  122. package/keycloak-theme/login/pages/login-update-profile/index.ts +3 -3
  123. package/keycloak-theme/login/pages/login-username/Page.stories.tsx +32 -42
  124. package/keycloak-theme/login/pages/login-username/Page.tsx +246 -246
  125. package/keycloak-theme/login/pages/login-username/index.ts +3 -3
  126. package/keycloak-theme/login/pages/login-username/useScript.tsx +62 -62
  127. package/keycloak-theme/login/pages/login-verify-email/Page.stories.tsx +68 -80
  128. package/keycloak-theme/login/pages/login-verify-email/Page.tsx +38 -38
  129. package/keycloak-theme/login/pages/login-verify-email/index.ts +3 -3
  130. package/keycloak-theme/login/pages/login-x509-info/Page.stories.tsx +29 -37
  131. package/keycloak-theme/login/pages/login-x509-info/Page.tsx +75 -75
  132. package/keycloak-theme/login/pages/login-x509-info/index.ts +3 -3
  133. package/keycloak-theme/login/pages/logout-confirm/Page.stories.tsx +34 -42
  134. package/keycloak-theme/login/pages/logout-confirm/Page.tsx +53 -53
  135. package/keycloak-theme/login/pages/logout-confirm/index.ts +3 -3
  136. package/keycloak-theme/login/pages/register/Form.tsx +106 -106
  137. package/keycloak-theme/login/pages/register/Page.stories.tsx +23 -6
  138. package/keycloak-theme/login/pages/register/Page.tsx +26 -26
  139. package/keycloak-theme/login/pages/register/TermsAcceptance.tsx +56 -56
  140. package/keycloak-theme/login/pages/register/index.ts +3 -3
  141. package/keycloak-theme/login/pages/saml-post-form/Page.stories.tsx +16 -22
  142. package/keycloak-theme/login/pages/saml-post-form/Page.tsx +66 -66
  143. package/keycloak-theme/login/pages/saml-post-form/index.ts +3 -3
  144. package/keycloak-theme/login/pages/select-authenticator/Page.stories.tsx +83 -95
  145. package/keycloak-theme/login/pages/select-authenticator/Page.tsx +100 -100
  146. package/keycloak-theme/login/pages/select-authenticator/index.ts +3 -3
  147. package/keycloak-theme/login/pages/select-organization/Page.stories.tsx +62 -49
  148. package/keycloak-theme/login/pages/select-organization/Page.tsx +126 -126
  149. package/keycloak-theme/login/pages/select-organization/index.ts +3 -3
  150. package/keycloak-theme/login/pages/terms/Page.stories.tsx +15 -0
  151. package/keycloak-theme/login/pages/terms/Page.tsx +51 -51
  152. package/keycloak-theme/login/pages/terms/index.ts +3 -3
  153. package/keycloak-theme/login/pages/update-email/Page.stories.tsx +27 -35
  154. package/keycloak-theme/login/pages/update-email/Page.tsx +62 -62
  155. package/keycloak-theme/login/pages/update-email/index.ts +3 -3
  156. package/keycloak-theme/login/pages/webauthn-authenticate/Page.stories.tsx +112 -126
  157. package/keycloak-theme/login/pages/webauthn-authenticate/Page.tsx +202 -202
  158. package/keycloak-theme/login/pages/webauthn-authenticate/index.ts +3 -3
  159. package/keycloak-theme/login/pages/webauthn-authenticate/useScript.tsx +55 -55
  160. package/keycloak-theme/login/pages/webauthn-error/Page.stories.tsx +54 -66
  161. package/keycloak-theme/login/pages/webauthn-error/Page.tsx +73 -73
  162. package/keycloak-theme/login/pages/webauthn-error/index.ts +3 -3
  163. package/keycloak-theme/login/pages/webauthn-register/Page.stories.tsx +39 -49
  164. package/keycloak-theme/login/pages/webauthn-register/Page.tsx +78 -78
  165. package/keycloak-theme/login/pages/webauthn-register/index.ts +3 -3
  166. package/keycloak-theme/login/pages/webauthn-register/useScript.tsx +62 -62
  167. package/keycloak-theme/login/shared/getColorScheme.ts +45 -45
  168. package/keycloak-theme/login/styleLevelCustomization.tsx +35 -35
  169. package/package.json +5 -1
@@ -11,8 +11,27 @@ export default meta;
11
11
 
12
12
  type Story = StoryObj<typeof meta>;
13
13
 
14
- export const Default: Story = {
15
- render: () => <KcPageStory />
14
+ export const Default: Story = {};
15
+
16
+ export const Arabic: Story = {
17
+ args: {
18
+ kcContext: {
19
+ locale: {
20
+ currentLanguageTag: "ar",
21
+ rtl: true
22
+ }
23
+ }
24
+ }
25
+ };
26
+ export const French: Story = {
27
+ args: {
28
+ kcContext: {
29
+ locale: {
30
+ currentLanguageTag: "fr",
31
+ rtl: false
32
+ }
33
+ }
34
+ }
16
35
  };
17
36
 
18
37
  /**
@@ -22,25 +41,23 @@ export const Default: Story = {
22
41
  * - Key Aspect: Ensures that when there are more than 3 organizations, they are displayed in a grid.
23
42
  */
24
43
  export const WithManyOrganizations: Story = {
25
- render: () => (
26
- <KcPageStory
27
- kcContext={{
28
- url: {
29
- loginAction: "/mock-login-action"
30
- },
31
- user: {
32
- organizations: [
33
- { alias: "org1", name: "Organization 1" },
34
- { alias: "org2", name: "Organization 2" },
35
- { alias: "org3", name: "Organization 3" },
36
- { alias: "org4", name: "Organization 4" },
37
- { alias: "org5", name: "Organization 5" },
38
- { alias: "org6", name: "Organization 6" }
39
- ]
40
- }
41
- }}
42
- />
43
- )
44
+ args: {
45
+ kcContext: {
46
+ url: {
47
+ loginAction: "/mock-login-action"
48
+ },
49
+ user: {
50
+ organizations: [
51
+ { alias: "org1", name: "Organization 1" },
52
+ { alias: "org2", name: "Organization 2" },
53
+ { alias: "org3", name: "Organization 3" },
54
+ { alias: "org4", name: "Organization 4" },
55
+ { alias: "org5", name: "Organization 5" },
56
+ { alias: "org6", name: "Organization 6" }
57
+ ]
58
+ }
59
+ }
60
+ }
44
61
  };
45
62
 
46
63
  /**
@@ -50,22 +67,20 @@ export const WithManyOrganizations: Story = {
50
67
  * - Key Aspect: Ensures that when there are 3 or fewer organizations, they are displayed in a list.
51
68
  */
52
69
  export const WithFewOrganizations: Story = {
53
- render: () => (
54
- <KcPageStory
55
- kcContext={{
56
- url: {
57
- loginAction: "/mock-login-action"
58
- },
59
- user: {
60
- organizations: [
61
- { alias: "org1", name: "Organization 1" },
62
- { alias: "org2", name: "Organization 2" },
63
- { alias: "org3", name: "Organization 3" }
64
- ]
65
- }
66
- }}
67
- />
68
- )
70
+ args: {
71
+ kcContext: {
72
+ url: {
73
+ loginAction: "/mock-login-action"
74
+ },
75
+ user: {
76
+ organizations: [
77
+ { alias: "org1", name: "Organization 1" },
78
+ { alias: "org2", name: "Organization 2" },
79
+ { alias: "org3", name: "Organization 3" }
80
+ ]
81
+ }
82
+ }
83
+ }
69
84
  };
70
85
 
71
86
  /**
@@ -75,16 +90,14 @@ export const WithFewOrganizations: Story = {
75
90
  * - Key Aspect: Ensures that a single organization is displayed correctly.
76
91
  */
77
92
  export const WithSingleOrganization: Story = {
78
- render: () => (
79
- <KcPageStory
80
- kcContext={{
81
- url: {
82
- loginAction: "/mock-login-action"
83
- },
84
- user: {
85
- organizations: [{ alias: "org1", name: "My Organization" }]
86
- }
87
- }}
88
- />
89
- )
93
+ args: {
94
+ kcContext: {
95
+ url: {
96
+ loginAction: "/mock-login-action"
97
+ },
98
+ user: {
99
+ organizations: [{ alias: "org1", name: "My Organization" }]
100
+ }
101
+ }
102
+ }
90
103
  };
@@ -1,126 +1,126 @@
1
- import { Button } from "@/components/ui/button";
2
- import {
3
- Select,
4
- SelectContent,
5
- SelectItem,
6
- SelectTrigger,
7
- SelectValue
8
- } from "@/components/ui/select";
9
- import { useI18n } from '@/login/i18n';
10
- import { useKcContext } from '@/login/KcContext';
11
- import { Building2 } from "lucide-react";
12
- import { MouseEvent, useRef, useState } from "react";
13
- import { assert } from 'tsafe/assert';
14
- import { Template } from "../../components/Template";
15
-
16
-
17
- export function Page() {
18
- const { kcContext } = useKcContext();
19
- assert(kcContext.pageId === "select-organization.ftl");
20
-
21
- const { msg } = useI18n();
22
-
23
- const [isSubmitting, setIsSubmitting] = useState(false);
24
- const [selectedOrg, setSelectedOrg] = useState<string>("");
25
- const formRef = useRef<HTMLFormElement>(null);
26
- const organizationInputRef = useRef<HTMLInputElement>(null);
27
-
28
- const onOrganizationClick =
29
- (organizationAlias: string) => (event: MouseEvent<HTMLButtonElement>) => {
30
- event.preventDefault();
31
-
32
- if (!organizationInputRef.current || !formRef.current) {
33
- return;
34
- }
35
-
36
- organizationInputRef.current.value = organizationAlias;
37
- setIsSubmitting(true);
38
-
39
- if (typeof formRef.current.requestSubmit === "function") {
40
- formRef.current.requestSubmit();
41
- return;
42
- }
43
-
44
- formRef.current.submit();
45
- };
46
-
47
- const onSelectChange = (value: string) => {
48
- setSelectedOrg(value);
49
- if (!organizationInputRef.current || !formRef.current) {
50
- return;
51
- }
52
-
53
- organizationInputRef.current.value = value;
54
- setIsSubmitting(true);
55
-
56
- if (typeof formRef.current.requestSubmit === "function") {
57
- formRef.current.requestSubmit();
58
- return;
59
- }
60
-
61
- formRef.current.submit();
62
- };
63
-
64
- const organizations = kcContext.user.organizations ?? [];
65
- const useSelect = organizations.length > 3;
66
-
67
- return (
68
- <Template
69
- headerNode={msg("organization.selectTitle")}
70
- >
71
- <form ref={formRef} action={kcContext.url.loginAction} method="post">
72
- <div id="kc-user-organizations" className="space-y-2">
73
- <h2 className="text-md font-semibold">
74
- {msg("organization.select")}
75
- </h2>
76
- {useSelect ? (
77
- <div className="space-y-2">
78
- <Select
79
- value={selectedOrg}
80
- onValueChange={onSelectChange}
81
- disabled={isSubmitting}
82
- >
83
- <SelectTrigger className="w-full">
84
- <SelectValue
85
- placeholder={msg("organization.pickPlaceholder")}
86
- />
87
- </SelectTrigger>
88
- <SelectContent>
89
- {organizations.map(({ alias, name }) => (
90
- <SelectItem key={alias} value={alias}>
91
- <div className="flex items-center gap-2">
92
- <Building2 className="w-4 h-4 text-muted-foreground" />
93
- {name ?? alias}
94
- </div>
95
- </SelectItem>
96
- ))}
97
- </SelectContent>
98
- </Select>
99
- </div>
100
- ) : (
101
- <ul className="space-y-3">
102
- {organizations.map(({ alias, name }) => (
103
- <li key={alias}>
104
- <Button
105
- id={`organization-${alias}`}
106
- type="button"
107
- variant="outline"
108
- onClick={onOrganizationClick(alias)}
109
- disabled={isSubmitting}
110
- className="w-full h-auto p-4 flex items-center gap-3 justify-start hover:bg-accent hover:border-primary transition-colors"
111
- >
112
- <Building2 className="w-5 h-5 text-muted-foreground shrink-0" />
113
- <span className="font-medium text-sm">
114
- {name ?? alias}
115
- </span>
116
- </Button>
117
- </li>
118
- ))}
119
- </ul>
120
- )}
121
- </div>
122
- <input ref={organizationInputRef} type="hidden" name="kc.org" />
123
- </form>
124
- </Template>
125
- );
126
- }
1
+ import { Button } from "@/components/ui/button";
2
+ import {
3
+ Select,
4
+ SelectContent,
5
+ SelectItem,
6
+ SelectTrigger,
7
+ SelectValue
8
+ } from "@/components/ui/select";
9
+ import { useI18n } from '@/login/i18n';
10
+ import { useKcContext } from '@/login/KcContext';
11
+ import { Building2 } from "lucide-react";
12
+ import { MouseEvent, useRef, useState } from "react";
13
+ import { assert } from 'tsafe/assert';
14
+ import { Template } from "../../components/Template";
15
+
16
+
17
+ export function Page() {
18
+ const { kcContext } = useKcContext();
19
+ assert(kcContext.pageId === "select-organization.ftl");
20
+
21
+ const { msg } = useI18n();
22
+
23
+ const [isSubmitting, setIsSubmitting] = useState(false);
24
+ const [selectedOrg, setSelectedOrg] = useState<string>("");
25
+ const formRef = useRef<HTMLFormElement>(null);
26
+ const organizationInputRef = useRef<HTMLInputElement>(null);
27
+
28
+ const onOrganizationClick =
29
+ (organizationAlias: string) => (event: MouseEvent<HTMLButtonElement>) => {
30
+ event.preventDefault();
31
+
32
+ if (!organizationInputRef.current || !formRef.current) {
33
+ return;
34
+ }
35
+
36
+ organizationInputRef.current.value = organizationAlias;
37
+ setIsSubmitting(true);
38
+
39
+ if (typeof formRef.current.requestSubmit === "function") {
40
+ formRef.current.requestSubmit();
41
+ return;
42
+ }
43
+
44
+ formRef.current.submit();
45
+ };
46
+
47
+ const onSelectChange = (value: string) => {
48
+ setSelectedOrg(value);
49
+ if (!organizationInputRef.current || !formRef.current) {
50
+ return;
51
+ }
52
+
53
+ organizationInputRef.current.value = value;
54
+ setIsSubmitting(true);
55
+
56
+ if (typeof formRef.current.requestSubmit === "function") {
57
+ formRef.current.requestSubmit();
58
+ return;
59
+ }
60
+
61
+ formRef.current.submit();
62
+ };
63
+
64
+ const organizations = kcContext.user.organizations ?? [];
65
+ const useSelect = organizations.length > 3;
66
+
67
+ return (
68
+ <Template
69
+ headerNode={msg("organization.selectTitle")}
70
+ >
71
+ <form ref={formRef} action={kcContext.url.loginAction} method="post">
72
+ <div id="kc-user-organizations" className="space-y-2">
73
+ <h2 className="text-md font-semibold">
74
+ {msg("organization.select")}
75
+ </h2>
76
+ {useSelect ? (
77
+ <div className="space-y-2">
78
+ <Select
79
+ value={selectedOrg}
80
+ onValueChange={onSelectChange}
81
+ disabled={isSubmitting}
82
+ >
83
+ <SelectTrigger className="w-full">
84
+ <SelectValue
85
+ placeholder={msg("organization.pickPlaceholder")}
86
+ />
87
+ </SelectTrigger>
88
+ <SelectContent>
89
+ {organizations.map(({ alias, name }) => (
90
+ <SelectItem key={alias} value={alias}>
91
+ <div className="flex items-center gap-2">
92
+ <Building2 className="w-4 h-4 text-muted-foreground" />
93
+ {name ?? alias}
94
+ </div>
95
+ </SelectItem>
96
+ ))}
97
+ </SelectContent>
98
+ </Select>
99
+ </div>
100
+ ) : (
101
+ <ul className="space-y-3">
102
+ {organizations.map(({ alias, name }) => (
103
+ <li key={alias}>
104
+ <Button
105
+ id={`organization-${alias}`}
106
+ type="button"
107
+ variant="outline"
108
+ onClick={onOrganizationClick(alias)}
109
+ disabled={isSubmitting}
110
+ className="w-full h-auto p-4 flex items-center gap-3 justify-start hover:bg-accent hover:border-primary transition-colors"
111
+ >
112
+ <Building2 className="w-5 h-5 text-muted-foreground shrink-0" />
113
+ <span className="font-medium text-sm">
114
+ {name ?? alias}
115
+ </span>
116
+ </Button>
117
+ </li>
118
+ ))}
119
+ </ul>
120
+ )}
121
+ </div>
122
+ <input ref={organizationInputRef} type="hidden" name="kc.org" />
123
+ </form>
124
+ </Template>
125
+ );
126
+ }
@@ -1,3 +1,3 @@
1
- import { Page } from "./Page";
2
-
3
- export default Page;
1
+ import { Page } from "./Page";
2
+
3
+ export default Page;
@@ -23,6 +23,21 @@ export const Default: Story = {
23
23
  }
24
24
  };
25
25
 
26
+ export const Arabic: Story = {
27
+ args: {
28
+ kcContext: {
29
+ locale: {
30
+ currentLanguageTag: "ar",
31
+ rtl: true
32
+ },
33
+ "x-keycloakify": {
34
+ messages: {
35
+ termsText: "<p>شروطي باللغة <strong>العربية</strong></p>"
36
+ }
37
+ }
38
+ }
39
+ }
40
+ };
26
41
  export const French: Story = {
27
42
  args: {
28
43
  kcContext: {
@@ -1,51 +1,51 @@
1
- import { Button } from '@/components/ui/button';
2
- import { assert } from "tsafe/assert";
3
- import { useKcContext } from "../../KcContext";
4
- import { Template } from "../../components/Template";
5
- import { useI18n } from "../../i18n";
6
-
7
- export function Page() {
8
- const { kcContext } = useKcContext();
9
- assert(kcContext.pageId === "terms.ftl");
10
-
11
-
12
- const { msg, msgStr } = useI18n();
13
-
14
- const { url } = kcContext;
15
-
16
- return (
17
- <Template displayMessage={false} headerNode={msg("termsTitle")}>
18
- <div className="space-y-6">
19
- <div
20
- id="kc-terms-text"
21
- className="p-4 bg-muted/50 rounded-lg max-h-64 overflow-y-auto text-sm leading-relaxed"
22
- >
23
- {msg("termsText")}
24
- </div>
25
-
26
- <form className="space-y-4" action={url.loginAction} method="POST">
27
- <div className="flex flex-col sm:flex-row gap-3 sm:justify-between">
28
- <Button
29
- variant="outline"
30
- name="cancel"
31
- id="kc-decline"
32
- type="submit"
33
- className="sm:flex-1"
34
- >
35
- {msgStr("doDecline")}
36
- </Button>
37
- <Button
38
- name="accept"
39
- id="kc-accept"
40
- type="submit"
41
- className="sm:flex-1"
42
- >
43
- {msgStr("doAccept")}
44
- </Button>
45
-
46
- </div>
47
- </form>
48
- </div>
49
- </Template>
50
- );
51
- }
1
+ import { Button } from '@/components/ui/button';
2
+ import { assert } from "tsafe/assert";
3
+ import { useKcContext } from "../../KcContext";
4
+ import { Template } from "../../components/Template";
5
+ import { useI18n } from "../../i18n";
6
+
7
+ export function Page() {
8
+ const { kcContext } = useKcContext();
9
+ assert(kcContext.pageId === "terms.ftl");
10
+
11
+
12
+ const { msg, msgStr } = useI18n();
13
+
14
+ const { url } = kcContext;
15
+
16
+ return (
17
+ <Template displayMessage={false} headerNode={msg("termsTitle")}>
18
+ <div className="space-y-6">
19
+ <div
20
+ id="kc-terms-text"
21
+ className="p-4 bg-muted/50 rounded-lg max-h-64 overflow-y-auto text-sm leading-relaxed"
22
+ >
23
+ {msg("termsText")}
24
+ </div>
25
+
26
+ <form className="space-y-4" action={url.loginAction} method="POST">
27
+ <div className="flex flex-col sm:flex-row gap-3 sm:justify-between">
28
+ <Button
29
+ variant="outline"
30
+ name="cancel"
31
+ id="kc-decline"
32
+ type="submit"
33
+ className="sm:flex-1"
34
+ >
35
+ {msgStr("doDecline")}
36
+ </Button>
37
+ <Button
38
+ name="accept"
39
+ id="kc-accept"
40
+ type="submit"
41
+ className="sm:flex-1"
42
+ >
43
+ {msgStr("doAccept")}
44
+ </Button>
45
+
46
+ </div>
47
+ </form>
48
+ </div>
49
+ </Template>
50
+ );
51
+ }
@@ -1,3 +1,3 @@
1
- import { Page } from "./Page";
2
-
3
- export default Page;
1
+ import { Page } from "./Page";
2
+
3
+ export default Page;
@@ -12,32 +12,26 @@ export default meta;
12
12
 
13
13
  type Story = StoryObj<typeof meta>;
14
14
 
15
- export const Default: Story = {
16
- render: () => <KcPageStory />
17
- };
15
+ export const Default: Story = {};
18
16
 
19
17
  export const Arabic: Story = {
20
- render: () => (
21
- <KcPageStory
22
- kcContext={{
23
- locale: {
24
- currentLanguageTag: "ar",
25
- rtl: true
26
- }
27
- }}
28
- />
29
- )
18
+ args: {
19
+ kcContext: {
20
+ locale: {
21
+ currentLanguageTag: "ar",
22
+ rtl: true
23
+ }
24
+ }
25
+ }
30
26
  };
31
27
  export const French: Story = {
32
- render: () => (
33
- <KcPageStory
34
- kcContext={{
35
- locale: {
36
- currentLanguageTag: "fr"
37
- }
38
- }}
39
- />
40
- )
28
+ args: {
29
+ kcContext: {
30
+ locale: {
31
+ currentLanguageTag: "fr"
32
+ }
33
+ }
34
+ }
41
35
  };
42
36
 
43
37
  /**
@@ -47,17 +41,15 @@ export const French: Story = {
47
41
  * - Key Aspect: Ensures the "Cancel" button is visible and functional during app-initiated actions.
48
42
  */
49
43
  export const WithAppInitiatedAction: Story = {
50
- render: () => (
51
- <KcPageStory
52
- kcContext={{
53
- url: {
54
- loginAction: "/mock-login-action"
55
- },
56
- messagesPerField: {
57
- exists: () => false
58
- },
59
- isAppInitiatedAction: true
60
- }}
61
- />
62
- )
44
+ args: {
45
+ kcContext: {
46
+ url: {
47
+ loginAction: "/mock-login-action"
48
+ },
49
+ messagesPerField: {
50
+ exists: () => false
51
+ },
52
+ isAppInitiatedAction: true
53
+ }
54
+ }
63
55
  };