Parcourir la source

fix login doc frente verso

Gustavo Zanatta il y a 1 jour
Parent
commit
00d105c70c
1 fichiers modifiés avec 297 ajouts et 57 suppressions
  1. 297 57
      src/components/login/LoginStep4Panel.vue

+ 297 - 57
src/components/login/LoginStep4Panel.vue

@@ -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>