Skip to content

Commit

Permalink
fix: updating styles and flow of otp auth
Browse files Browse the repository at this point in the history
  • Loading branch information
jcaleb4 committed Dec 20, 2024
1 parent 099952d commit c4cc6a3
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useAuthenticate } from "../../../../hooks/useAuthenticate.js";
import { ls } from "../../../../strings.js";
import { useAuthContext, type AuthStep } from "../../context.js";
import {
AuthStepStatus,
useAuthContext,
type AuthStep,
} from "../../context.js";
import { Button } from "../../../button.js";

type EmailNotReceivedDisclaimerProps = {
Expand All @@ -18,6 +22,14 @@ export const EmailNotReceivedDisclaimer = ({
},
});

const isOTPVerifying = useMemo(() => {
return (
authStep.type === "otp_verify" &&
(authStep.status === AuthStepStatus.verifying ||
authStep.status === AuthStepStatus.success)
);
}, [authStep]);

useEffect(() => {
if (emailResent) {
// set the text back to "Resend" after 2 seconds
Expand All @@ -29,13 +41,20 @@ export const EmailNotReceivedDisclaimer = ({

return (
<div className="flex flex-row gap-2 justify-center mb-2">
<span className="text-fg-tertiary text-xs">
<span
className={`${
isOTPVerifying ? "text-fg-disabled" : "text-fg-tertiary"
} text-xs`}
>
{ls.loadingEmail.emailNotReceived}
</span>
<Button
variant="link"
className="text-xs font-normal underline"
disabled={emailResent}
className={`text-xs font-normal underline ${
isOTPVerifying ? "text-fg-disabled" : "text-btn-primary"
}`}
style={isOTPVerifying ? { opacity: 1 } : {}}
disabled={emailResent || isOTPVerifying}
onClick={() => {
authenticate({
type: "email",
Expand Down
46 changes: 37 additions & 9 deletions account-kit/react/src/components/auth/card/loading/otp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
isOTPCodeType,
} from "../../../otp-input/otp-input.js";
import { Spinner } from "../../../../icons/spinner.js";
import { useAuthContext } from "../../context.js";
import { AuthStepStatus, useAuthContext } from "../../context.js";
import { useAuthenticate } from "../../../../hooks/useAuthenticate.js";
import { useSignerStatus } from "../../../../hooks/useSignerStatus.js";

Expand All @@ -17,31 +17,41 @@ export const LoadingOtp = () => {
const { authStep } = useAuthContext("otp_verify");
const [otpCode, setOtpCode] = useState<OTPCodeType>(initialOTPValue);
const [errorText, setErrorText] = useState(authStep.error?.message || "");
const [isDisabled, setIsDisabled] = useState(false);
const [titleText, setTitleText] = useState(ls.loadingOtp.title);
const { setAuthStep } = useAuthContext();
const resetOTP = (errorText = "") => {
setOtpCode(initialOTPValue);
setErrorText(errorText);
setIsDisabled(false);

if (errorText) {
setTitleText(ls.loadingOtp.title);
}
};
const { authenticate } = useAuthenticate({
onError: (error: any) => {
console.error(error);
const { email } = authStep;
setAuthStep({ type: "otp_verify", email, error });

setAuthStep({ ...authStep, error, status: null });
resetOTP(getUserErrorMessage(error));
},
onSuccess: () => {
if (isConnected) {
setAuthStep({ type: "complete" });
setAuthStep({ ...authStep, status: AuthStepStatus.success });
setTitleText(ls.loadingOtp.verified);
setTimeout(() => {
setAuthStep({ type: "complete" });
}, 3000);
}
},
});

const setValue = (otpCode: OTPCodeType) => {
setOtpCode(otpCode);
if (isOTPCodeType(otpCode)) {
setIsDisabled(true);
const otp = otpCode.join("");

setAuthStep({ ...authStep, status: AuthStepStatus.verifying });
setTitleText(ls.loadingOtp.verifying);
authenticate({ type: "otp", otpCode: otp });
}
};
Expand All @@ -57,7 +67,7 @@ export const LoadingOtp = () => {
/>
</div>
<h3 className="text-fg-primary font-semibold text-lg mb-2">
{ls.loadingOtp.title}
{titleText}
</h3>
<p className="text-fg-secondary text-center text-sm mb-1">
{ls.loadingOtp.body}
Expand All @@ -66,13 +76,31 @@ export const LoadingOtp = () => {
{authStep.email}
</p>
<OTPInput
disabled={isDisabled}
disabled={authStep.status === AuthStepStatus.verifying}
value={otpCode}
setValue={setValue}
setErrorText={setErrorText}
errorText={errorText}
handleReset={resetOTP}
isVerified={authStep.status === AuthStepStatus.success}
/>
</div>
);
};

function getUserErrorMessage(error: Error | undefined): string {
if (!error) {
return "";
}
// Errors from Alchemy have a JSON message.
try {
const message = JSON.parse(error.message).error;
if (message === "invalid OTP code") {
return ls.error.otp.invalid;
}
return message;
} catch (e) {
// Ignore
}
return error.message;
}
13 changes: 12 additions & 1 deletion account-kit/react/src/components/auth/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,20 @@ import type { Connector } from "@wagmi/core";
import { createContext, useContext } from "react";
import type { AuthType } from "./types";

export enum AuthStepStatus {
success = "success",
error = "error",
verifying = "verifying",
}

export type AuthStep =
| { type: "email_verify"; email: string }
| { type: "otp_verify"; email: string; error?: Error }
| {
type: "otp_verify";
email: string;
error?: Error;
status?: AuthStepStatus | null;
}
| { type: "passkey_verify"; error?: Error }
| { type: "passkey_create"; error?: Error }
| { type: "passkey_create_success" }
Expand Down
16 changes: 13 additions & 3 deletions account-kit/react/src/components/otp-input/otp-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type OTPInputProps = {
disabled?: boolean;
handleReset: () => void;
className?: string;
isVerified?: boolean;
};

export const isOTPCodeType = (arg: string[]): arg is OTPCodeType => {
Expand All @@ -30,6 +31,7 @@ export const OTPInput: React.FC<OTPInputProps> = ({
setErrorText,
handleReset,
className,
isVerified,
}) => {
const [autoComplete, setAutoComplete] = useState<string>("");
const [activeElement, setActiveElement] = useState<number | null>(0);
Expand Down Expand Up @@ -140,9 +142,17 @@ export const OTPInput: React.FC<OTPInputProps> = ({
<div className="flex gap-2.5">
{initialOTPValue.map((_, i) => (
<input
className={`border w-8 bg-bg-surface-default h-10 text-fg-primary rounded text-center focus:outline-none focus:border-active ${
!!errorText && "border-fg-critical"
}`}
className={`
border w-8 h-10 rounded text-center
focus:outline-none focus:border-active
${!disabled && "bg-bg-surface-default text-fg-primary"}
${!!errorText && "border-fg-critical"}
${isVerified && "border-fg-success"}
${
disabled &&
"border-fg-disabled bg-bg-surface-inset text-fg-disabled"
}
`}
ref={(el) => (refs.current[i] = el)}
tabIndex={i + 1}
type="text"
Expand Down
2 changes: 2 additions & 0 deletions account-kit/react/src/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const STRINGS = {
body: "We sent a verification code to",
notReceived: "Didn't receive code?",
resend: "Resend",
verifying: "Verifying...",
verified: "Verified!",
},
completingEmail: {
body: "Completing login. Please wait a few seconds for this to screen to update.",
Expand Down

0 comments on commit c4cc6a3

Please sign in to comment.