Files
dbx/apps/desktop/src/components/config/ConfigPassphraseDialog.vue
T

104 lines
3.3 KiB
Vue

<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { Lock } from "@lucide/vue";
import { Button } from "@/components/ui/button";
import PasswordInput from "@/components/ui/PasswordInput.vue";
import { Label } from "@/components/ui/label";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
const props = defineProps<{
open: boolean;
mode: "export" | "import";
externalError?: string;
busy?: boolean;
}>();
const emit = defineEmits<{
"update:open": [value: boolean];
confirm: [passphrase: string];
requestUnencrypted: [];
}>();
const { t } = useI18n();
const dialogOpen = computed({
get: () => props.open,
set: (v) => emit("update:open", v),
});
const passphrase = ref("");
const passphraseConfirm = ref("");
const error = ref("");
watch(
dialogOpen,
(open) => {
if (open) {
passphrase.value = "";
passphraseConfirm.value = "";
error.value = "";
}
},
{ immediate: true },
);
function confirm() {
if (props.busy) return;
if (!passphrase.value) {
error.value = t("configExport.passphraseRequired");
return;
}
if (props.mode === "export" && passphrase.value !== passphraseConfirm.value) {
error.value = t("configExport.passphraseMismatch");
return;
}
if (props.mode === "export" && passphrase.value.length < 4) {
error.value = t("configExport.passphraseTooShort");
return;
}
emit("confirm", passphrase.value);
}
const displayError = computed(() => error.value || props.externalError || "");
</script>
<template>
<Dialog v-model:open="dialogOpen">
<DialogContent class="sm:max-w-[440px]">
<DialogHeader>
<DialogTitle class="flex items-center gap-2">
<Lock class="h-5 w-5" />
{{ mode === "export" ? t("configExport.passphraseTitle") : t("configExport.passphraseImportTitle") }}
</DialogTitle>
</DialogHeader>
<div class="grid gap-4 py-4">
<p class="text-sm text-muted-foreground">
{{ mode === "export" ? t("configExport.passphraseExportHint") : t("configExport.passphraseImportHint") }}
</p>
<div class="grid gap-2">
<Label>{{ t("configExport.passphrase") }}</Label>
<PasswordInput v-model="passphrase" :placeholder="t('configExport.passphrasePlaceholder')" :toggle-tab-index="-1" :disabled="busy" @keydown.enter="mode === 'import' ? confirm() : undefined" />
</div>
<div v-if="mode === 'export'" class="grid gap-2">
<Label>{{ t("configExport.passphraseConfirm") }}</Label>
<PasswordInput v-model="passphraseConfirm" :placeholder="t('configExport.passphraseConfirmPlaceholder')" :toggle-tab-index="-1" :disabled="busy" @keydown.enter="confirm" />
</div>
<p v-if="displayError" class="text-sm text-destructive">{{ displayError }}</p>
</div>
<DialogFooter>
<Button v-if="mode === 'export'" type="button" variant="outline" :disabled="busy" @click="emit('requestUnencrypted')">
{{ t("configExport.exportUnencrypted") }}
</Button>
<Button type="button" :disabled="busy" @click="confirm">
{{ mode === "export" ? t("configExport.exportEncrypted") : t("configExport.decryptImport") }}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>