@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
@@ -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,29 +41,27 @@ export const French: Story = {
47
41
  * - Key Aspect: Ensures that multiple OTP credentials are listed and selectable, and the correct credential is selected by default.
48
42
  */
49
43
  export const MultipleOtpCredentials: Story = {
50
- render: () => (
51
- <KcPageStory
52
- kcContext={{
53
- otpLogin: {
54
- userOtpCredentials: [
55
- { id: "credential1", userLabel: "Device 1" },
56
- { id: "credential2", userLabel: "Device 2" },
57
- { id: "credential3", userLabel: "Device 3" },
58
- { id: "credential4", userLabel: "Device 4" },
59
- { id: "credential5", userLabel: "Device 5" },
60
- { id: "credential6", userLabel: "Device 6" }
61
- ],
62
- selectedCredentialId: "credential1"
63
- },
64
- url: {
65
- loginAction: "/login-action"
66
- },
67
- messagesPerField: {
68
- existsError: () => false
69
- }
70
- }}
71
- />
72
- )
44
+ args: {
45
+ kcContext: {
46
+ otpLogin: {
47
+ userOtpCredentials: [
48
+ { id: "credential1", userLabel: "Device 1" },
49
+ { id: "credential2", userLabel: "Device 2" },
50
+ { id: "credential3", userLabel: "Device 3" },
51
+ { id: "credential4", userLabel: "Device 4" },
52
+ { id: "credential5", userLabel: "Device 5" },
53
+ { id: "credential6", userLabel: "Device 6" }
54
+ ],
55
+ selectedCredentialId: "credential1"
56
+ },
57
+ url: {
58
+ loginAction: "/login-action"
59
+ },
60
+ messagesPerField: {
61
+ existsError: () => false
62
+ }
63
+ }
64
+ }
73
65
  };
74
66
 
75
67
  /**
@@ -79,22 +71,20 @@ export const MultipleOtpCredentials: Story = {
79
71
  * - Key Aspect: Ensures that the OTP input displays error messages correctly and the error is visible.
80
72
  */
81
73
  export const WithOtpError: Story = {
82
- render: () => (
83
- <KcPageStory
84
- kcContext={{
85
- otpLogin: {
86
- userOtpCredentials: []
87
- },
88
- url: {
89
- loginAction: "/login-action"
90
- },
91
- messagesPerField: {
92
- existsError: (field: string) => field === "totp",
93
- get: () => "Invalid OTP code"
94
- }
95
- }}
96
- />
97
- )
74
+ args: {
75
+ kcContext: {
76
+ otpLogin: {
77
+ userOtpCredentials: []
78
+ },
79
+ url: {
80
+ loginAction: "/login-action"
81
+ },
82
+ messagesPerField: {
83
+ existsError: (field: string) => field === "totp",
84
+ get: () => "Invalid OTP code"
85
+ }
86
+ }
87
+ }
98
88
  };
99
89
 
100
90
  /**
@@ -104,21 +94,19 @@ export const WithOtpError: Story = {
104
94
  * - Key Aspect: Ensures that the component handles cases where there are no user OTP credentials, and the user is only prompted for the OTP code.
105
95
  */
106
96
  export const NoOtpCredentials: Story = {
107
- render: () => (
108
- <KcPageStory
109
- kcContext={{
110
- otpLogin: {
111
- userOtpCredentials: []
112
- },
113
- url: {
114
- loginAction: "/login-action"
115
- },
116
- messagesPerField: {
117
- existsError: () => false
118
- }
119
- }}
120
- />
121
- )
97
+ args: {
98
+ kcContext: {
99
+ otpLogin: {
100
+ userOtpCredentials: []
101
+ },
102
+ url: {
103
+ loginAction: "/login-action"
104
+ },
105
+ messagesPerField: {
106
+ existsError: () => false
107
+ }
108
+ }
109
+ }
122
110
  };
123
111
 
124
112
  /**
@@ -128,24 +116,22 @@ export const NoOtpCredentials: Story = {
128
116
  * - Key Aspect: Ensures that the component can handle both multiple OTP credentials and display an error message simultaneously.
129
117
  */
130
118
  export const WithErrorAndMultipleOtpCredentials: Story = {
131
- render: () => (
132
- <KcPageStory
133
- kcContext={{
134
- otpLogin: {
135
- userOtpCredentials: [
136
- { id: "credential1", userLabel: "Device 1" },
137
- { id: "credential2", userLabel: "Device 2" }
138
- ],
139
- selectedCredentialId: "credential1"
140
- },
141
- url: {
142
- loginAction: "/login-action"
143
- },
144
- messagesPerField: {
145
- existsError: (field: string) => field === "totp",
146
- get: () => "Invalid OTP code"
147
- }
148
- }}
149
- />
150
- )
119
+ args: {
120
+ kcContext: {
121
+ otpLogin: {
122
+ userOtpCredentials: [
123
+ { id: "credential1", userLabel: "Device 1" },
124
+ { id: "credential2", userLabel: "Device 2" }
125
+ ],
126
+ selectedCredentialId: "credential1"
127
+ },
128
+ url: {
129
+ loginAction: "/login-action"
130
+ },
131
+ messagesPerField: {
132
+ existsError: (field: string) => field === "totp",
133
+ get: () => "Invalid OTP code"
134
+ }
135
+ }
136
+ }
151
137
  };
@@ -1,108 +1,108 @@
1
- import { Button } from "@/components/ui/button";
2
- import { FieldError } from '@/components/ui/field';
3
- import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
4
- import { Label } from "@/components/ui/label";
5
- import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
6
- import { useI18n } from '@/login/i18n';
7
- import { useKcContext } from '@/login/KcContext';
8
- import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
9
- import { kcSanitize } from "keycloakify/lib/kcSanitize";
10
- import { useState } from 'react';
11
- import { MdOutlineDevices } from "react-icons/md";
12
- import { assert } from "tsafe/assert";
13
- import { Template } from "../../components/Template";
14
-
15
- export function Page() {
16
-
17
- const { kcContext } = useKcContext();
18
-
19
- assert(kcContext.pageId === "login-otp.ftl");
20
-
21
- const { msg, msgStr } = useI18n();
22
-
23
- const [isSubmitting, setIsSubmitting] = useState(false);
24
- return (
25
- <Template
26
- displayMessage={!kcContext.messagesPerField.existsError("totp")}
27
- headerNode={msg("doLogIn")}
28
- >
29
- <form
30
- id="kc-otp-login-form"
31
- className="space-y-6"
32
- action={kcContext.url.loginAction}
33
- onSubmit={() => {
34
- setIsSubmitting(true);
35
- return true;
36
- }}
37
- method="post"
38
- >
39
- {kcContext.otpLogin.userOtpCredentials.length > 1 && (
40
- <div className="space-y-3">
41
- <RadioGroup
42
- name="selectedCredentialId"
43
- defaultValue={kcContext.otpLogin.selectedCredentialId}
44
- className="space-y-2"
45
- >
46
- {kcContext.otpLogin.userOtpCredentials.map((otpCredential, index) => (
47
- <div
48
- key={otpCredential.id}
49
- className="flex items-center space-x-3 p-3 border rounded-lg"
50
- >
51
- <RadioGroupItem
52
- value={otpCredential.id}
53
- id={`kc-otp-credential-${index}`}
54
- />
55
- <Label
56
- htmlFor={`kc-otp-credential-${index}`}
57
- className="flex items-center space-x-2 cursor-pointer flex-1"
58
- >
59
- <MdOutlineDevices />
60
- <span className="text-sm font-medium">
61
- {otpCredential.userLabel}
62
- </span>
63
- </Label>
64
- </div>
65
- ))}
66
- </RadioGroup>
67
- </div>
68
- )}
69
-
70
- <div className="space-y-2">
71
- <Label htmlFor="otp" className="text-sm font-medium block">
72
- {msg("loginOtpOneTime")}
73
- </Label>
74
- <div className="flex w-72 ">
75
- <InputOTP
76
- maxLength={6}
77
- pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
78
- name="otp"
79
- autoFocus
80
- >
81
- <InputOTPGroup>
82
- <InputOTPSlot index={0} className="h-12 w-12 text-lg" />
83
- <InputOTPSlot index={1} className="h-12 w-12 text-lg" />
84
- <InputOTPSlot index={2} className="h-12 w-12 text-lg" />
85
- <InputOTPSlot index={3} className="h-12 w-12 text-lg" />
86
- <InputOTPSlot index={4} className="h-12 w-12 text-lg" />
87
- <InputOTPSlot index={5} className="h-12 w-12 text-lg" />
88
- </InputOTPGroup>
89
- </InputOTP>
90
- </div>
91
- {kcContext.messagesPerField.existsError("totp") && (
92
- <FieldError id="input-error-otp-code">
93
- <span
94
- dangerouslySetInnerHTML={{
95
- __html: kcSanitize(kcContext.messagesPerField.get("totp"))
96
- }}
97
- />
98
- </FieldError>
99
- )}
100
- </div>
101
-
102
- <Button className="w-full" name="login" id="kc-login" type="submit" disabled={isSubmitting}>
103
- {msgStr("doLogIn")}
104
- </Button>
105
- </form>
106
- </Template>
107
- );
108
- }
1
+ import { Button } from "@/components/ui/button";
2
+ import { FieldError } from '@/components/ui/field';
3
+ import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
4
+ import { Label } from "@/components/ui/label";
5
+ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
6
+ import { useI18n } from '@/login/i18n';
7
+ import { useKcContext } from '@/login/KcContext';
8
+ import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
9
+ import { kcSanitize } from "keycloakify/lib/kcSanitize";
10
+ import { useState } from 'react';
11
+ import { MdOutlineDevices } from "react-icons/md";
12
+ import { assert } from "tsafe/assert";
13
+ import { Template } from "../../components/Template";
14
+
15
+ export function Page() {
16
+
17
+ const { kcContext } = useKcContext();
18
+
19
+ assert(kcContext.pageId === "login-otp.ftl");
20
+
21
+ const { msg, msgStr } = useI18n();
22
+
23
+ const [isSubmitting, setIsSubmitting] = useState(false);
24
+ return (
25
+ <Template
26
+ displayMessage={!kcContext.messagesPerField.existsError("totp")}
27
+ headerNode={msg("doLogIn")}
28
+ >
29
+ <form
30
+ id="kc-otp-login-form"
31
+ className="space-y-6"
32
+ action={kcContext.url.loginAction}
33
+ onSubmit={() => {
34
+ setIsSubmitting(true);
35
+ return true;
36
+ }}
37
+ method="post"
38
+ >
39
+ {kcContext.otpLogin.userOtpCredentials.length > 1 && (
40
+ <div className="space-y-3">
41
+ <RadioGroup
42
+ name="selectedCredentialId"
43
+ defaultValue={kcContext.otpLogin.selectedCredentialId}
44
+ className="space-y-2"
45
+ >
46
+ {kcContext.otpLogin.userOtpCredentials.map((otpCredential, index) => (
47
+ <div
48
+ key={otpCredential.id}
49
+ className="flex items-center space-x-3 p-3 border rounded-lg"
50
+ >
51
+ <RadioGroupItem
52
+ value={otpCredential.id}
53
+ id={`kc-otp-credential-${index}`}
54
+ />
55
+ <Label
56
+ htmlFor={`kc-otp-credential-${index}`}
57
+ className="flex items-center space-x-2 cursor-pointer flex-1"
58
+ >
59
+ <MdOutlineDevices />
60
+ <span className="text-sm font-medium">
61
+ {otpCredential.userLabel}
62
+ </span>
63
+ </Label>
64
+ </div>
65
+ ))}
66
+ </RadioGroup>
67
+ </div>
68
+ )}
69
+
70
+ <div className="space-y-2">
71
+ <Label htmlFor="otp" className="text-sm font-medium block">
72
+ {msg("loginOtpOneTime")}
73
+ </Label>
74
+ <div className="flex w-72 ">
75
+ <InputOTP
76
+ maxLength={6}
77
+ pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
78
+ name="otp"
79
+ autoFocus
80
+ >
81
+ <InputOTPGroup>
82
+ <InputOTPSlot index={0} className="h-12 w-12 text-lg" />
83
+ <InputOTPSlot index={1} className="h-12 w-12 text-lg" />
84
+ <InputOTPSlot index={2} className="h-12 w-12 text-lg" />
85
+ <InputOTPSlot index={3} className="h-12 w-12 text-lg" />
86
+ <InputOTPSlot index={4} className="h-12 w-12 text-lg" />
87
+ <InputOTPSlot index={5} className="h-12 w-12 text-lg" />
88
+ </InputOTPGroup>
89
+ </InputOTP>
90
+ </div>
91
+ {kcContext.messagesPerField.existsError("totp") && (
92
+ <FieldError id="input-error-otp-code">
93
+ <span
94
+ dangerouslySetInnerHTML={{
95
+ __html: kcSanitize(kcContext.messagesPerField.get("totp"))
96
+ }}
97
+ />
98
+ </FieldError>
99
+ )}
100
+ </div>
101
+
102
+ <Button className="w-full" name="login" id="kc-login" type="submit" disabled={isSubmitting}>
103
+ {msgStr("doLogIn")}
104
+ </Button>
105
+ </form>
106
+ </Template>
107
+ );
108
+ }
@@ -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,18 +41,16 @@ export const French: Story = {
47
41
  * - Key Aspect: Ensures that error messages are displayed correctly in addition to the page expiration notice.
48
42
  */
49
43
  export const WithErrorMessage: Story = {
50
- render: () => (
51
- <KcPageStory
52
- kcContext={{
53
- url: {
54
- loginRestartFlowUrl: "/mock-restart-flow",
55
- loginAction: "/mock-continue-login"
56
- },
57
- message: {
58
- type: "error",
59
- summary: "An error occurred while processing your session."
60
- }
61
- }}
62
- />
63
- )
44
+ args: {
45
+ kcContext: {
46
+ url: {
47
+ loginRestartFlowUrl: "/mock-restart-flow",
48
+ loginAction: "/mock-continue-login"
49
+ },
50
+ message: {
51
+ type: "error",
52
+ summary: "An error occurred while processing your session."
53
+ }
54
+ }
55
+ }
64
56
  };
@@ -1,47 +1,47 @@
1
- import { Alert, AlertDescription } from "@/components/ui/alert";
2
- import { useI18n } from '@/login/i18n';
3
- import { useKcContext } from '@/login/KcContext';
4
- import { assert } from 'tsafe/assert';
5
- import { Template } from "../../components/Template";
6
-
7
- export function Page() {
8
- const { kcContext } = useKcContext();
9
- assert(kcContext.pageId === "login-page-expired.ftl");
10
-
11
- const { msg } = useI18n();
12
-
13
- return (
14
- <Template
15
- headerNode={msg("pageExpiredTitle")}
16
- >
17
- <Alert variant="warning" className="my-6">
18
- <AlertDescription>
19
- <div className="space-y-3 text-sm leading-relaxed">
20
- <p>
21
- {msg("pageExpiredMsg1")}{" "}
22
- <a
23
- id="loginRestartLink"
24
- href={kcContext.url.loginRestartFlowUrl}
25
- className="text-primary dark:text-white hover:text-primary/80 underline underline-offset-2 font-medium"
26
- >
27
- {msg("doClickHere")}
28
- </a>
29
- .
30
- </p>
31
- <p>
32
- {msg("pageExpiredMsg2")}{" "}
33
- <a
34
- id="loginContinueLink"
35
- href={kcContext.url.loginAction}
36
- className="text-primary dark:text-white hover:text-primary/80 underline underline-offset-2 font-medium"
37
- >
38
- {msg("doClickHere")}
39
- </a>
40
- .
41
- </p>
42
- </div>
43
- </AlertDescription>
44
- </Alert>
45
- </Template>
46
- );
47
- }
1
+ import { Alert, AlertDescription } from "@/components/ui/alert";
2
+ import { useI18n } from '@/login/i18n';
3
+ import { useKcContext } from '@/login/KcContext';
4
+ import { assert } from 'tsafe/assert';
5
+ import { Template } from "../../components/Template";
6
+
7
+ export function Page() {
8
+ const { kcContext } = useKcContext();
9
+ assert(kcContext.pageId === "login-page-expired.ftl");
10
+
11
+ const { msg } = useI18n();
12
+
13
+ return (
14
+ <Template
15
+ headerNode={msg("pageExpiredTitle")}
16
+ >
17
+ <Alert variant="warning" className="my-6">
18
+ <AlertDescription>
19
+ <div className="space-y-3 text-sm leading-relaxed">
20
+ <p>
21
+ {msg("pageExpiredMsg1")}{" "}
22
+ <a
23
+ id="loginRestartLink"
24
+ href={kcContext.url.loginRestartFlowUrl}
25
+ className="text-primary dark:text-white hover:text-primary/80 underline underline-offset-2 font-medium"
26
+ >
27
+ {msg("doClickHere")}
28
+ </a>
29
+ .
30
+ </p>
31
+ <p>
32
+ {msg("pageExpiredMsg2")}{" "}
33
+ <a
34
+ id="loginContinueLink"
35
+ href={kcContext.url.loginAction}
36
+ className="text-primary dark:text-white hover:text-primary/80 underline underline-offset-2 font-medium"
37
+ >
38
+ {msg("doClickHere")}
39
+ </a>
40
+ .
41
+ </p>
42
+ </div>
43
+ </AlertDescription>
44
+ </Alert>
45
+ </Template>
46
+ );
47
+ }
@@ -1,3 +1,3 @@
1
- import { Page } from "./Page";
2
-
3
- export default Page;
1
+ import { Page } from "./Page";
2
+
3
+ export default Page;
@@ -14,3 +14,23 @@ export default meta;
14
14
  type Story = StoryObj<typeof meta>;
15
15
 
16
16
  export const Default: Story = {};
17
+
18
+ export const Arabic: Story = {
19
+ args: {
20
+ kcContext: {
21
+ locale: {
22
+ currentLanguageTag: "ar",
23
+ rtl: true
24
+ }
25
+ }
26
+ }
27
+ };
28
+ export const French: Story = {
29
+ args: {
30
+ kcContext: {
31
+ locale: {
32
+ currentLanguageTag: "fr"
33
+ }
34
+ }
35
+ }
36
+ };