keycloakify 11.3.9-rc.0 → 11.3.9

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 (32) hide show
  1. package/login/lib/getUserProfileApi/getUserProfileApi.d.ts +0 -1
  2. package/login/lib/getUserProfileApi/getUserProfileApi.js +1 -1
  3. package/login/lib/getUserProfileApi/getUserProfileApi.js.map +1 -1
  4. package/package.json +1 -1
  5. package/src/login/lib/getUserProfileApi/getUserProfileApi.ts +1 -3
  6. package/stories/account/pages/Account.stories.tsx +102 -0
  7. package/stories/account/pages/Applications.stories.tsx +148 -0
  8. package/stories/account/pages/FederatedIdentity.stories.tsx +58 -0
  9. package/stories/account/pages/Log.stories.tsx +104 -0
  10. package/stories/account/pages/Password.stories.tsx +76 -0
  11. package/stories/account/pages/Sessions.stories.tsx +94 -0
  12. package/stories/account/pages/Totp.stories.tsx +61 -0
  13. package/stories/login/pages/LoginIdpLinkConfirm.stories.tsx +38 -1
  14. package/stories/login/pages/LoginIdpLinkEmail.stories.tsx +54 -11
  15. package/stories/login/pages/LoginOauth2DeviceVerifyUserCode.stories.tsx +44 -0
  16. package/stories/login/pages/LoginOauthGrant.stories.tsx +69 -1
  17. package/stories/login/pages/LoginOtp.stories.tsx +110 -0
  18. package/stories/login/pages/LoginPageExpired.stories.tsx +23 -0
  19. package/stories/login/pages/LoginPassword.stories.tsx +51 -0
  20. package/stories/login/pages/LoginRecoveryAuthnCodeConfig.stories.tsx +22 -0
  21. package/stories/login/pages/LoginResetOtp.stories.tsx +79 -0
  22. package/stories/login/pages/LoginResetPassword.stories.tsx +30 -0
  23. package/stories/login/pages/LoginUpdatePassword.stories.tsx +46 -0
  24. package/stories/login/pages/LoginUpdateProfile.stories.tsx +23 -0
  25. package/stories/login/pages/LoginVerifyEmail.stories.tsx +75 -0
  26. package/stories/login/pages/LoginX509Info.stories.tsx +25 -0
  27. package/stories/login/pages/LogoutConfirm.stories.tsx +29 -0
  28. package/stories/login/pages/SelectAuthenticator.stories.tsx +21 -0
  29. package/stories/login/pages/UpdateEmail.stories.tsx +22 -0
  30. package/stories/login/pages/WebauthnAuthenticate.stories.tsx +141 -0
  31. package/stories/login/pages/WebauthnError.stories.tsx +69 -0
  32. package/stories/login/pages/WebauthnRegister.stories.tsx +44 -0
@@ -26,3 +26,79 @@ export const WithMessage: Story = {
26
26
  />
27
27
  )
28
28
  };
29
+ /**
30
+ * FirstTimePasswordSetup:
31
+ * - Purpose: Tests the page when no password is set (e.g., first login).
32
+ * - Scenario: This renders the form without the current password field.
33
+ * - Key Aspect: Ensures the page only displays fields for setting a new password.
34
+ */
35
+ export const FirstTimePasswordSetup: Story = {
36
+ render: () => (
37
+ <KcPageStory
38
+ kcContext={{
39
+ account: {
40
+ username: "john_doe"
41
+ },
42
+ password: {
43
+ passwordSet: false
44
+ },
45
+ url: {
46
+ passwordUrl: "/password"
47
+ },
48
+ stateChecker: "state-checker"
49
+ }}
50
+ />
51
+ )
52
+ };
53
+
54
+ /**
55
+ * IncorrectCurrentPassword:
56
+ * - Purpose: Simulates validation error when the current password is incorrect.
57
+ * - Scenario: This renders the page with an error message indicating the current password is incorrect.
58
+ * - Key Aspect: Validates that an error message is correctly displayed for the current password input.
59
+ */
60
+ export const IncorrectCurrentPassword: Story = {
61
+ render: () => (
62
+ <KcPageStory
63
+ kcContext={{
64
+ message: { type: "error", summary: "Incorrect current password." },
65
+ account: {
66
+ username: "john_doe"
67
+ },
68
+ password: {
69
+ passwordSet: true
70
+ },
71
+ url: {
72
+ passwordUrl: "/password"
73
+ },
74
+ stateChecker: "state-checker"
75
+ }}
76
+ />
77
+ )
78
+ };
79
+
80
+ /**
81
+ * SubmissionSuccessWithRedirect:
82
+ * - Purpose: Simulates a successful form submission with a redirect or success message.
83
+ * - Scenario: After successfully changing the password, a success message and redirect behavior are triggered.
84
+ * - Key Aspect: Verifies the handling of successful submissions.
85
+ */
86
+ export const SubmissionSuccessWithRedirect: Story = {
87
+ render: () => (
88
+ <KcPageStory
89
+ kcContext={{
90
+ message: { type: "success", summary: "Password successfully changed." },
91
+ account: {
92
+ username: "john_doe"
93
+ },
94
+ password: {
95
+ passwordSet: true
96
+ },
97
+ url: {
98
+ passwordUrl: "/password"
99
+ },
100
+ stateChecker: "state-checker"
101
+ }}
102
+ />
103
+ )
104
+ };
@@ -57,3 +57,97 @@ export const WithError: Story = {
57
57
  />
58
58
  )
