Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@
- click to access feedback details for items in personal feedback list
- Notifications infrastructure
- Anonymous feedback claiming
- Email verification nudge
- Create widgets for users to embed on their website
112 changes: 112 additions & 0 deletions components/EmailVerificationNudge.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<template>
<div
v-if="showNudge"
class="flex items-center gap-3 rounded-lg border border-yellow-200 bg-yellow-50 px-4 py-3 text-sm text-yellow-800 dark:border-yellow-800 dark:bg-yellow-950 dark:text-yellow-200"
>
<Icon name="lucide:mail-warning" class="h-4 w-4 shrink-0" />
<span class="flex-1">
Please verify your email address
<span class="font-medium">{{ userEmail }}</span>
to access all features.
</span>
<span v-if="successMessage" class="font-medium text-green-700 dark:text-green-400">
{{ successMessage }}
</span>
<span v-else-if="errorMessage" class="font-medium text-red-700 dark:text-red-400">
{{ errorMessage }}
</span>
<Button
v-if="!successMessage"
variant="outline"
size="sm"
:disabled="isSending"
class="shrink-0 border-yellow-300 bg-yellow-100 text-yellow-800 hover:bg-yellow-200 dark:border-yellow-700 dark:bg-yellow-900 dark:text-yellow-200 dark:hover:bg-yellow-800"
@click="resendVerification"
>
<Icon v-if="isSending" name="lucide:loader-circle" class="h-3 w-3 animate-spin" />
{{ isSending ? 'Sending…' : 'Resend email' }}
</Button>
<button
class="ml-1 shrink-0 rounded p-0.5 opacity-60 hover:opacity-100"
aria-label="Dismiss"
@click="dismiss"
>
<Icon name="lucide:x" class="h-4 w-4" />
</button>
</div>
</template>

<script>
import { authClient } from '~/lib/auth-client'

export default {
name: 'EmailVerificationNudge',

data() {
return {
session: null,
isPending: true,
dismissed: false,
isSending: false,
successMessage: '',
errorMessage: '',
}
},

computed: {
userEmail() {
return this.session?.user?.email || ''
},

emailVerified() {
return this.session?.user?.emailVerified ?? true
},

showNudge() {
return !this.isPending && !this.dismissed && !this.emailVerified
},
},

async mounted() {
if (import.meta.client) {
try {
const { data: session } = await authClient.useSession(useFetch)
this.session = session.value || null
this.isPending = false

watch(session, (next) => {
this.session = next || null
})
} catch {
this.isPending = false
}
}
},

methods: {
dismiss() {
this.dismissed = true
},

async resendVerification() {
if (!this.userEmail || this.isSending) return

this.isSending = true
this.errorMessage = ''
this.successMessage = ''

try {
await authClient.sendVerificationEmail({
email: this.userEmail,
callbackURL: '/dashboard',
})
this.successMessage = 'Verification email sent!'
Comment on lines +99 to +103

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Check sendVerificationEmail result before showing success

authClient.sendVerificationEmail does not throw on API failures by default (the client in lib/auth-client.ts does not enable fetchOptions.throw), so this try/catch path marks the action as successful even when the backend returns an error (for example rate limiting or mail delivery failure). In that case successMessage is set and the resend button is hidden (v-if="!successMessage"), which misleads users and prevents retrying from the banner.

Useful? React with 👍 / 👎.

} catch {
this.errorMessage = 'Failed to send. Please try again.'
} finally {
this.isSending = false
}
},
},
}
</script>
7 changes: 6 additions & 1 deletion layouts/dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@
</div>
</header>
<div class="flex flex-1 flex-col gap-4 p-4 pt-0">
<EmailVerificationNudge />
<slot />
</div>
</SidebarInset>
</SidebarProvider>
</TooltipProvider>
</template>

<script setup lang="ts"></script>
<script>
export default {
name: 'DashboardLayout',
}
</script>
Loading