Skip to content

Commit 2be9027

Browse files
authored
Merge pull request #24 from magiclabs/fix-loading-states
fix: implement loading states in auth modal
2 parents 68cd226 + c0890e1 commit 2be9027

File tree

5 files changed

+104
-54
lines changed

5 files changed

+104
-54
lines changed

src/app/(demo)/embedded-wallet/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { LoadingScreen } from "@/components/LoadingScreen";
88
import { PageHeader } from "@/components/PageHeader";
99

1010
export default function Home() {
11-
const { isAuthenticated, isLoading } = useEmbeddedWallet();
11+
const { isAuthenticated, isLoading, fetchAllNetworkAddresses } =
12+
useEmbeddedWallet();
1213
const router = useRouter();
1314

1415
useEffect(() => {
@@ -40,7 +41,7 @@ export default function Home() {
4041
{/* Main Content */}
4142
<div className="flex flex-col [@media(min-width:820px)]:flex-row items-center gap-10 lg:gap-20 w-full max-w-[820px]">
4243
{/* Email OTP Authentication */}
43-
<EmailOTPAuth />
44+
<EmailOTPAuth onSuccess={fetchAllNetworkAddresses} />
4445

4546
<div className="h-px [@media(min-width:820px)]:h-64 w-full [@media(min-width:820px)]:w-px bg-slate-3 flex-shrink-0" />
4647

src/components/Modal.tsx

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ interface ModalProps {
99
message: string;
1010
retries?: number;
1111
maxRetries?: number;
12-
errorMessage?: string;
12+
inputValue: string;
13+
setInputValue: (value: string) => void;
14+
errorMessage: string | undefined;
15+
setErrorMessage: (value: string | undefined) => void;
16+
isLoading?: boolean;
1317
onSubmit?: (value: string) => void;
1418
onCancel?: () => void;
1519
onClose?: () => void;
@@ -82,22 +86,17 @@ export const Modal = ({
8286
type,
8387
title,
8488
message,
89+
inputValue,
90+
setInputValue,
8591
errorMessage,
92+
setErrorMessage,
93+
isLoading,
8694
onSubmit,
8795
onCancel,
8896
onClose,
8997
}: ModalProps) => {
90-
const [inputValue, setInputValue] = React.useState("");
91-
const [localErrorMessage, setLocalErrorMessage] = React.useState<
92-
string | undefined
93-
>(errorMessage);
9498
const inputRef = React.useRef<HTMLInputElement>(null);
9599

96-
// Update local error message when prop changes
97-
React.useEffect(() => {
98-
setLocalErrorMessage(errorMessage);
99-
}, [errorMessage]);
100-
101100
React.useEffect(() => {
102101
if (isOpen && (type === "otp" || type === "mfa" || type === "recovery")) {
103102
// Focus on input after modal opens
@@ -110,16 +109,14 @@ export const Modal = ({
110109
const handleInputChange = (value: string) => {
111110
setInputValue(value);
112111
// Clear error message when user starts typing
113-
if (localErrorMessage) {
114-
setLocalErrorMessage(undefined);
112+
if (errorMessage) {
113+
setErrorMessage(undefined);
115114
}
116115
};
117116

118117
const handleSubmit = () => {
119118
if (onSubmit) {
120119
onSubmit(inputValue);
121-
setInputValue("");
122-
setLocalErrorMessage(undefined);
123120
}
124121
};
125122

@@ -128,15 +125,15 @@ export const Modal = ({
128125
onCancel();
129126
}
130127
setInputValue("");
131-
setLocalErrorMessage(undefined);
128+
setErrorMessage(undefined);
132129
};
133130

134131
const handleClose = () => {
135132
if (onClose) {
136133
onClose();
137134
}
138135
setInputValue("");
139-
setLocalErrorMessage(undefined);
136+
setErrorMessage(undefined);
140137
};
141138

142139
const handleKeyPress = (e: React.KeyboardEvent) => {
@@ -195,7 +192,7 @@ export const Modal = ({
195192
onChange={handleInputChange}
196193
onKeyDown={handleKeyPress}
197194
inputRef={inputRef}
198-
errorMessage={localErrorMessage}
195+
errorMessage={errorMessage}
199196
/>
200197
)}
201198

@@ -218,9 +215,9 @@ export const Modal = ({
218215
onClick={handleSubmit}
219216
variant="primary"
220217
fullWidth
221-
disabled={!inputValue}
218+
disabled={!inputValue || isLoading}
222219
>
223-
Submit
220+
{isLoading ? "Submitting..." : "Submit"}
224221
</Button>
225222
</>
226223
)}

src/components/embedded-wallet/auth/EmailOTPAuth.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ export function EmailOTPAuth({ onSuccess }: EmailOTPAuthProps) {
2121
modalState,
2222
handleWhitelabelEmailOTPLogin: whitelabelLogin,
2323
closeModal,
24+
inputValue,
25+
setInputValue,
26+
errorMessage,
27+
setErrorMessage,
28+
isLoading: isLoadingModal,
2429
} = useEmailOTPModal();
2530

2631
// Load persisted email on component mount
@@ -153,7 +158,11 @@ export function EmailOTPAuth({ onSuccess }: EmailOTPAuthProps) {
153158
message={modalState.message}
154159
retries={modalState.retries}
155160
maxRetries={modalState.maxRetries}
156-
errorMessage={modalState.errorMessage}
161+
inputValue={inputValue}
162+
setInputValue={setInputValue}
163+
errorMessage={errorMessage}
164+
setErrorMessage={setErrorMessage}
165+
isLoading={isLoadingModal}
157166
onSubmit={modalState.onSubmit}
158167
onCancel={modalState.onCancel}
159168
onClose={closeModal}

src/components/embedded-wallet/wallet/UserMethods.tsx

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -73,34 +73,34 @@ export function UserMethods() {
7373
payload: null,
7474
handler: () => MagicService.magic.user.showSettings(),
7575
},
76-
{
77-
value: "recover-account",
78-
label: "Recover Account",
79-
functionName: "magic.user.recoverAccount()",
80-
payload: null,
81-
handler: () =>
82-
MagicService.magic.user.getInfo().then((user: MagicUserMetadata) =>
83-
MagicService.magic.user
84-
.logout()
85-
.then(() =>
86-
MagicService.magic.user.recoverAccount({ email: user.email })
87-
)
88-
.catch((error: unknown) => {
89-
logToConsole(
90-
LogType.ERROR,
91-
LogMethod.MAGIC_USER_RECOVER_ACCOUNT,
92-
"Error recovering account",
93-
error
94-
);
95-
logToConsole(
96-
LogType.INFO,
97-
LogMethod.MAGIC_USER_RECOVER_ACCOUNT,
98-
"Redirecting to auth page"
99-
);
100-
router.push("/embedded-wallet");
101-
})
102-
),
103-
},
76+
// {
77+
// value: "recover-account",
78+
// label: "Recover Account",
79+
// functionName: "magic.user.recoverAccount()",
80+
// payload: null,
81+
// handler: () =>
82+
// MagicService.magic.user.getInfo().then((user: MagicUserMetadata) =>
83+
// MagicService.magic.user
84+
// .logout()
85+
// .then(() =>
86+
// MagicService.magic.user.recoverAccount({ email: user.email })
87+
// )
88+
// .catch((error: unknown) => {
89+
// logToConsole(
90+
// LogType.ERROR,
91+
// LogMethod.MAGIC_USER_RECOVER_ACCOUNT,
92+
// "Error recovering account",
93+
// error
94+
// );
95+
// logToConsole(
96+
// LogType.INFO,
97+
// LogMethod.MAGIC_USER_RECOVER_ACCOUNT,
98+
// "Redirecting to auth page"
99+
// );
100+
// router.push("/embedded-wallet");
101+
// })
102+
// ),
103+
// },
104104
revealPrivateKeyTab,
105105
{
106106
value: "enable-mfa",

src/hooks/useEmailOTPModal.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@ interface ModalState {
1313
message: string;
1414
retries?: number;
1515
maxRetries?: number;
16-
errorMessage?: string;
16+
// errorMessage?: string;
1717
onSubmit?: (value: string) => void;
1818
onCancel?: () => void;
1919
}
2020

2121
export function useEmailOTPModal() {
22+
const [inputValue, setInputValue] = useState("");
23+
const [errorMessage, setErrorMessage] = useState<string | undefined>(
24+
undefined
25+
);
26+
const [isLoading, setIsLoading] = useState(false);
2227
const [modalState, setModalState] = useState<ModalState>({
2328
isOpen: false,
2429
type: "otp",
@@ -33,6 +38,7 @@ export function useEmailOTPModal() {
3338
}, []);
3439

3540
const closeModal = useCallback(() => {
41+
setIsLoading(false);
3642
setModalState((prev) => ({ ...prev, isOpen: false }));
3743
}, []);
3844

@@ -83,6 +89,7 @@ export function useEmailOTPModal() {
8389
retries: retries,
8490
maxRetries: 2,
8591
onSubmit: (otp) => {
92+
setIsLoading(true);
8693
handle.emit(
8794
LoginWithEmailOTPEventEmit.VerifyEmailOtp,
8895
otp as never
@@ -95,15 +102,22 @@ export function useEmailOTPModal() {
95102
});
96103

97104
handle.on(LoginWithEmailOTPEventOnReceived.InvalidEmailOtp, () => {
105+
setIsLoading(false);
106+
98107
if (!retries) {
99108
handle.emit(LoginWithEmailOTPEventEmit.Cancel);
100109
} else {
101110
// Update the existing modal with error message instead of opening a new one
111+
setInputValue("");
112+
setErrorMessage(
113+
`Invalid code, please enter OTP again. Retries left: ${retries}`
114+
);
115+
102116
setModalState((prev) => ({
103117
...prev,
104-
errorMessage: `Invalid code, please enter OTP again. Retries left: ${retries}`,
105118
retries: retries,
106119
onSubmit: (otp: string) => {
120+
setIsLoading(true);
107121
retries--;
108122
handle.emit(
109123
LoginWithEmailOTPEventEmit.VerifyEmailOtp,
@@ -117,13 +131,17 @@ export function useEmailOTPModal() {
117131
// MFA
118132
let retriesMFA = 2;
119133
handle.on(LoginWithEmailOTPEventOnReceived.MfaSentHandle, () => {
134+
setInputValue("");
135+
setIsLoading(false);
136+
120137
openModal({
121138
type: "mfa",
122139
title: "Enter MFA Code",
123140
message: "Please enter the MFA code from your authenticator app",
124141
retries: retriesMFA,
125142
maxRetries: 2,
126143
onSubmit: (mfa) => {
144+
setIsLoading(true);
127145
handle.emit(
128146
LoginWithEmailOTPEventEmit.VerifyMFACode,
129147
mfa as never
@@ -136,15 +154,21 @@ export function useEmailOTPModal() {
136154
});
137155

138156
handle.on(LoginWithEmailOTPEventOnReceived.InvalidMfaOtp, () => {
157+
setIsLoading(false);
158+
setInputValue("");
159+
139160
if (!retriesMFA) {
140161
handle.emit(LoginWithEmailOTPEventEmit.LostDevice);
141162
} else {
163+
setErrorMessage(
164+
`Invalid MFA code, please enter MFA code again. Retries left: ${retriesMFA}`
165+
);
142166
// Update the existing modal with error message instead of opening a new one
143167
setModalState((prev) => ({
144168
...prev,
145-
errorMessage: `Invalid MFA code, please enter MFA code again. Retries left: ${retriesMFA}`,
146169
retries: retriesMFA,
147170
onSubmit: (mfa: string) => {
171+
setIsLoading(true);
148172
retriesMFA--;
149173
handle.emit(
150174
LoginWithEmailOTPEventEmit.VerifyMFACode,
@@ -160,13 +184,17 @@ export function useEmailOTPModal() {
160184
handle.on(
161185
LoginWithEmailOTPEventOnReceived.RecoveryCodeSentHandle,
162186
() => {
187+
setInputValue("");
188+
setIsLoading(false);
189+
163190
openModal({
164191
type: "recovery",
165192
title: "Enter Recovery Code",
166193
message: "Please enter your MFA recovery code",
167194
retries: retriesRecoveryMFA,
168195
maxRetries: 2,
169196
onSubmit: (recovery) => {
197+
setIsLoading(true);
170198
handle.emit(
171199
LoginWithEmailOTPEventEmit.VerifyRecoveryCode,
172200
recovery as string
@@ -180,15 +208,22 @@ export function useEmailOTPModal() {
180208
);
181209

182210
handle.on(LoginWithEmailOTPEventOnReceived.InvalidRecoveryCode, () => {
211+
setInputValue("");
212+
setIsLoading(false);
213+
183214
if (!retriesRecoveryMFA) {
184215
handle.emit(LoginWithEmailOTPEventEmit.Cancel);
185216
} else {
217+
setErrorMessage(
218+
`Invalid recovery code, please enter recovery code again. Retries left: ${retriesRecoveryMFA}`
219+
);
186220
// Update the existing modal with error message instead of opening a new one
187221
setModalState((prev) => ({
188222
...prev,
189-
errorMessage: `Invalid recovery code, please enter recovery code again. Retries left: ${retriesRecoveryMFA}`,
190223
retries: retriesRecoveryMFA,
191224
onSubmit: (recovery: string) => {
225+
setIsLoading(true);
226+
192227
retriesRecoveryMFA--;
193228
handle.emit(
194229
LoginWithEmailOTPEventEmit.VerifyRecoveryCode,
@@ -201,6 +236,8 @@ export function useEmailOTPModal() {
201236

202237
handle.on("error", (error: unknown) => {
203238
const errorMsg = `Error: ${error}`;
239+
setIsLoading(false);
240+
204241
openModal({
205242
type: "error",
206243
title: "Authentication Error",
@@ -212,6 +249,7 @@ export function useEmailOTPModal() {
212249
});
213250

214251
handle.on("done", () => {
252+
setIsLoading(false);
215253
openModal({
216254
type: "success",
217255
title: "Login Complete!",
@@ -259,6 +297,11 @@ export function useEmailOTPModal() {
259297

260298
return {
261299
modalState,
300+
inputValue,
301+
setInputValue,
302+
errorMessage,
303+
setErrorMessage,
304+
isLoading,
262305
handleWhitelabelEmailOTPLogin,
263306
closeModal,
264307
};

0 commit comments

Comments
 (0)