@@ -24,6 +43,20 @@ export function EmailVerificationModal({
+
+
+ resendEmailVerification({ userId, isAuthFlow: true })
+ }
+ isDisabled={isResending || isCooldownActive}
+ className="w-full font-semibold"
+ >
+ {resendButtonLabel}
+
+
+
diff --git a/frontend/src/hooks/mutation/use-resend-email-verification.ts b/frontend/src/hooks/mutation/use-resend-email-verification.ts
new file mode 100644
index 0000000000..2a8b8c4bb1
--- /dev/null
+++ b/frontend/src/hooks/mutation/use-resend-email-verification.ts
@@ -0,0 +1,49 @@
+import { useMutation } from "@tanstack/react-query";
+import { AxiosError } from "axios";
+import { useTranslation } from "react-i18next";
+import { I18nKey } from "#/i18n/declaration";
+import { emailService } from "#/api/email-service/email-service.api";
+import {
+ displaySuccessToast,
+ displayErrorToast,
+} from "#/utils/custom-toast-handlers";
+import { retrieveAxiosErrorMessage } from "#/utils/retrieve-axios-error-message";
+import { ResendEmailVerificationParams } from "#/api/email-service/email.types";
+
+interface UseResendEmailVerificationOptions {
+ onSuccess?: () => void;
+}
+
+export const useResendEmailVerification = (
+ options?: UseResendEmailVerificationOptions,
+) => {
+ const { t } = useTranslation();
+
+ return useMutation({
+ mutationFn: (params: ResendEmailVerificationParams) =>
+ emailService.resendEmailVerification(params),
+ onSuccess: () => {
+ displaySuccessToast(t(I18nKey.SETTINGS$VERIFICATION_EMAIL_SENT));
+ options?.onSuccess?.();
+ },
+ onError: (error: AxiosError) => {
+ // Check if it's a rate limit error (429)
+ if (error.response?.status === 429) {
+ // FastAPI returns errors in { detail: "..." } format
+ const errorData = error.response.data as
+ | { detail?: string }
+ | undefined;
+
+ const rateLimitMessage =
+ errorData?.detail ||
+ retrieveAxiosErrorMessage(error) ||
+ t(I18nKey.SETTINGS$FAILED_TO_RESEND_VERIFICATION);
+
+ displayErrorToast(rateLimitMessage);
+ } else {
+ // For other errors, show the generic error message
+ displayErrorToast(t(I18nKey.SETTINGS$FAILED_TO_RESEND_VERIFICATION));
+ }
+ },
+ });
+};
diff --git a/frontend/src/hooks/use-email-verification.ts b/frontend/src/hooks/use-email-verification.ts
index c0068395b5..29ed876295 100644
--- a/frontend/src/hooks/use-email-verification.ts
+++ b/frontend/src/hooks/use-email-verification.ts
@@ -1,10 +1,12 @@
import React from "react";
import { useSearchParams } from "react-router";
+import { useResendEmailVerification } from "#/hooks/mutation/use-resend-email-verification";
/**
* Hook to handle email verification logic from URL query parameters.
* Manages the email verification modal state and email verified state
* based on query parameters in the URL.
+ * Also provides functionality to resend email verification.
*
* @returns An object containing:
* - emailVerificationModalOpen: boolean state for modal visibility
@@ -12,6 +14,12 @@ import { useSearchParams } from "react-router";
* - emailVerified: boolean state for email verification status
* - setEmailVerified: function to control email verification status
* - hasDuplicatedEmail: boolean state for duplicate email error status
+ * - userId: string | null for the user ID from the redirect URL
+ * - resendEmailVerification: function to resend verification email
+ * - isResendingVerification: boolean indicating if resend is in progress
+ * - isCooldownActive: boolean indicating if cooldown is currently active
+ * - cooldownRemaining: number of milliseconds remaining in cooldown
+ * - formattedCooldownTime: string formatted as "M:SS" for display
*/
export function useEmailVerification() {
const [searchParams, setSearchParams] = useSearchParams();
@@ -19,6 +27,26 @@ export function useEmailVerification() {
React.useState(false);
const [emailVerified, setEmailVerified] = React.useState(false);
const [hasDuplicatedEmail, setHasDuplicatedEmail] = React.useState(false);
+ const [userId, setUserId] = React.useState