add pending email cancel button

This commit is contained in:
Gregor Vostrak
2026-05-29 15:31:33 +02:00
committed by Constantin Graf
parent 734aab6353
commit 24c19e7e7c
4 changed files with 121 additions and 9 deletions

View File

@@ -5,11 +5,13 @@ import axios from 'axios';
import ActionMessage from '@/Components/ActionMessage.vue';
import FormSection from '@/Components/FormSection.vue';
import { Field, FieldError, FieldLabel } from '@/packages/ui/src/field';
import { Button } from '@/packages/ui/src/Buttons';
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
import TextInput from '@/packages/ui/src/Input/TextInput.vue';
import {
useResendUserEmailVerificationMutation,
useResetUserPendingEmailMutation,
useUpdateUserMutation,
useUserQuery,
} from '@/utils/useUserQuery';
@@ -18,6 +20,7 @@ import type { UpdateUserBody, User } from '@/packages/api/src';
const { user } = useUserQuery();
const updateUser = useUpdateUserMutation();
const resendVerification = useResendUserEmailVerificationMutation();
const resetPendingEmail = useResetUserPendingEmailMutation();
const name = ref('');
const email = ref('');
@@ -152,6 +155,17 @@ async function clickResend() {
}
}
async function clickCancelEmailChange() {
if (!user.value || resetPendingEmail.isPending.value) return;
try {
// Clears pending_email on the server; the pending banner hides once the
// me query refetches. The email field already shows the current address.
await resetPendingEmail.mutateAsync(user.value.id);
} catch {
// notification handled by mutation
}
}
function flashSaved() {
recentlySaved.value = true;
setTimeout(() => (recentlySaved.value = false), 2000);
@@ -259,15 +273,26 @@ const page = usePage<{
<span class="font-medium">{{ pendingEmail }}</span
>. Click the link in the email to confirm the change.
</p>
<button
v-if="!resendCooldown"
type="button"
class="mt-1 underline text-text-secondary hover:text-text-primary rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
:disabled="!isUserLoaded || resendVerification.isPending.value"
@click="clickResend">
Resend verification email
</button>
<p v-else class="mt-1 font-medium text-green-400">Verification email sent.</p>
<div class="mt-2 -ms-3 flex flex-wrap items-center gap-x-1 gap-y-1">
<Button
v-if="!resendCooldown"
variant="ghost"
size="sm"
type="button"
:disabled="!isUserLoaded || resendVerification.isPending.value"
@click="clickResend">
Resend verification email
</Button>
<p v-else class="ms-3 font-medium text-green-400">Verification email sent.</p>
<Button
variant="ghost"
size="sm"
type="button"
:disabled="!isUserLoaded || resetPendingEmail.isPending.value"
@click="clickCancelEmailChange">
Cancel email change
</Button>
</div>
</div>
</Field>

View File

@@ -4573,6 +4573,38 @@ The report is considered public if the &#x60;is_public&#x60; field is set to &#x
},
],
},
{
method: 'post',
path: '/v1/users/:user/reset-pending-email',
alias: 'resetUserPendingEmail',
description: `This endpoint is independent of the organization.`,
requestFormat: 'json',
parameters: [
{
name: 'user',
type: 'Path',
schema: z.string(),
},
],
response: z.void(),
errors: [
{
status: 401,
description: `Unauthenticated`,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 403,
description: `Authorization error`,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 404,
description: `Not found`,
schema: z.object({ message: z.string() }).passthrough(),
},
],
},
{
method: 'post',
path: '/v1/users/:user/resend-email-verification',

View File

@@ -93,3 +93,21 @@ export function useResendUserEmailVerificationMutation() {
},
});
}
export function useResetUserPendingEmailMutation() {
const queryClient = useQueryClient();
const { handleApiRequestNotifications } = useNotificationsStore();
return useMutation({
mutationFn: async (userId: string) => {
return handleApiRequestNotifications(
() => api.resetUserPendingEmail(undefined, { params: { user: userId } }),
'Email change canceled',
'Failed to cancel email change'
);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ME_QUERY_KEY });
},
});
}