|
@@ -0,0 +1,189 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <q-dialog v-model="open" persistent>
|
|
|
|
|
+ <q-card class="verification-dialog">
|
|
|
|
|
+ <q-card-section class="column items-center text-center q-gutter-y-md">
|
|
|
|
|
+ <q-spinner-dots v-if="busy" color="primary" size="48px" />
|
|
|
|
|
+
|
|
|
|
|
+ <q-icon v-else :color="iconColor" :name="icon" size="48px" />
|
|
|
|
|
+
|
|
|
|
|
+ <div class="font16 fontbold text-text">{{ title }}</div>
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(line, index) in lines"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ class="font14 text-text-secondary"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ line }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-if="showAttempts"
|
|
|
|
|
+ class="font12 text-text-secondary"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ $t("verification.attempts_left", { count: attemptsLeft }) }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </q-card-section>
|
|
|
|
|
+
|
|
|
|
|
+ <q-card-actions align="right" class="q-pb-md q-px-md">
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ v-close-popup
|
|
|
|
|
+ color="primary"
|
|
|
|
|
+ flat
|
|
|
|
|
+ no-caps
|
|
|
|
|
+ rounded
|
|
|
|
|
+ :label="$t('common.actions.close')"
|
|
|
|
|
+ @click="onClose"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ v-if="showAction"
|
|
|
|
|
+ color="primary"
|
|
|
|
|
+ no-caps
|
|
|
|
|
+ rounded
|
|
|
|
|
+ unelevated
|
|
|
|
|
+ :label="actionLabel"
|
|
|
|
|
+ :loading="loading"
|
|
|
|
|
+ @click="start"
|
|
|
|
|
+ />
|
|
|
|
|
+ </q-card-actions>
|
|
|
|
|
+ </q-card>
|
|
|
|
|
+ </q-dialog>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { computed, watch } from "vue";
|
|
|
|
|
+import { useI18n } from "vue-i18n";
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ useIdentityVerification,
|
|
|
|
|
+ VERIFICATION_STATUS,
|
|
|
|
|
+} from "src/composables/useIdentityVerification";
|
|
|
|
|
+
|
|
|
|
|
+defineOptions({
|
|
|
|
|
+ name: "IdentityVerificationDialog",
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const emit = defineEmits(["approved"]);
|
|
|
|
|
+
|
|
|
|
|
+const open = defineModel({
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ type: Boolean,
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const { t } = useI18n();
|
|
|
|
|
+
|
|
|
|
|
+const {
|
|
|
|
|
+ attemptsLeft,
|
|
|
|
|
+ canRetry,
|
|
|
|
|
+ hasOpenSession,
|
|
|
|
|
+ isApproved,
|
|
|
|
|
+ loading,
|
|
|
|
|
+ polling,
|
|
|
|
|
+ refresh,
|
|
|
|
|
+ start,
|
|
|
|
|
+ status,
|
|
|
|
|
+} = useIdentityVerification();
|
|
|
|
|
+
|
|
|
|
|
+watch(open, (visible) => {
|
|
|
|
|
+ if (visible) refresh().catch(() => {});
|
|
|
|
|
+}, { immediate: true });
|
|
|
|
|
+
|
|
|
|
|
+watch(isApproved, (approved) => {
|
|
|
|
|
+ if (approved && open.value) emit("approved");
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const busy = computed(() => loading.value || polling.value);
|
|
|
|
|
+
|
|
|
|
|
+const icon = computed(() => {
|
|
|
|
|
+ switch (status.value) {
|
|
|
|
|
+ case VERIFICATION_STATUS.APPROVED:
|
|
|
|
|
+ return "mdi-check-circle-outline";
|
|
|
|
|
+ case VERIFICATION_STATUS.DECLINED:
|
|
|
|
|
+ return "mdi-alert-circle-outline";
|
|
|
|
|
+ case VERIFICATION_STATUS.IN_REVIEW:
|
|
|
|
|
+ return "mdi-clock-outline";
|
|
|
|
|
+ default:
|
|
|
|
|
+ return "mdi-card-account-details-outline";
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const iconColor = computed(() =>
|
|
|
|
|
+ status.value === VERIFICATION_STATUS.APPROVED ? "positive" : "primary",
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+const title = computed(() => {
|
|
|
|
|
+ if (busy.value) return t("verification.title");
|
|
|
|
|
+
|
|
|
|
|
+ switch (status.value) {
|
|
|
|
|
+ case VERIFICATION_STATUS.APPROVED:
|
|
|
|
|
+ return t("verification.approved_title");
|
|
|
|
|
+ case VERIFICATION_STATUS.DECLINED:
|
|
|
|
|
+ return t("verification.declined_title");
|
|
|
|
|
+ case VERIFICATION_STATUS.IN_REVIEW:
|
|
|
|
|
+ return t("verification.in_review_title");
|
|
|
|
|
+ case VERIFICATION_STATUS.PENDING:
|
|
|
|
|
+ return t("verification.in_progress_title");
|
|
|
|
|
+ default:
|
|
|
|
|
+ return t("verification.title");
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const lines = computed(() => {
|
|
|
|
|
+ if (busy.value) {
|
|
|
|
|
+ return [loading.value ? t("verification.opening") : t("verification.checking")];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ switch (status.value) {
|
|
|
|
|
+ case VERIFICATION_STATUS.APPROVED:
|
|
|
|
|
+ return [t("verification.approved")];
|
|
|
|
|
+ case VERIFICATION_STATUS.DECLINED:
|
|
|
|
|
+ return canRetry.value
|
|
|
|
|
+ ? [t("verification.declined")]
|
|
|
|
|
+ : [t("verification.attempts_exhausted")];
|
|
|
|
|
+ case VERIFICATION_STATUS.IN_REVIEW:
|
|
|
|
|
+ return [t("verification.in_review")];
|
|
|
|
|
+ case VERIFICATION_STATUS.PENDING:
|
|
|
|
|
+ return hasOpenSession.value
|
|
|
|
|
+ ? [t("verification.resume_hint")]
|
|
|
|
|
+ : [t("verification.in_progress")];
|
|
|
|
|
+ default:
|
|
|
|
|
+ return [t("verification.intro"), t("verification.documents_hint")];
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const showAction = computed(() => {
|
|
|
|
|
+ if (busy.value || isApproved.value) return false;
|
|
|
|
|
+
|
|
|
|
|
+ if (status.value === VERIFICATION_STATUS.DECLINED) return canRetry.value;
|
|
|
|
|
+
|
|
|
|
|
+ if (status.value === VERIFICATION_STATUS.PENDING) return hasOpenSession.value;
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ VERIFICATION_STATUS.NOT_STARTED,
|
|
|
|
|
+ VERIFICATION_STATUS.EXPIRED,
|
|
|
|
|
+ ].includes(status.value);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const actionLabel = computed(() => {
|
|
|
|
|
+ if (status.value === VERIFICATION_STATUS.DECLINED) return t("verification.retry");
|
|
|
|
|
+ if (status.value === VERIFICATION_STATUS.PENDING) return t("verification.resume");
|
|
|
|
|
+
|
|
|
|
|
+ return t("verification.start");
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const showAttempts = computed(
|
|
|
|
|
+ () => showAction.value && Number.isFinite(attemptsLeft.value),
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+const onClose = () => {
|
|
|
|
|
+ open.value = false;
|
|
|
|
|
+};
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.verification-dialog {
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ max-width: 420px;
|
|
|
|
|
+ width: 90vw;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|