59
59
  };
60
+ /**
61
+ * No active sessions scenario:
62
+ * - Simulates the scenario where no sessions are active for the user.
63
+ */
64
+ export const NoActiveSessions: Story = {
65
+ render: () => (
66
+ <KcPageStory
67
+ kcContext={{
68
+ sessions: {
69
+ sessions: []
70
+ },
71
+ stateChecker: "randomStateCheckerValue"
72
+ }}
73
+ />
74
+ )
75
+ };
76
+
77
+ /**
78
+ * Single session scenario:
79
+ * - Displays only one active session with session details.
80
+ */
81
+ export const SingleSession: Story = {
82
+ render: () => (
83
+ <KcPageStory
84
+ kcContext={{
85
+ sessions: {
86
+ sessions: [
87
+ {
88
+ expires: "2024-04-26T18:14:19Z",
89
+ clients: ["account"],
90
+ ipAddress: "172.20.0.1",
91
+ started: "2024-04-26T08:14:19Z",
92
+ lastAccess: "2024-04-26T08:30:54Z",
93
+ id: "single-session-id"
94
+ }
95
+ ]
96
+ },
97
+ stateChecker: "anotherStateChecker"
98
+ }}
99
+ />
100
+ )
101
+ };
102
+
103
+ /**
104
+ * Multiple clients per session scenario:
105
+ * - Displays sessions where each session has multiple associated clients.
106
+ */
107
+ export const MultipleClientsSession: Story = {
108
+ render: () => (
109
+ <KcPageStory
110
+ kcContext={{
111
+ sessions: {
112
+ sessions: [
113
+ {
114
+ expires: "2024-04-26T18:14:19Z",
115
+ clients: ["account", "admin-console", "another-client"],
116
+ ipAddress: "172.20.0.1",
117
+ started: "2024-04-26T08:14:19Z",
118
+ lastAccess: "2024-04-26T08:30:54Z",
119
+ id: "multiple-clients-session"
120
+ }
121
+ ]
122
+ },
123
+ stateChecker: "multiClientsStateChecker"
124
+ }}
125
+ />
126
+ )
127
+ };
128
+
129
+ /**
130
+ * Session without client details scenario:
131
+ * - Simulates a session where no client information is provided.
132
+ */
133
+ export const SessionWithoutClients: Story = {
134
+ render: () => (
135
+ <KcPageStory
136
+ kcContext={{
137
+ sessions: {
138
+ sessions: [
139
+ {
140
+ expires: "2024-04-26T18:14:19Z",
141
+ clients: [], // No clients information
142
+ ipAddress: "172.20.0.1",
143
+ started: "2024-04-26T08:14:19Z",
144
+ lastAccess: "2024-04-26T08:30:54Z",
145
+ id: "no-clients-session"
146
+ }
147
+ ]
148
+ },
149
+ stateChecker: "noClientsStateChecker"
150
+ }}
151
+ />
152
+ )
153
+ };
@@ -180,3 +180,64 @@ export const MoreThanOneTotpProviders: Story = {
180
180
  />
181
181
  )
182
182
  };
