79 lines
1.7 KiB
Vue
79 lines
1.7 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
|
|
const props = defineProps<{
|
|
codes: string[]
|
|
}>()
|
|
|
|
const copied = ref(false)
|
|
|
|
function recoveryCodesText() {
|
|
return [
|
|
'Two-factor authentication recovery codes',
|
|
'',
|
|
...props.codes,
|
|
'',
|
|
'Each code can only be used once. Store these codes securely.',
|
|
].join('\n')
|
|
}
|
|
|
|
async function copyAll() {
|
|
await navigator.clipboard.writeText(props.codes.join('\n'))
|
|
copied.value = true
|
|
|
|
window.setTimeout(() => {
|
|
copied.value = false
|
|
}, 2000)
|
|
}
|
|
|
|
function download() {
|
|
const blob = new Blob([recoveryCodesText()], {
|
|
type: 'text/plain;charset=utf-8',
|
|
})
|
|
const url = URL.createObjectURL(blob)
|
|
const link = document.createElement('a')
|
|
|
|
link.href = url
|
|
link.download = 'two-factor-recovery-codes.txt'
|
|
link.click()
|
|
URL.revokeObjectURL(url)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="space-y-3">
|
|
<div class="flex flex-wrap gap-2">
|
|
<UButton
|
|
type="button"
|
|
variant="outline"
|
|
size="sm"
|
|
:color="copied ? 'success' : 'neutral'"
|
|
:icon="copied ? 'i-lucide-check' : 'i-lucide-copy'"
|
|
:aria-label="copied ? 'Recovery codes copied' : 'Copy all recovery codes'"
|
|
@click="copyAll"
|
|
>
|
|
{{ copied ? 'Copied' : 'Copy all' }}
|
|
</UButton>
|
|
<UButton
|
|
type="button"
|
|
color="neutral"
|
|
variant="outline"
|
|
size="sm"
|
|
icon="i-lucide-download"
|
|
aria-label="Download recovery codes"
|
|
@click="download"
|
|
>
|
|
Download
|
|
</UButton>
|
|
</div>
|
|
|
|
<div class="grid gap-2 sm:grid-cols-2">
|
|
<code
|
|
v-for="code in codes"
|
|
:key="code"
|
|
class="rounded bg-elevated px-3 py-2 text-center text-sm"
|
|
>{{ code }}</code>
|
|
</div>
|
|
</div>
|
|
</template>
|