|
|
@@ -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();
|