Browse Source

refactor: deixa o prestador atualizar sua foto (agora o backend tem fluxo pra lidar com a autorizacao recorrente apos troca de foto de perfil

Gustavo Mantovani 1 tuần trước cách đây
mục cha
commit
fe99e9fa21

+ 15 - 0
src/api/provider.js

@@ -5,6 +5,21 @@ export const updateProvider = async (id, data) => {
   return response.payload;
 };
 
+export const updateProviderWithFiles = async (id, provider) => {
+  const form = new FormData();
+
+  Object.entries(provider).forEach(([key, value]) => {
+    if (value !== null && value !== undefined) {
+      form.append(key, value);
+    }
+  });
+
+  form.append('_method', 'PUT');
+
+  const { data: response } = await api.post(`/provider/${id}`, form);
+  return response.payload;
+};
+
 export const getProviderDailyPriceMin = async () => {
   const { data: response } = await api.get('/provider/daily-price-min');
   return response.payload ?? response;

+ 1 - 0
src/i18n/locales/en.json

@@ -480,6 +480,7 @@
     "edit_profile": "Edit profile",
     "edit_data": "My data",
     "change_photo": "Request photo change",
+    "photo_capture_error": "Unable to take the selfie. Please try again.",
     "full_name": "Full Name",
     "email": "E-mail",
     "phone": "Phone",

+ 1 - 0
src/i18n/locales/es.json

@@ -480,6 +480,7 @@
     "edit_profile": "Editar perfil",
     "edit_data": "Mis datos",
     "change_photo": "Solicitar cambio de foto",
+    "photo_capture_error": "No fue posible tomar la selfie. Inténtelo de nuevo.",
     "full_name": "Nombre completo",
     "email": "Correo electrónico",
     "phone": "Teléfono",

+ 1 - 0
src/i18n/locales/pt.json

@@ -483,6 +483,7 @@
     "edit_profile": "Editar perfil",
     "edit_data": "Meu dados",
     "change_photo": "Solicitar alteração de foto",
+    "photo_capture_error": "Não foi possível tirar a selfie. Tente novamente.",
     "full_name": "Nome Completo",
     "email": "E-mail",
     "phone": "Telefone",

+ 110 - 19
src/pages/profile/ProfileEditDialog.vue

@@ -71,6 +71,8 @@
               flat
               no-caps
               :label="$t('profile.change_photo')"
+              :loading="capturingPhoto"
+              @click="takeSelfie"
             />
           </div>
 
@@ -143,8 +145,8 @@
               padding="8px 16px"
               rounded
               unelevated
-              :color="hasUpdatedFields ? 'primary' : 'grey-4'"
-              :disable="!hasUpdatedFields"
+              :color="hasChanges ? 'primary' : 'grey-4'"
+              :disable="!hasChanges"
               :label="$t('profile.update')"
               :loading="submitting"
               @click="submitUpdate"
@@ -157,10 +159,18 @@
 </template>
 
 <script setup>
+import {
+  Camera,
+  CameraDirection,
+  CameraResultType,
+  CameraSource,
+} from "@capacitor/camera";
+
 import { computed, onMounted, ref } from "vue";
 import { genderOptions } from "src/helpers/genderOptions";
-import { updateUser } from "src/api/user";
-import { useDialogPluginComponent } from "quasar";
+import { getUser, updateUser } from "src/api/user";
+import { updateProviderWithFiles } from "src/api/provider";
+import { useDialogPluginComponent, useQuasar } from "quasar";
 import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
 import { useI18n } from "vue-i18n";
 
@@ -176,7 +186,9 @@ defineEmits([...useDialogPluginComponent.emits]);
 const { dialogRef, onDialogCancel, onDialogOK } = useDialogPluginComponent();
 const { t } = useI18n();
 
-const { form, hasUpdatedFields, setUpdateFormAsOriginal } =
+const $q = useQuasar();
+
+const { form, getUpdatedFields, hasUpdatedFields, setUpdateFormAsOriginal } =
   useFormUpdateTracker({
     email: "",
     gender: "",
@@ -184,10 +196,17 @@ const { form, hasUpdatedFields, setUpdateFormAsOriginal } =
     phone: "",
   });
 
-const loading      = ref(false);
-const profilePhoto = ref(null);
-const submitting   = ref(false);
-const userId       = ref(null);
+const capturingPhoto = ref(false);
+const loading        = ref(false);
+const profilePhoto   = ref(null);
+const providerId     = ref(null);
+const selfieFile     = ref(null);
+const submitting     = ref(false);
+const userId         = ref(null);
+
+const hasChanges = computed(
+  () => hasUpdatedFields.value || !!selfieFile.value,
+);
 
 const translatedGenderOptions = computed(() =>
   genderOptions.map((option) => ({
@@ -200,23 +219,94 @@ const avatarStyle = {
   background: "linear-gradient(135deg, #ffd7e8 0%, #ff9acc 100%)", color: "#7a154f",
 };
 
+const base64ToFile = (base64, mimeType) => {
+  const byteString = atob(base64);
+
+  const bytes = new Uint8Array(byteString.length);
+
+  for (let index = 0; index < byteString.length; index++) {
+    bytes[index] = byteString.charCodeAt(index);
+  }
+
+  return new File([bytes], "selfie.jpg", { type: mimeType });
+};
+
+const isCameraCancellation = (error) => {
+  const message = String(error?.message ?? "").toLowerCase();
+
+  return (
+    message.includes("cancel")
+    || message.includes("dismiss")
+    || message.includes("no image")
+  );
+};
+
+const takeSelfie = async () => {
+  if (capturingPhoto.value) return;
+
+  capturingPhoto.value = true;
+
+  try {
+    const photo = await Camera.getPhoto({
+      allowEditing: false,
+      direction: CameraDirection.Front,
+      quality: 85,
+      resultType: CameraResultType.Base64,
+      saveToGallery: false,
+      source: CameraSource.Camera,
+    });
+
+    const base64 = photo.base64String?.replace(/\s/g, "");
+
+    if (!base64) {
+      throw new Error("Camera did not return image data");
+    }
+
+    const mimeType = `image/${photo.format || "jpeg"}`;
+
+    selfieFile.value = base64ToFile(base64, mimeType);
+
+    profilePhoto.value = `data:${mimeType};base64,${base64}`;
+  } catch (error) {
+    if (!isCameraCancellation(error)) {
+      console.error("Erro ao capturar selfie:", error);
+
+      $q.notify({
+        message: t("profile.photo_capture_error"),
+        type: "negative",
+      });
+    }
+  } finally {
+    capturingPhoto.value = false;
+  }
+};
+
 const submitUpdate = async () => {
-  if (!hasUpdatedFields.value) return;
+  if (!hasChanges.value) return;
 
   submitting.value = true;
 
   try {
-    const data = await updateUser(
-      {
-        email: form.email,
-        gender: form.gender,
-        name: form.name,
-        phone: form.phone,
-      },
-      userId.value,
-    );
+    const userData = { ...getUpdatedFields.value };
+
+    let data = null;
+
+    if (Object.keys(userData).length > 0) {
+      data = await updateUser(userData, userId.value);
+    }
+
+    if (selfieFile.value) {
+      await updateProviderWithFiles(providerId.value, {
+        avatar: selfieFile.value,
+      });
+
+      data = await getUser();
+    }
 
     setUpdateFormAsOriginal();
+
+    selfieFile.value = null;
+
     onDialogOK(data);
   } catch (error) {
     console.error("Erro ao atualizar perfil:", error);
@@ -239,6 +329,7 @@ onMounted(() => {
   form.name = data.name || "";
   form.phone = data.phone || "";
   profilePhoto.value = data.profile_photo || null;
+  providerId.value = data.provider_id || data.provider?.id || null;
   userId.value = data.id;
 
   setUpdateFormAsOriginal();