183
+
184
+ // TOTP Enabled but No Existing OTP Credentials
185
+ export const TotpEnabledNoOtpCredentials: Story = {
186
+ render: () => (
187
+ <KcPageStory
188
+ kcContext={{
189
+ totp: {
190
+ enabled: true,
191
+ totpSecretEncoded: "HE4W MSTC OBKU CY2M",
192
+ otpCredentials: [] // No OTP Credentials
193
+ },
194
+ stateChecker: "stateChecker123",
195
+ url: {
196
+ totpUrl: "http://localhost:8080/realms/myrealm/account/totp"
197
+ }
198
+ }}
199
+ />
200
+ )
201
+ };
202
+
203
+ // Manual Mode TOTP without Scanning
204
+ export const ManualModeTotp: Story = {
205
+ render: () => (
206
+ <KcPageStory
207
+ kcContext={{
208
+ mode: "manual", // Manual mode
209
+ totp: {
210
+ enabled: false,
211
+ totpSecretEncoded: "HE4W MSTC OBKU CY2M",
212
+ otpCredentials: []
213
+ },
214
+ stateChecker: "stateChecker123",
215
+ url: {
216
+ totpUrl: "http://localhost:8080/realms/myrealm/account/totp"
217
+ }
218
+ }}
219
+ />
220
+ )
221
+ };
222
+
223
+ // Multiple OTP Devices Scenario
224
+ export const MultipleOtpDevices: Story = {
225
+ render: () => (
226
+ <KcPageStory
227
+ kcContext={{
228
+ totp: {
229
+ enabled: true,
230
+ totpSecretEncoded: "G55E MZKC JFUD",
231
+ otpCredentials: [
232
+ { id: "1", userLabel: "Phone 1" },
233
+ { id: "2", userLabel: "Tablet" }
234
+ ]
235
+ },
236
+ stateChecker: "stateChecker123",
237
+ url: {
238
+ totpUrl: "http://localhost:8080/realms/myrealm/account/totp"
239
+ }
240
+ }}
241
+ />
242
+ )
243
+ };
@@ -2,6 +2,14 @@ import React from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
3
  import { createKcPageStory } from "../KcPageStory";
4
4
 
5
+ // Mock kcContext to avoid the TS2304 error
6
+ const mockKcContext = {
7
+ url: {
8
+ loginAction: "/login-action"
9
+ },
10
+ idpAlias: "mockIdpAlias"
11
+ };
12
+
5
13
  const { KcPageStory } = createKcPageStory({ pageId: "login-idp-link-confirm.ftl" });
6
14
 
