|
|
@@ -58,7 +58,7 @@
|
|
|
:ref="fieldRefs.document_front"
|
|
|
class="photo-circle"
|
|
|
:class="docDone ? 'photo-circle--done' : 'photo-circle--pending'"
|
|
|
- @click="takeDocFront"
|
|
|
+ @click="openDocSubStep"
|
|
|
>
|
|
|
<q-icon
|
|
|
color="white"
|
|
|
@@ -74,6 +74,14 @@
|
|
|
{{ serverErrors.document_front }}
|
|
|
</div>
|
|
|
|
|
|
+ <div
|
|
|
+ v-if="serverErrors.document_back"
|
|
|
+ :ref="fieldRefs.document_back"
|
|
|
+ class="text-negative font12 text-center"
|
|
|
+ >
|
|
|
+ {{ serverErrors.document_back }}
|
|
|
+ </div>
|
|
|
+
|
|
|
<template v-if="!docDone">
|
|
|
<q-btn
|
|
|
color="primary-button"
|
|
|
@@ -81,8 +89,7 @@
|
|
|
padding="10px 28px"
|
|
|
rounded
|
|
|
:label="t('provider.login.steps.step_4.btn_document')"
|
|
|
- :loading="loadingDoc"
|
|
|
- @click="takeDocFront"
|
|
|
+ @click="openDocSubStep"
|
|
|
/>
|
|
|
|
|
|
<div class="font12 text-center text-text">
|
|
|
@@ -161,12 +168,167 @@
|
|
|
: t('provider.login.steps.step_4.btn_continue')"
|
|
|
@click="closeResult"
|
|
|
/>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Teleport>
|
|
|
+
|
|
|
+ <Teleport to="body">
|
|
|
+ <div
|
|
|
+ v-if="showDocSubStep"
|
|
|
+ class="fs-overlay doc-substep"
|
|
|
+ >
|
|
|
+ <div class="doc-substep__header">
|
|
|
+ <q-btn
|
|
|
+ flat
|
|
|
+ icon="arrow_back"
|
|
|
+ round
|
|
|
+ @click="closeDocSubStep"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="doc-substep__body column items-center q-gutter-y-lg">
|
|
|
+ <div class="doc-slot column items-center q-gutter-y-sm">
|
|
|
+ <div class="text-primary">
|
|
|
+ {{ t('provider.login.steps.step_4.document_front') }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="font12 text-center text-text">
|
|
|
+ {{ t('provider.login.steps.step_4.document_front_desc') }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="doc-slot__preview"
|
|
|
+ :class="docFrontDraft ? 'doc-slot__preview--done' : 'doc-slot__preview--pending'"
|
|
|
+ >
|
|
|
+ <q-img
|
|
|
+ v-if="docFrontDraft"
|
|
|
+ class="doc-slot__thumb"
|
|
|
+ :src="docFrontPreviewUrl"
|
|
|
+ />
|
|
|
+
|
|
|
+ <q-icon
|
|
|
+ v-else
|
|
|
+ color="white"
|
|
|
+ name="mdi-card-account-details-outline"
|
|
|
+ size="48px"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="docFrontDraft"
|
|
|
+ class="items-center q-gutter-x-xs row"
|
|
|
+ >
|
|
|
+ <q-icon
|
|
|
+ color="positive"
|
|
|
+ name="check_circle"
|
|
|
+ size="16px"
|
|
|
+ />
|
|
|
+
|
|
|
+ <span class="font12 text-primary">
|
|
|
+ {{ t('provider.login.steps.step_4.photo_captured') }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="docFrontError"
|
|
|
+ class="text-negative font12 text-center"
|
|
|
+ >
|
|
|
+ {{ t('provider.login.steps.step_4.error_message') }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <q-btn
|
|
|
+ color="primary-button"
|
|
|
+ no-caps
|
|
|
+ padding="8px 22px"
|
|
|
+ rounded
|
|
|
+ :label="docFrontDraft
|
|
|
+ ? t('provider.login.steps.step_4.btn_retake')
|
|
|
+ : t('provider.login.steps.step_4.btn_capture_front')"
|
|
|
+ :loading="loadingDocFront"
|
|
|
+ @click="captureDocFront"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="doc-slot column items-center q-gutter-y-sm">
|
|
|
+ <div class="text-primary">
|
|
|
+ {{ t('provider.login.steps.step_4.document_back') }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="font12 text-center text-text">
|
|
|
+ {{ t('provider.login.steps.step_4.document_back_desc') }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="doc-slot__preview"
|
|
|
+ :class="docBackDraft ? 'doc-slot__preview--done' : 'doc-slot__preview--pending'"
|
|
|
+ >
|
|
|
+ <q-img
|
|
|
+ v-if="docBackDraft"
|
|
|
+ class="doc-slot__thumb"
|
|
|
+ :src="docBackPreviewUrl"
|
|
|
+ />
|
|
|
+
|
|
|
+ <q-icon
|
|
|
+ v-else
|
|
|
+ color="white"
|
|
|
+ name="mdi-card-account-details-outline"
|
|
|
+ size="48px"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="docBackDraft"
|
|
|
+ class="items-center q-gutter-x-xs row"
|
|
|
+ >
|
|
|
+ <q-icon
|
|
|
+ color="positive"
|
|
|
+ name="check_circle"
|
|
|
+ size="16px"
|
|
|
+ />
|
|
|
+
|
|
|
+ <span class="font12 text-primary">
|
|
|
+ {{ t('provider.login.steps.step_4.photo_captured') }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="docBackError"
|
|
|
+ class="text-negative font12 text-center"
|
|
|
+ >
|
|
|
+ {{ t('provider.login.steps.step_4.error_message') }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <q-btn
|
|
|
+ color="primary-button"
|
|
|
+ no-caps
|
|
|
+ padding="8px 22px"
|
|
|
+ rounded
|
|
|
+ :label="docBackDraft
|
|
|
+ ? t('provider.login.steps.step_4.btn_retake')
|
|
|
+ : t('provider.login.steps.step_4.btn_capture_back')"
|
|
|
+ :loading="loadingDocBack"
|
|
|
+ @click="captureDocBack"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="doc-substep__footer">
|
|
|
+ <q-btn
|
|
|
+ v-if="docSubStepDone"
|
|
|
+ color="primary-button"
|
|
|
+ no-caps
|
|
|
+ padding="14px 48px"
|
|
|
+ rounded
|
|
|
+ style="min-width: 200px"
|
|
|
+ :label="t('provider.login.steps.step_4.btn_continue')"
|
|
|
+ @click="confirmDocCapture"
|
|
|
+ />
|
|
|
|
|
|
<div
|
|
|
- v-if="!resultError && showDocHint"
|
|
|
- class="font12 q-mt-sm text-center text-grey-6"
|
|
|
+ v-else
|
|
|
+ class="font12 text-center text-grey-6"
|
|
|
>
|
|
|
- {{ t('provider.login.steps.step_4.document_hint_result') }}
|
|
|
+ {{ t('provider.login.steps.step_4.upload_all_photos') }}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -181,7 +343,9 @@ import {
|
|
|
CameraResultType,
|
|
|
CameraSource,
|
|
|
} from '@capacitor/camera';
|
|
|
-import { computed, inject, ref } from 'vue';
|
|
|
+import {
|
|
|
+ computed, inject, onBeforeUnmount, ref,
|
|
|
+} from 'vue';
|
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
|
|
import LogoDiaria from 'src/assets/logo_diaria_campos_login.svg';
|
|
|
@@ -200,28 +364,39 @@ const serverErrors = inject("serverErrors", ref({}));
|
|
|
const fieldRefs = {
|
|
|
selfie: ref(null),
|
|
|
document_front: ref(null),
|
|
|
+ document_back: ref(null),
|
|
|
};
|
|
|
|
|
|
defineExpose({
|
|
|
- getFieldRef: (field) => field === 'document_back'
|
|
|
- ? fieldRefs.document_front
|
|
|
- : fieldRefs[field],
|
|
|
+ getFieldRef: (field) => fieldRefs[field] ?? null,
|
|
|
});
|
|
|
|
|
|
-const loadingDoc = ref(false);
|
|
|
const loadingSelfie = ref(false);
|
|
|
const resultError = ref(false);
|
|
|
const resultIcon = ref('photo_camera');
|
|
|
const resultMessage = ref('');
|
|
|
-const showDocHint = ref(false);
|
|
|
const showResult = ref(false);
|
|
|
-const tempDocFrontFile = ref(null);
|
|
|
+
|
|
|
+const showDocSubStep = ref(false);
|
|
|
+const docFrontDraft = ref(null);
|
|
|
+const docBackDraft = ref(null);
|
|
|
+const docFrontPreviewUrl = ref(null);
|
|
|
+const docBackPreviewUrl = ref(null);
|
|
|
+const loadingDocFront = ref(false);
|
|
|
+const loadingDocBack = ref(false);
|
|
|
+const docFrontError = ref(false);
|
|
|
+const docBackError = ref(false);
|
|
|
|
|
|
const docDone = computed(() => (
|
|
|
!!form.value.document_back
|
|
|
&& !!form.value.document_front
|
|
|
));
|
|
|
|
|
|
+const docSubStepDone = computed(() => (
|
|
|
+ !!docFrontDraft.value
|
|
|
+ && !!docBackDraft.value
|
|
|
+));
|
|
|
+
|
|
|
const selfieDone = computed(() => !!form.value.selfie);
|
|
|
|
|
|
const base64ToFile = (base64, filename, mimeType = 'image/jpeg') => {
|
|
|
@@ -267,20 +442,33 @@ const openCamera = async (direction) => {
|
|
|
);
|
|
|
};
|
|
|
|
|
|
-const takeDocBack = async () => {
|
|
|
+const setDraftFile = (side, file) => {
|
|
|
+ const draftRef = side === 'front' ? docFrontDraft : docBackDraft;
|
|
|
+ const previewRef = side === 'front' ? docFrontPreviewUrl : docBackPreviewUrl;
|
|
|
+
|
|
|
+ if (previewRef.value) {
|
|
|
+ URL.revokeObjectURL(previewRef.value);
|
|
|
+ }
|
|
|
+
|
|
|
+ draftRef.value = file;
|
|
|
+ previewRef.value = file ? URL.createObjectURL(file) : null;
|
|
|
+};
|
|
|
+
|
|
|
+const captureDocSide = async (side) => {
|
|
|
+ const loadingRef = side === 'front' ? loadingDocFront : loadingDocBack;
|
|
|
+ const errorRef = side === 'front' ? docFrontError : docBackError;
|
|
|
+
|
|
|
+ if (loadingRef.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ loadingRef.value = true;
|
|
|
+ errorRef.value = false;
|
|
|
+
|
|
|
try {
|
|
|
const file = await openCamera('rear');
|
|
|
|
|
|
- form.value.document_back = file;
|
|
|
- form.value.document_front = tempDocFrontFile.value;
|
|
|
- resultError.value = false;
|
|
|
- resultIcon.value = 'mdi-card-account-details-outline';
|
|
|
- resultMessage.value = t('provider.login.steps.step_4.document_success');
|
|
|
- showDocHint.value = true;
|
|
|
- showResult.value = true;
|
|
|
- tempDocFrontFile.value = null;
|
|
|
-
|
|
|
- emit('update:show-sub-step', true);
|
|
|
+ setDraftFile(side, file);
|
|
|
} catch (err) {
|
|
|
const msg = (err?.message || '').toLowerCase();
|
|
|
|
|
|
@@ -289,51 +477,60 @@ const takeDocBack = async () => {
|
|
|
&& !msg.includes('dismiss')
|
|
|
&& !msg.includes('no image')
|
|
|
) {
|
|
|
- resultError.value = true;
|
|
|
- resultIcon.value = 'mdi-card-account-details-outline';
|
|
|
- showDocHint.value = false;
|
|
|
- showResult.value = true;
|
|
|
-
|
|
|
- emit('update:show-sub-step', true);
|
|
|
+ errorRef.value = true;
|
|
|
}
|
|
|
-
|
|
|
- tempDocFrontFile.value = null;
|
|
|
+ } finally {
|
|
|
+ loadingRef.value = false;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-const takeDocFront = async () => {
|
|
|
- if (loadingDoc.value) {
|
|
|
- return;
|
|
|
+const captureDocFront = () => captureDocSide('front');
|
|
|
+const captureDocBack = () => captureDocSide('back');
|
|
|
+
|
|
|
+const openDocSubStep = () => {
|
|
|
+ if (!docFrontDraft.value && form.value.document_front) {
|
|
|
+ setDraftFile('front', form.value.document_front);
|
|
|
}
|
|
|
|
|
|
- loadingDoc.value = true;
|
|
|
+ if (!docBackDraft.value && form.value.document_back) {
|
|
|
+ setDraftFile('back', form.value.document_back);
|
|
|
+ }
|
|
|
|
|
|
- try {
|
|
|
- const file = await openCamera('rear');
|
|
|
+ docFrontError.value = false;
|
|
|
+ docBackError.value = false;
|
|
|
+ showDocSubStep.value = true;
|
|
|
|
|
|
- tempDocFrontFile.value = file;
|
|
|
+ emit('update:show-sub-step', true);
|
|
|
+};
|
|
|
|
|
|
- await takeDocBack();
|
|
|
- } catch (err) {
|
|
|
- const msg = (err?.message || '').toLowerCase();
|
|
|
+const closeDocSubStep = () => {
|
|
|
+ showDocSubStep.value = false;
|
|
|
|
|
|
- if (
|
|
|
- !msg.includes('cancel')
|
|
|
- && !msg.includes('dismiss')
|
|
|
- && !msg.includes('no image')
|
|
|
- ) {
|
|
|
- resultError.value = true;
|
|
|
- resultIcon.value = 'mdi-card-account-details-outline';
|
|
|
- showDocHint.value = false;
|
|
|
- showResult.value = true;
|
|
|
+ emit('update:show-sub-step', false);
|
|
|
+};
|
|
|
|
|
|
- emit('update:show-sub-step', true);
|
|
|
- }
|
|
|
- } finally {
|
|
|
- loadingDoc.value = false;
|
|
|
+const confirmDocCapture = () => {
|
|
|
+ if (!docSubStepDone.value) {
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ form.value.document_front = docFrontDraft.value;
|
|
|
+ form.value.document_back = docBackDraft.value;
|
|
|
+ showDocSubStep.value = false;
|
|
|
+
|
|
|
+ emit('update:show-sub-step', false);
|
|
|
};
|
|
|
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ if (docFrontPreviewUrl.value) {
|
|
|
+ URL.revokeObjectURL(docFrontPreviewUrl.value);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (docBackPreviewUrl.value) {
|
|
|
+ URL.revokeObjectURL(docBackPreviewUrl.value);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
const takeSelfie = async () => {
|
|
|
if (loadingSelfie.value) {
|
|
|
return;
|
|
|
@@ -348,7 +545,6 @@ const takeSelfie = async () => {
|
|
|
resultError.value = false;
|
|
|
resultIcon.value = 'photo_camera';
|
|
|
resultMessage.value = t('provider.login.steps.step_4.selfie_success');
|
|
|
- showDocHint.value = false;
|
|
|
showResult.value = true;
|
|
|
|
|
|
emit('update:show-sub-step', true);
|
|
|
@@ -362,7 +558,6 @@ const takeSelfie = async () => {
|
|
|
) {
|
|
|
resultError.value = true;
|
|
|
resultIcon.value = 'photo_camera';
|
|
|
- showDocHint.value = false;
|
|
|
showResult.value = true;
|
|
|
|
|
|
emit('update:show-sub-step', true);
|
|
|
@@ -430,4 +625,49 @@ const takeSelfie = async () => {
|
|
|
background-color: rgba(139, 92, 246, 0.18);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.doc-substep {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ &__header {
|
|
|
+ padding: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__body {
|
|
|
+ flex: 1;
|
|
|
+ padding: 0 24px;
|
|
|
+ overflow-y: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__footer {
|
|
|
+ padding: 24px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.doc-slot__preview {
|
|
|
+ width: 160px;
|
|
|
+ height: 110px;
|
|
|
+ border-radius: 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ &--pending {
|
|
|
+ background-color: rgba(139, 92, 246, 0.18);
|
|
|
+ }
|
|
|
+
|
|
|
+ &--done {
|
|
|
+ background: linear-gradient(-90deg, #ec48d1 5%, #6b11cb 65%, #2574fc 100%);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.doc-slot__thumb {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
</style>
|