| 1 | <template> |
| 2 | <div class="flex flex-col gap-2"> |
| 3 | <n-alert type="info">Backup codes generated. Save them — they are shown only once.</n-alert> |
| 4 | |
| 5 | <div class="grid max-w-140 grid-cols-2 gap-2"> |
| 6 | <n-code v-for="code in codes" :key="code" class="p-2 text-center"> |
| 7 | {{ code }} |
| 8 | </n-code> |
| 9 | </div> |
| 10 | |
| 11 | <div class="flex gap-2"> |
| 12 | <n-button v-if="isCopySupported" size="small" secondary @click="copyBackupCodes(codes)"> |
| 13 | <template #icon> |
| 14 | <Icon name="carbon:copy" /> |
| 15 | </template> |
| 16 | Copy all |
| 17 | </n-button> |
| 18 | <n-button size="small" secondary @click="downloadBackupCodes(codes)"> |
| 19 | <template #icon> |
| 20 | <Icon name="carbon:download" /> |
| 21 | </template> |
| 22 | Download .txt |
| 23 | </n-button> |
| 24 | </div> |
| 25 | </div> |
| 26 | </template> |
| 27 | |
| 28 | <script lang="ts" setup> |
| 29 | import { useClipboard } from "@vueuse/core" |
| 30 | import { saveAs } from "file-saver" |
| 31 | import { NAlert, NButton, NCode, useMessage } from "naive-ui" |
| 32 | import { watch } from "vue" |
| 33 | import Icon from "@/components/common/Icon.vue" |
| 34 | |
| 35 | defineProps<{ |
| 36 | codes: string[] |
| 37 | }>() |
| 38 | |
| 39 | const message = useMessage() |
| 40 | const { copy, copied, isSupported: isCopySupported } = useClipboard() |
| 41 | |
| 42 | function copyBackupCodes(codes: string[]) { |
| 43 | copy(codes.join("\n")) |
| 44 | } |
| 45 | |
| 46 | function downloadBackupCodes(codes: string[]) { |
| 47 | const text = `CoPilot — 2FA Backup Codes\n${"=".repeat(30)}\n\n${codes.join( |
| 48 | "\n" |
| 49 | )}\n\nKeep these codes safe. Each code can only be used once.\n` |
| 50 | |
| 51 | saveAs(new Blob([text], { type: "text/plain" }), "copilot-2fa-backup-codes.txt") |
| 52 | } |
| 53 | |
| 54 | watch(copied, newVal => { |
| 55 | if (newVal) { |
| 56 | message.success("Backup codes copied to clipboard") |
| 57 | } |
| 58 | }) |
| 59 | </script> |