fix: add verification resend flow
All checks were successful
deploy-socialize / image (push) Successful in 1m21s
deploy-socialize / deploy (push) Successful in 14s

This commit is contained in:
2026-05-06 15:43:25 -04:00
parent 7a862a202a
commit a97ff2dc38
4 changed files with 103 additions and 19 deletions

View File

@@ -32,10 +32,18 @@ public class RegisterHandler(
RegisterRequest request, RegisterRequest request,
CancellationToken ct) CancellationToken ct)
{ {
// Check if the user already exists
User? existingUser = await userManager.FindByEmailAsync(request.Email); User? existingUser = await userManager.FindByEmailAsync(request.Email);
if (existingUser is not null) if (existingUser is not null)
{ {
if (!existingUser.EmailConfirmed)
{
await emailVerificationService.SendVerificationEmailAsync(existingUser);
await SendOkAsync(
new RegisterResponse("Registration successful! Please check your email to verify your account."),
ct);
return;
}
await SendStringAsync( await SendStringAsync(
"A user with this email already exists", "A user with this email already exists",
400, 400,

View File

@@ -198,7 +198,10 @@
} }
function resendVerification() { function resendVerification() {
router.push('/verify-email'); router.push({
path: '/verify-email',
query: email.value ? { email: email.value.trim() } : {},
});
} }
</script> </script>

View File

@@ -135,10 +135,12 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { useClient } from '@/plugins/api.js'; import { useClient } from '@/plugins/api.js';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { mdiEye, mdiEyeOff } from '@mdi/js'; import { mdiEye, mdiEyeOff } from '@mdi/js';
import { branding } from '@/branding/branding.js'; import { branding } from '@/branding/branding.js';
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter();
const clientApi = useClient(); const clientApi = useClient();
const name = ref(''); const name = ref('');
@@ -168,9 +170,12 @@
password: password.value, password: password.value,
}); });
// On success, show verification message
userEmail.value = email.value.trim(); userEmail.value = email.value.trim();
registrationSuccess.value = true; registrationSuccess.value = true;
await router.push({
path: '/verify-email',
query: { email: userEmail.value, pending: '1' },
});
} catch (error) { } catch (error) {
console.error('Registration failed:', error); console.error('Registration failed:', error);
errorMessage.value = error.response?.data?.message || t('registrationFailed'); errorMessage.value = error.response?.data?.message || t('registrationFailed');

View File

@@ -36,28 +36,18 @@
<!-- Error state --> <!-- Error state -->
<div <div
v-else v-else-if="showResendOnly"
class="flex flex-col items-center gap-6" class="flex flex-col items-center gap-6"
> >
<v-icon <v-icon
color="error" color="primary"
icon="mdi-alert-circle" icon="mdi-email-sync"
size="64" size="64"
></v-icon> ></v-icon>
<h1 class="text-2xl font-bold text-red-600">{{ t('error.title') }}</h1> <h1 class="text-2xl font-bold">{{ t('pending.title') }}</h1>
<p>{{ errorMessage || t('error.defaultMessage') }}</p> <p>{{ t('pending.message') }}</p>
<div class="mt-4 flex flex-col gap-4 w-full"> <div class="mt-4 flex flex-col gap-4 w-full">
<v-btn
color="primary"
@click="goToLogin"
>
{{ t('error.goToLogin') }}
</v-btn>
<v-divider class="my-4"></v-divider>
<!-- Resend verification email section -->
<h2 class="text-xl font-medium">{{ t('resend.title') }}</h2>
<v-form <v-form
class="w-full" class="w-full"
@submit.prevent="handleResendVerification" @submit.prevent="handleResendVerification"
@@ -99,6 +89,69 @@
</v-form> </v-form>
</div> </div>
</div> </div>
<!-- Error state -->
<div
v-else
class="flex flex-col items-center gap-6"
>
<v-icon
color="error"
icon="mdi-alert-circle"
size="64"
></v-icon>
<h1 class="text-2xl font-bold text-red-600">{{ t('error.title') }}</h1>
<p>{{ errorMessage || t('error.defaultMessage') }}</p>
<div class="mt-4 flex flex-col gap-4 w-full">
<v-btn
color="primary"
@click="goToLogin"
>
{{ t('error.goToLogin') }}
</v-btn>
<v-divider class="my-4"></v-divider>
<h2 class="text-xl font-medium">{{ t('resend.title') }}</h2>
<v-form
class="w-full"
@submit.prevent="handleResendVerification"
>
<div class="flex flex-col gap-4">
<v-text-field
v-model="resendEmail"
:error-messages="resendEmailError"
:label="t('resend.emailLabel')"
required
type="email"
></v-text-field>
<v-btn
:loading="resendLoading"
block
color="secondary"
type="submit"
>
{{ t('resend.button') }}
</v-btn>
<div
v-if="resendSuccess"
class="mt-2 p-3 bg-green-50 border border-green-200 rounded text-green-700 text-sm"
>
{{ t('resend.success') }}
</div>
<div
v-if="resendError"
class="mt-2 p-3 bg-red-50 border border-red-200 rounded text-red-700 text-sm"
>
{{ resendError }}
</div>
</div>
</v-form>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -118,6 +171,7 @@
const isLoading = ref(true); const isLoading = ref(true);
const verificationSuccess = ref(false); const verificationSuccess = ref(false);
const errorMessage = ref(''); const errorMessage = ref('');
const showResendOnly = ref(false);
// Resend verification state // Resend verification state
const resendEmail = ref(''); const resendEmail = ref('');
@@ -138,7 +192,11 @@
// Check if we have the required parameters // Check if we have the required parameters
if (!userId || !token) { if (!userId || !token) {
isLoading.value = false; isLoading.value = false;
if (route.query.email || route.query.pending) {
showResendOnly.value = true;
} else {
errorMessage.value = t('error.missingParams'); errorMessage.value = t('error.missingParams');
}
return; return;
} }
@@ -202,8 +260,13 @@
"missingParams": "Missing required verification parameters.", "missingParams": "Missing required verification parameters.",
"goToLogin": "Go to Login" "goToLogin": "Go to Login"
}, },
"pending": {
"title": "Check your email",
"message": "We sent a verification link to your inbox. You can request a new link if it doesn't arrive."
},
"resend": { "resend": {
"title": "Resend Verification Email", "title": "Resend Verification Email",
"description": "Enter your account email and we'll send a new verification link.",
"emailLabel": "Email", "emailLabel": "Email",
"button": "Resend Verification Email", "button": "Resend Verification Email",
"success": "Verification email sent successfully. Please check your inbox.", "success": "Verification email sent successfully. Please check your inbox.",
@@ -224,8 +287,13 @@
"missingParams": "Paramètres de vérification requis manquants.", "missingParams": "Paramètres de vérification requis manquants.",
"goToLogin": "Aller à la connexion" "goToLogin": "Aller à la connexion"
}, },
"pending": {
"title": "Vérifiez votre email",
"message": "Nous avons envoyé un lien de vérification dans votre boîte de réception. Vous pouvez demander un nouveau lien s'il n'arrive pas."
},
"resend": { "resend": {
"title": "Renvoyer l'email de vérification", "title": "Renvoyer l'email de vérification",
"description": "Entrez l'email de votre compte et nous enverrons un nouveau lien de vérification.",
"emailLabel": "Email", "emailLabel": "Email",
"button": "Renvoyer l'email de vérification", "button": "Renvoyer l'email de vérification",
"success": "Email de vérification envoyé avec succès. Veuillez vérifier votre boîte de réception.", "success": "Email de vérification envoyé avec succès. Veuillez vérifier votre boîte de réception.",