Skip to content

Commit 6f97f60

Browse files
committed
feat(email-otp): sync theme with latest change in for-keycloak/email-otp-authenticator#34
1 parent 7ae9826 commit 6f97f60

File tree

5 files changed

+114
-4
lines changed

5 files changed

+114
-4
lines changed

compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ services:
2121
- app-network
2222

2323
playwright:
24-
image: mcr.microsoft.com/playwright:v1.57.0-noble
24+
image: mcr.microsoft.com/playwright:v1.58.0-noble
2525
volumes:
2626
- .:/app
2727
working_dir: /app

src/login/KcContext.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import type { KcContext as KcContextBase } from "keycloakify/login/KcContext";
55
type KcContextLoginEmailOtp = KcContextBase.Common & {
66
pageId: "login-email-otp.ftl";
77
url: KcContextBase.Common["url"];
8+
// Device trust feature (feat/trust-ips-devices branch)
9+
deviceTrustEnabled?: boolean;
10+
deviceTrustPermanent?: boolean;
11+
trustDurationUnitKey?: string;
12+
trustDurationValue?: number;
13+
trustHideNumber?: boolean;
814
};
915

1016
export type KcContext = KcContextBase | KcContextLoginEmailOtp;

src/login/i18n.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
import { i18nBuilder } from "keycloakify/login/i18n";
22

3-
const { useI18n, ofTypeI18n } = i18nBuilder.withCustomTranslations({}).build();
3+
// Message keys for the email-otp-authenticator plugin device trust feature
4+
// These are provided by the plugin at runtime, but we define fallbacks here
5+
// for Storybook and type safety
6+
const { useI18n, ofTypeI18n } = i18nBuilder
7+
.withCustomTranslations({
8+
en: {
9+
dontAskForCodePermanently: "Don't ask for a code on this device again",
10+
dontAskForCodeFor: "Don't ask for a code on this device for {0} {1}",
11+
unitDayOne: "day",
12+
unitDayMany: "days",
13+
unitWeekOne: "week",
14+
unitWeekMany: "weeks",
15+
unitMonthOne: "month",
16+
unitMonthMany: "months",
17+
unitYearOne: "year",
18+
unitYearMany: "years"
19+
}
20+
})
21+
.build();
422

523
type I18n = typeof ofTypeI18n;
624

src/login/pages/LoginEmailOtp.tsx

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,21 @@ export default function LoginEmailOtp(
1919
classes
2020
});
2121

22-
const { url } = kcContext;
22+
const {
23+
url,
24+
deviceTrustEnabled,
25+
deviceTrustPermanent,
26+
trustDurationUnitKey,
27+
trustDurationValue,
28+
trustHideNumber
29+
} = kcContext;
2330
// messagesPerField comes from Common but TypeScript needs a cast for plugin pages
2431
const messagesPerField = (kcContext as any).messagesPerField ?? {
2532
existsError: () => false,
2633
get: () => "",
2734
getFirstError: () => ""
2835
};
29-
const { msg, msgStr } = i18n;
36+
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
3037

3138
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
3239

@@ -74,6 +81,36 @@ export default function LoginEmailOtp(
7481
)}
7582
</div>
7683

84+
{deviceTrustEnabled && (
85+
<div className={kcClsx("kcFormGroupClass")}>
86+
<div className="checkbox">
87+
<label>
88+
<input
89+
type="checkbox"
90+
id="trust-device"
91+
name="trust-device"
92+
value="true"
93+
/>{" "}
94+
{deviceTrustPermanent
95+
? advancedMsg("dontAskForCodePermanently")
96+
: trustDurationUnitKey
97+
? trustHideNumber
98+
? advancedMsgStr(
99+
"dontAskForCodeFor",
100+
"",
101+
advancedMsgStr(trustDurationUnitKey)
102+
).trim()
103+
: advancedMsg(
104+
"dontAskForCodeFor",
105+
String(trustDurationValue ?? 1),
106+
advancedMsgStr(trustDurationUnitKey)
107+
)
108+
: advancedMsg("dontAskForCodePermanently")}
109+
</label>
110+
</div>
111+
</div>
112+
)}
113+
77114
<div id="kc-form-buttons" className={kcClsx("kcFormGroupClass")}>
78115
<input
79116
disabled={isLoginButtonDisabled}

src/login/stories/login-email-otp.ftl.stories.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,52 @@ export const WithError: Story = {
3939
})
4040
}
4141
};
42+
43+
export const WithDeviceTrustPermanent: Story = {
44+
name: "Device Trust - Permanent",
45+
args: {
46+
kcContext: createEmailOtpMock({
47+
deviceTrustEnabled: true,
48+
deviceTrustPermanent: true
49+
})
50+
}
51+
};
52+
53+
export const WithDeviceTrustDays: Story = {
54+
name: "Device Trust - 30 Days",
55+
args: {
56+
kcContext: createEmailOtpMock({
57+
deviceTrustEnabled: true,
58+
deviceTrustPermanent: false,
59+
trustDurationUnitKey: "unitDayMany",
60+
trustDurationValue: 30,
61+
trustHideNumber: false
62+
})
63+
}
64+
};
65+
66+
export const WithDeviceTrustWeek: Story = {
67+
name: "Device Trust - 1 Week",
68+
args: {
69+
kcContext: createEmailOtpMock({
70+
deviceTrustEnabled: true,
71+
deviceTrustPermanent: false,
72+
trustDurationUnitKey: "unitWeekOne",
73+
trustDurationValue: 1,
74+
trustHideNumber: false
75+
})
76+
}
77+
};
78+
79+
export const WithDeviceTrustHiddenNumber: Story = {
80+
name: "Device Trust - Hidden Number",
81+
args: {
82+
kcContext: createEmailOtpMock({
83+
deviceTrustEnabled: true,
84+
deviceTrustPermanent: false,
85+
trustDurationUnitKey: "unitMonthMany",
86+
trustDurationValue: 3,
87+
trustHideNumber: true
88+
})
89+
}
90+
};

0 commit comments

Comments
 (0)