Auth Mfa
block authTwo-step verification surface. 6-digit OTP pin input auto-submits on entry, shows verifying state and an inline error on mismatch, has a 30-second resend cooldown, and swaps to a Verified card with a Continue button on success. Emits `verify` (code), `resend`, and `continue`. `demoCode` prop controls the value the built-in mock validator accepts.
Also available for React ->Installation
$ pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/auth-mfa.json$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/auth-mfa.json$ yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/auth-mfa.json$ bunx shadcn-vue@latest add https://uipkge.dev/r/vue/auth-mfa.json
Or with the named registry:
npx shadcn-vue@latest add @uipkge/auth-mfa
Examples
npm dependencies
Files (1)
-
app/components/blocks/AuthMfa.vue 4 kB
<script setup lang="ts"> import { computed, ref, watch } from 'vue' import { ShieldCheck, RotateCw } from 'lucide-vue-next' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { PinInput, PinInputGroup, PinInputSlot } from '@/components/ui/pin-input' const props = withDefaults( defineProps<{ title?: string description?: string continueHref?: string recoveryHref?: string demoCode?: string }>(), { title: 'Two-step verification', description: 'Enter the 6-digit code from your authenticator app.', continueHref: '/', recoveryHref: '#', demoCode: '123456', }, ) const emit = defineEmits<{ (e: 'verify', code: string): void (e: 'resend'): void (e: 'continue'): void }>() const code = ref<string[]>([]) const verifying = ref(false) const verified = ref(false) const error = ref('') const resendIn = ref(0) const joined = computed(() => code.value.join('')) watch(joined, (val) => { if (val.length === 6) { error.value = '' verifying.value = true emit('verify', val) setTimeout(() => { verifying.value = false if (val === props.demoCode) verified.value = true else { error.value = `Invalid code. Try ${props.demoCode} for the demo.` code.value = [] } }, 700) } }) function startResendCooldown() { resendIn.value = 30 emit('resend') const t = setInterval(() => { resendIn.value -= 1 if (resendIn.value <= 0) clearInterval(t) }, 1000) } </script> <template> <div class="bg-background flex min-h-svh items-center justify-center p-6"> <Card class="w-full max-w-sm"> <template v-if="!verified"> <CardHeader class="text-center"> <div class="bg-primary/10 text-primary mx-auto mb-2 flex size-12 items-center justify-center rounded-full"> <ShieldCheck class="size-6" /> </div> <CardTitle class="text-2xl">{{ title }}</CardTitle> <CardDescription>{{ description }}</CardDescription> </CardHeader> <CardContent class="space-y-4"> <div class="flex justify-center"> <PinInput v-model="code" otp :disabled="verifying"> <PinInputGroup> <PinInputSlot v-for="i in 6" :key="i" :index="i - 1" /> </PinInputGroup> </PinInput> </div> <p v-if="verifying" class="text-muted-foreground text-center text-sm">Verifying…</p> <p v-if="error" class="text-destructive text-center text-sm">{{ error }}</p> <div class="text-center"> <button v-if="resendIn === 0" type="button" class="text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-xs underline-offset-4 hover:underline" @click="startResendCooldown" > <RotateCw class="size-3" />Resend code </button> <p v-else class="text-muted-foreground text-xs">Resend available in {{ resendIn }}s</p> </div> </CardContent> <CardFooter class="justify-center"> <p class="text-muted-foreground text-xs"> Lost your device? <a :href="recoveryHref" class="text-foreground underline-offset-4 hover:underline">Use a recovery code</a> </p> </CardFooter> </template> <template v-else> <CardContent class="space-y-4 pt-6 text-center"> <div class="bg-success/10 text-success mx-auto flex size-12 items-center justify-center rounded-full"> <ShieldCheck class="size-6" /> </div> <div class="space-y-1"> <h3 class="text-lg font-semibold">Verified</h3> <p class="text-muted-foreground text-sm">You're all set. Continuing to your dashboard…</p> </div> <a :href="continueHref" @click="emit('continue')"><Button class="w-full">Continue</Button></a> </CardContent> </template> </Card> </div> </template>
Raw manifest: https://uipkge.dev/r/vue/auth-mfa.json