7
15
  const meta = {
@@ -13,6 +21,35 @@ export default meta;
13
21
 
14
22
  type Story = StoryObj<typeof meta>;
15
23
 
24
+ /**
25
+ * Default:
26
+ * - Purpose: Tests standard behavior with mock data.
27
+ * - Scenario: The component renders with a mocked identity provider alias (`mockIdpAlias`) and a login action URL (`/login-action`).
28
+ * - Key Aspect: Ensures the default behavior of the component with standard values for kcContext.
29
+ */
16
30
  export const Default: Story = {
17
- render: () => <KcPageStory />
31
+ render: () => <KcPageStory kcContext={mockKcContext} />
32
+ };
33
+
34
+ /**
35
+ * WithFormSubmissionError:
36
+ * - Purpose: Tests how the component handles form submission errors.
37
+ * - Scenario: Simulates a form submission error by setting the login action URL to `/error` and displays an error message.
38
+ * - Key Aspect: Verifies that the component can display error messages during form submission failure, ensuring proper error handling.
39
+ */
40
+ export const WithFormSubmissionError: Story = {
41
+ render: () => (
42
+ <KcPageStory
43
+ kcContext={{
44
+ ...mockKcContext,
45
+ url: {
46
+ loginAction: "/error"
47
+ },
48
+ message: {
49
+ type: "error",
50
+ summary: "An error occurred during form submission."
51
+ }
52
+ }}
53
+ />
54
+ )
18
55
  };
@@ -2,6 +2,20 @@ import React from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
3
  import { createKcPageStory } from "../KcPageStory";
4
4
 
5
+ // Mock kcContext to avoid TS2304 error and to simulate the real environment
6
+ const mockKcContext = {
7
+ url: {
8
+ loginAction: "/login-action"
9
+ },
10
+ idpAlias: "mockIdpAlias",
11
+ brokerContext: {
12
+ username: "mockUser"
13
+ },
14
+ realm: {
15
+ displayName: "MockRealm"
16
+ }
17
+ };
18
+
5
19
  const { KcPageStory } = createKcPageStory({ pageId: "login-idp-link-email.ftl" });
6
20
 
7
21
  const meta = {
@@ -13,13 +27,27 @@ export default meta;
13
27
 
14
28
  type Story = StoryObj<typeof meta>;
15
29
 
30
+ /**
31
+ * Default:
32
+ * - Purpose: Tests the default behavior with mock data.
33
+ * - Scenario: The component renders with a mocked identity provider alias (`mockIdpAlias`), a default broker username (`mockUser`), and a default realm name (`MockRealm`).
34
+ * - Key Aspect: Ensures the default behavior of the component with typical kcContext values.
35
+ */
16
36
  export const Default: Story = {
17
- render: () => <KcPageStory />
37
+ render: () => <KcPageStory kcContext={mockKcContext} />
18
38
  };
39
+
40
+ /**
41
+ * WithIdpAlias:
42
+ * - Purpose: Tests behavior when the idpAlias is set to "Google".
43
+ * - Scenario: Simulates the component being used with a Google identity provider, showing the username "john.doe" and realm "MyRealm".
44
+ * - Key Aspect: Ensures the correct identity provider alias ("Google") and broker context (user info) are displayed in the email linking instructions.
45
+ */
19
46
  export const WithIdpAlias: Story = {
20
47
  render: () => (
21
48
  <KcPageStory
22
49
  kcContext={{
50
+ ...mockKcContext,
23
51
  idpAlias: "Google",
24
52
  brokerContext: {
25
53
  username: "john.doe"
@@ -31,32 +59,47 @@ export const WithIdpAlias: Story = {
31
59
  />
32
60
  )
33
61
  };
34
- export const WithoutIdpAlias: Story = {
62
+
63
+ /**
64
+ * WithCustomRealmDisplayName:
65
+ * - Purpose: Tests behavior when the realm display name is customized.
66
+ * - Scenario: Simulates the component with a Facebook identity provider, a broker username "jane.doe", and a custom realm name "CustomRealm".
67
+ * - Key Aspect: Ensures that custom realm display names are rendered correctly alongside the idpAlias and broker context.
68
+ */
69
+ export const WithCustomRealmDisplayName: Story = {
35
70
  render: () => (
36
71
  <KcPageStory
37
72
  kcContext={{
38
- idpAlias: undefined,
73
+ ...mockKcContext,
74
+ idpAlias: "Facebook",
39
75
  brokerContext: {
40
- username: "john.doe"
76
+ username: "jane.doe"
41
77
  },
42
78
  realm: {
43
- displayName: "MyRealm"
79
+ displayName: "CUSTOM REALM DISPLAY NAME"
44
80
  }
45
81
  }}
46
82
  />
47
83
  )
48
84
  };
49
85
 
50
- export const WithCustomRealmDisplayName: Story = {
86
+ /**
87
+ * WithFormSubmissionError:
88
+ * - Purpose: Tests how the component handles form submission errors.
89
+ * - Scenario: Simulates a form submission error by setting the login action URL to `/error` and displays an error message.
90
+ * - Key Aspect: Verifies that the component can display error messages during form submission failure, ensuring proper error handling.
91
+ */
92
+ export const WithFormSubmissionError: Story = {
51
93
  render: () => (
52
94
  <KcPageStory
53
95
  kcContext={{
54
- idpAlias: "Facebook",
55
- brokerContext: {
56
- username: "jane.doe"
96
+ ...mockKcContext,
97
+ url: {
98
+ loginAction: "/error"
57
99
  },
58
- realm: {
59
- displayName: "CustomRealm"
100
+ message: {
101
+ type: "error",
102
+ summary: "An error occurred during form submission."
60
103
  }
61
104
  }}
62
105
  />
@@ -16,3 +16,47 @@ type Story = StoryObj<typeof meta>;
16
16
  export const Default: Story = {
17
17
  render: () => <KcPageStory />
18
18
  };
19
+
20
+ /**
21
+ * WithErrorMessage:
22
+ * - Purpose: Tests when there is an error with the OAuth2 device user code entry.
23
+ * - Scenario: The component renders with an error message displayed to the user.
24
+ * - Key Aspect: Ensures the error message is properly shown when the user enters an invalid code.
25
+ */
26
+ export const WithErrorMessage: Story = {
27
+ render: () => (
28
+ <KcPageStory
29
+ kcContext={{
30
+ url: {
31
+ oauth2DeviceVerificationAction: "/mock-oauth2-device-verification"
32
+ },
33
+ message: {
34
+ summary: "The user code you entered is invalid. Please try again.",
35
+ type: "error"
36
+ }
37
+ }}
38
+ />
39
+ )
40
+ };
41
+
42
+ /**
43
+ * WithEmptyInputField:
44
+ * - Purpose: Tests when the user code field is left empty.
45
+ * - Scenario: The component renders the form, and the user tries to submit without entering any code.
46
+ * - Key Aspect: Ensures the form displays validation errors when the field is left empty.
47
+ */
48
+ export const WithEmptyInputField: Story = {
49
+ render: () => (
50
+ <KcPageStory
51
+ kcContext={{
52
+ url: {
53
+ oauth2DeviceVerificationAction: "/mock-oauth2-device-verification"
54
+ },
55
+ message: {
56
+ summary: "User code cannot be empty. Please enter a valid code.",
57
+ type: "error"
58
+ }
59
+ }}
60
+ />
61
+ )
62
+ };
@@ -2,6 +2,25 @@ import React from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
3
  import { createKcPageStory } from "../KcPageStory";
4
4
 
5
+ // Mock kcContext to simulate real environment
6
+ const mockKcContext = {
7
+ url: {
8
+ oauthAction: "/oauth-action"
9
+ },
10
+ oauth: {
11
+ clientScopesRequested: [{ consentScreenText: "Scope1", dynamicScopeParameter: "dynamicScope1" }, { consentScreenText: "Scope2" }],
12
+ code: "mockCode"
13
+ },
14
+ client: {
15
+ attributes: {
16
+ policyUri: "https://twitter.com/en/tos",
17
+ tosUri: "https://twitter.com/en/privacy"
18
+ },
19
+ name: "Twitter",
20
+ clientId: "twitter-client-id"
21
+ }
22
+ };
23
+
5
24
  const { KcPageStory } = createKcPageStory({ pageId: "login-oauth-grant.ftl" });
6
25
 
7
26
  const meta = {
@@ -13,6 +32,55 @@ export default meta;
13
32
 
14
33
  type Story = StoryObj<typeof meta>;
15
34
 
35
+ /**
36
+ * Default:
37
+ * - Purpose: Tests the default behavior with meaningful logo (Twitter).
38
+ * - Scenario: The component renders with Twitter as the client, displaying its logo, policy, and terms of service links.
39
+ * - Key Aspect: Ensures the component works with a realistic `logoUri` and client name.
40
+ */
16
41
  export const Default: Story = {
17
- render: () => <KcPageStory />
42
+ render: () => <KcPageStory kcContext={mockKcContext} />
43
+ };
44
+
45
+ /**
46
+ * WithoutScopes:
47
+ * - Purpose: Tests the component when no OAuth scopes are requested.
48
+ * - Scenario: The component renders with no scopes listed under the consent screen.
49
+ * - Key Aspect: Ensures the component renders correctly when there are no requested scopes.
50
+ */
51
+ export const WithoutScopes: Story = {
52
+ render: () => (
53
+ <KcPageStory
54
+ kcContext={{
55
+ ...mockKcContext,
56
+ oauth: {
57
+ ...mockKcContext.oauth,
58
+ clientScopesRequested: []
59
+ }
60
+ }}
61
+ />
62
+ )
63
+ };
64
+
65
+ /**
66
+ * WithFormSubmissionError:
67
+ * - Purpose: Tests how the component handles form submission errors.
68
+ * - Scenario: The `oauthAction` URL is set to an error route and an error message is displayed.
69
+ * - Key Aspect: Ensures that the component can display error messages when form submission fails.
70
+ */
71
+ export const WithFormSubmissionError: Story = {
72
+ render: () => (
73
+ <KcPageStory
74
+ kcContext={{
75
+ ...mockKcContext,
76
+ url: {
77
+ oauthAction: "/error"
78
+ },
79
+ message: {
80
+ type: "error",
81
+ summary: "An error occurred during form submission."
82
+ }
83
+ }}
84
+ />
85
+ )
18
86
  };
@@ -16,3 +16,113 @@ type Story = StoryObj<typeof meta>;
16
16
  export const Default: Story = {
17
17
  render: () => <KcPageStory />
18
18
  };
19
+
20
+ /**
21
+ * MultipleOtpCredentials:
22
+ * - Purpose: Tests the behavior when the user has multiple OTP credentials to choose from.
23
+ * - Scenario: Simulates the scenario where the user is presented with multiple OTP credentials and must select one to proceed.
24
+ * - Key Aspect: Ensures that multiple OTP credentials are listed and selectable, and the correct credential is selected by default.
25
+ */
26
+ export const MultipleOtpCredentials: Story = {
27
+ render: () => (
28
+ <KcPageStory
29
+ kcContext={{
30
+ otpLogin: {
31
+ userOtpCredentials: [
32
+ { id: "credential1", userLabel: "Device 1" },
33
+ { id: "credential2", userLabel: "Device 2" },
34
+ { id: "credential2", userLabel: "Device 3" },
35
+ { id: "credential2", userLabel: "Device 4" },
36
+ { id: "credential2", userLabel: "Device 5" },
37
+ { id: "credential2", userLabel: "Device 6" }
38
+ ],
39
+ selectedCredentialId: "credential1"
40
+ },
41
+ url: {
42
+ loginAction: "/login-action"
43
+ },
44
+ messagesPerField: {
45
+ existsError: () => false
46
+ }
47
+ }}
48
+ />
49
+ )
50
+ };
51
+
52
+ /**
53
+ * WithOtpError:
54
+ * - Purpose: Tests the behavior when an error occurs with the OTP field (e.g., invalid OTP code).
55
+ * - Scenario: Simulates an invalid OTP code scenario where an error message is displayed.
56
+ * - Key Aspect: Ensures that the OTP input displays error messages correctly and the error is visible.
57
+ */
58
+ export const WithOtpError: Story = {
59
+ render: () => (
60
+ <KcPageStory
61
+ kcContext={{
62
+ otpLogin: {
63
+ userOtpCredentials: []
64
+ },
65
+ url: {
66
+ loginAction: "/login-action"
67
+ },
68
+ messagesPerField: {
69
+ existsError: (field: string) => field === "totp",
70
+ get: () => "Invalid OTP code"
71
+ }
72
+ }}
73
+ />
74
+ )
75
+ };
76
+
77
+ /**
78
+ * NoOtpCredentials:
79
+ * - Purpose: Tests the behavior when no OTP credentials are provided for the user.
80
+ * - Scenario: Simulates the scenario where the user is not presented with any OTP credentials, and only the OTP input is displayed.
81
+ * - 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.
82
+ */
83
+ export const NoOtpCredentials: Story = {
84
+ render: () => (
85
+ <KcPageStory
86
+ kcContext={{
87
+ otpLogin: {
88
+ userOtpCredentials: []
89
+ },
90
+ url: {
91
+ loginAction: "/login-action"
92
+ },
93
+ messagesPerField: {
94
+ existsError: () => false
95
+ }
96
+ }}
97
+ />
98
+ )
99
+ };
100
+
101
+ /**
102
+ * WithErrorAndMultipleOtpCredentials:
103
+ * - Purpose: Tests behavior when there is both an error in the OTP field and multiple OTP credentials.
104
+ * - Scenario: Simulates the case where the user has multiple OTP credentials and encounters an error with the OTP input.
105
+ * - Key Aspect: Ensures that the component can handle both multiple OTP credentials and display an error message simultaneously.
106
+ */
107
+ export const WithErrorAndMultipleOtpCredentials: Story = {
108
+ render: () => (
109
+ <KcPageStory
110
+ kcContext={{
111
+ otpLogin: {
112
+ userOtpCredentials: [
113
+ { id: "credential1", userLabel: "Device 1" },
114
+ { id: "credential2", userLabel: "Device 2" }
115
+ ],
116
+ selectedCredentialId: "credential1"
117
+ },
118
+ url: {
119
+ loginAction: "/login-action"
120
+ },
121
+ messagesPerField: {
122
+ existsError: (field: string) => field === "totp",
123
+ get: () => "Invalid OTP code"
124
+ }
125
+ }}
126
+ />
127
+ )
128
+ };
@@ -16,3 +16,26 @@ type Story = StoryObj<typeof meta>;
16
16
  export const Default: Story = {
17
17
  render: () => <KcPageStory />
18
18
  };
19
+
20
+ /**
21
+ * WithErrorMessage:
22
+ * - Purpose: Tests behavior when an error message is displayed along with the page expiration message.
23
+ * - Scenario: Simulates a case where the session expired due to an error, and an error message is displayed alongside the expiration message.
24
+ * - Key Aspect: Ensures that error messages are displayed correctly in addition to the page expiration notice.
25
+ */
26
+ export const WithErrorMessage: Story = {
27
+ render: () => (
28
+ <KcPageStory
29
+ kcContext={{
30
+ url: {
31
+ loginRestartFlowUrl: "/mock-restart-flow",
32
+ loginAction: "/mock-continue-login"
33
+ },
34
+ message: {
35
+ type: "error",
36
+ summary: "An error occurred while processing your session."
37
+ }
38
+ }}
39
+ />
40
+ )
41
+ };