Files
RenewCMS/web/src/views/PendingView.vue
T

149 lines
3.8 KiB
Vue

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useOverlay, useToast } from '@nuxt/ui/composables'
import AuthShell from '@/components/AuthShell.vue'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import ResultPanel from '@/components/ResultPanel.vue'
import { ApiError, useApi } from '@/composables/useApi'
import { registrationCancelledToast } from '@/lib/registrationToasts'
import { VERIFIED_REDIRECT_DELAY_MS, wait } from '@/lib/verifiedRedirect'
import { useSessionStore } from '@/stores/session'
const router = useRouter()
const api = useApi()
const session = useSessionStore()
const overlay = useOverlay()
const toast = useToast()
const pendingEmail = ref<string | null>(null)
const loggingOut = ref(false)
const cancelOutcome = ref<'cancelled' | 'alreadyVerified' | null>(null)
const verified = ref(false)
onMounted(() => {
pendingEmail.value = sessionStorage.getItem('renewcms:pendingEmail')
document.title = 'Check your email · RenewCMS'
})
const description = computed(() =>
pendingEmail.value
? `We sent a verification link to ${pendingEmail.value}. It expires in 2 hours.`
: 'We sent a verification link to the email you registered with. It expires in 2 hours.',
)
async function logOut() {
loggingOut.value = true
try {
await api.request('/v1/auth/logout', { method: 'POST' })
} finally {
loggingOut.value = false
}
session.clear()
await router.push('/login')
}
async function deleteRegistration() {
try {
await api.request('/v1/auth/register', {
method: 'DELETE',
onForbidden: 'passthrough',
})
cancelOutcome.value = 'cancelled'
} catch (error) {
if (error instanceof ApiError && error.status === 403) {
await session.retry()
cancelOutcome.value = 'alreadyVerified'
return
}
throw new Error(
"Couldn't cancel registration, so it's still pending. Try again in a moment.",
)
}
}
async function cancelRegistration() {
cancelOutcome.value = null
const confirmed = await overlay
.create(ConfirmDialog, {
props: {
title: 'Cancel your registration?',
description:
'Your account will be deleted. You can register again with the same or a different email.',
confirmLabel: 'Cancel registration',
cancelLabel: 'Keep my account',
pendingLabel: 'Cancelling…',
tone: 'danger',
action: deleteRegistration,
},
})
.open()
if (!confirmed) {
return
}
if (cancelOutcome.value === 'cancelled') {
await router.push('/register')
toast.add(registrationCancelledToast)
return
}
if (cancelOutcome.value === 'alreadyVerified') {
verified.value = true
toast.add({
color: 'info',
icon: 'i-lucide-info',
title: 'Your account is already verified',
description: "There's nothing to cancel.",
})
await wait(VERIFIED_REDIRECT_DELAY_MS)
await router.push('/home')
}
}
const actions = computed(() => [
{
label: 'Log out',
color: 'neutral' as const,
variant: 'outline' as const,
disabled: loggingOut.value,
onClick: logOut,
},
{
label: 'Cancel registration',
color: 'neutral' as const,
variant: 'outline' as const,
disabled: loggingOut.value,
onClick: cancelRegistration,
},
])
</script>
<template>
<AuthShell title="">
<ResultPanel
v-if="verified"
tone="success"
icon="i-lucide-shield-check"
title="You're verified"
description="Taking you to your dashboard…"
:actions="[]"
/>
<ResultPanel
v-else
tone="success"
icon="i-lucide-mail-check"
title="Check your email"
:description="description"
:actions="actions"
>
<p class="text-sm text-pretty text-muted">
Opened it on another device? That works too; you'll log in there first. Can't find
it? Check your spam folder. If the link has expired, cancel your registration and
start again.
</p>
</ResultPanel>
</AuthShell>
</template>