IdentityVerificationDialog.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. <template>
  2. <q-dialog v-model="open" persistent>
  3. <q-card class="verification-dialog">
  4. <q-card-section class="column items-center text-center q-gutter-y-md">
  5. <q-spinner-dots v-if="busy" color="primary" size="48px" />
  6. <q-icon v-else :color="iconColor" :name="icon" size="48px" />
  7. <div class="font16 fontbold text-text">{{ title }}</div>
  8. <div
  9. v-for="(line, index) in lines"
  10. :key="index"
  11. class="font14 text-text-secondary"
  12. >
  13. {{ line }}
  14. </div>
  15. <div
  16. v-if="showAttempts"
  17. class="font12 text-text-secondary"
  18. >
  19. {{ $t("verification.attempts_left", { count: attemptsLeft }) }}
  20. </div>
  21. </q-card-section>
  22. <q-card-actions align="right" class="q-pb-md q-px-md">
  23. <q-btn
  24. v-close-popup
  25. color="primary"
  26. flat
  27. no-caps
  28. rounded
  29. :label="$t('common.actions.close')"
  30. @click="onClose"
  31. />
  32. <q-btn
  33. v-if="showAction"
  34. color="primary"
  35. no-caps
  36. rounded
  37. unelevated
  38. :label="actionLabel"
  39. :loading="loading"
  40. @click="start"
  41. />
  42. </q-card-actions>
  43. </q-card>
  44. </q-dialog>
  45. </template>
  46. <script setup>
  47. import { computed, watch } from "vue";
  48. import { useI18n } from "vue-i18n";
  49. import {
  50. useIdentityVerification,
  51. VERIFICATION_STATUS,
  52. } from "src/composables/useIdentityVerification";
  53. defineOptions({
  54. name: "IdentityVerificationDialog",
  55. });
  56. const emit = defineEmits(["approved"]);
  57. const open = defineModel({
  58. required: true,
  59. type: Boolean,
  60. });
  61. const { t } = useI18n();
  62. const {
  63. attemptsLeft,
  64. canRetry,
  65. hasOpenSession,
  66. isApproved,
  67. loading,
  68. polling,
  69. refresh,
  70. start,
  71. status,
  72. } = useIdentityVerification();
  73. watch(open, (visible) => {
  74. if (visible) refresh().catch(() => {});
  75. }, { immediate: true });
  76. watch(isApproved, (approved) => {
  77. if (approved && open.value) emit("approved");
  78. });
  79. const busy = computed(() => loading.value || polling.value);
  80. const icon = computed(() => {
  81. switch (status.value) {
  82. case VERIFICATION_STATUS.APPROVED:
  83. return "mdi-check-circle-outline";
  84. case VERIFICATION_STATUS.DECLINED:
  85. return "mdi-alert-circle-outline";
  86. case VERIFICATION_STATUS.IN_REVIEW:
  87. return "mdi-clock-outline";
  88. default:
  89. return "mdi-card-account-details-outline";
  90. }
  91. });
  92. const iconColor = computed(() =>
  93. status.value === VERIFICATION_STATUS.APPROVED ? "positive" : "primary",
  94. );
  95. const title = computed(() => {
  96. if (busy.value) return t("verification.title");
  97. switch (status.value) {
  98. case VERIFICATION_STATUS.APPROVED:
  99. return t("verification.approved_title");
  100. case VERIFICATION_STATUS.DECLINED:
  101. return t("verification.declined_title");
  102. case VERIFICATION_STATUS.IN_REVIEW:
  103. return t("verification.in_review_title");
  104. case VERIFICATION_STATUS.PENDING:
  105. return t("verification.in_progress_title");
  106. default:
  107. return t("verification.title");
  108. }
  109. });
  110. const lines = computed(() => {
  111. if (busy.value) {
  112. return [loading.value ? t("verification.opening") : t("verification.checking")];
  113. }
  114. switch (status.value) {
  115. case VERIFICATION_STATUS.APPROVED:
  116. return [t("verification.approved")];
  117. case VERIFICATION_STATUS.DECLINED:
  118. return canRetry.value
  119. ? [t("verification.declined")]
  120. : [t("verification.attempts_exhausted")];
  121. case VERIFICATION_STATUS.IN_REVIEW:
  122. return [t("verification.in_review")];
  123. case VERIFICATION_STATUS.PENDING:
  124. return hasOpenSession.value
  125. ? [t("verification.resume_hint")]
  126. : [t("verification.in_progress")];
  127. default:
  128. return [t("verification.intro"), t("verification.documents_hint")];
  129. }
  130. });
  131. const showAction = computed(() => {
  132. if (busy.value || isApproved.value) return false;
  133. if (status.value === VERIFICATION_STATUS.DECLINED) return canRetry.value;
  134. if (status.value === VERIFICATION_STATUS.PENDING) return hasOpenSession.value;
  135. return [
  136. VERIFICATION_STATUS.NOT_STARTED,
  137. VERIFICATION_STATUS.EXPIRED,
  138. ].includes(status.value);
  139. });
  140. const actionLabel = computed(() => {
  141. if (status.value === VERIFICATION_STATUS.DECLINED) return t("verification.retry");
  142. if (status.value === VERIFICATION_STATUS.PENDING) return t("verification.resume");
  143. return t("verification.start");
  144. });
  145. const showAttempts = computed(
  146. () => showAction.value && Number.isFinite(attemptsLeft.value),
  147. );
  148. const onClose = () => {
  149. open.value = false;
  150. };
  151. </script>
  152. <style lang="scss" scoped>
  153. .verification-dialog {
  154. border-radius: 16px;
  155. max-width: 420px;
  156. width: 90vw;
  157. }
  158. </style>