| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- <template>
- <q-dialog
- ref="dialogRef"
- maximized
- persistent
- transition-hide="slide-right"
- transition-show="slide-left"
- >
- <q-card class="bg-white full-width full-height">
- <div
- class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile"
- >
- <q-btn
- class="header-back-btn"
- color="primary"
- dense
- flat
- icon="mdi-chevron-left"
- round
- @click="onDialogCancel"
- />
- <q-space />
- <span
- class="text-subtitle1 text-primary font16 fontbold gradient-diarista"
- >
- {{ $t("profile.edit_data") }}
- </span>
- <q-space />
- <div style="width: 32px"></div>
- </div>
- <div
- v-if="loading"
- class="flex flex-center q-pa-xl"
- >
- <q-spinner
- color="primary"
- size="3em"
- />
- </div>
- <template v-else>
- <q-scroll-area
- class="col"
- style="height: calc(100vh - 72px)"
- >
- <div class="column items-center q-mt-xl q-mb-md">
- <q-avatar
- class="text-h2 shadow-1"
- color="indigo-1"
- size="140px"
- text-color="indigo-4"
- >
- <img
- v-if="avatarPreview"
- :src="avatarPreview"
- style="object-fit: cover; width: 100%; height: 100%"
- />
- <span v-else>
- {{ form.name ? form.name.charAt(0).toUpperCase() : "" }}
- </span>
- </q-avatar>
- <input
- ref="fileInputRef"
- accept="image/jpeg,image/png,image/webp"
- class="hidden"
- type="file"
- @change="onFileSelected"
- />
- <q-btn
- class="q-mt-sm"
- color="grey-6"
- flat
- no-caps
- :label="$t('profile.change_photo')"
- @click="fileInputRef.click()"
- />
- </div>
- <q-form
- ref="formRef"
- @submit.prevent="onSubmit"
- >
- <div class="q-px-xl q-gutter-y-lg">
- <div>
- <div class="text-grey-8 q-mb-sm font12 fontbold">
- {{ $t("profile.full_name") }}
- </div>
- <q-input
- v-model="form.name"
- dense
- input-class="text-text"
- outlined
- :error="!!serverErrors.name"
- :error-message="serverErrors.name"
- :placeholder="$t('profile.placeholder_name')"
- :rules="[inputRules.required]"
- @update:model-value="serverErrors.name = null"
- />
- </div>
- <div>
- <div class="text-grey-8 q-mb-sm font12 fontbold">
- {{ $t("profile.email") }}
- </div>
- <q-input
- v-model="form.email"
- dense
- input-class="text-text"
- outlined
- :error="!!serverErrors.email"
- :error-message="serverErrors.email"
- :placeholder="$t('profile.placeholder_email')"
- :rules="[inputRules.email]"
- @update:model-value="serverErrors.email = null"
- />
- </div>
- <div>
- <div class="text-grey-8 q-mb-sm font12 fontbold">
- {{ $t("profile.phone") }}
- </div>
- <q-input
- v-model="form.phone"
- dense
- input-class="text-text"
- mask="(##) #####-####"
- outlined
- unmasked-value
- :error="!!serverErrors.phone"
- :error-message="serverErrors.phone"
- :placeholder="$t('profile.placeholder_phone')"
- @update:model-value="serverErrors.phone = null"
- />
- </div>
- <div>
- <div class="text-grey-8 q-mb-sm font12 fontbold">
- {{ $t("common.terms.cpf") }}
- </div>
- <q-input
- v-model="form.document"
- dense
- hide-bottom-space
- input-class="text-text"
- lazy-rules
- mask="###.###.###-##"
- no-error-icon
- outlined
- placeholder="000.000.000-00"
- type="tel"
- unmasked-value
- :error="!!serverErrors.document"
- :error-message="serverErrors.document"
- :rules="[inputRules.cpf]"
- @update:model-value="serverErrors.document = null"
- />
- </div>
- <div>
- <div class="text-grey-8 q-mb-sm font12 fontbold">
- {{ $t("profile.language") }}
- </div>
- <div class="row">
- <q-radio
- v-model="selectedLocale"
- class="text-text col-4 font10 fontmedium"
- color="primary"
- keep-color
- val="pt"
- :label="$t('profile.lang_pt')"
- @update:model-value="onLocaleChange"
- />
- <q-radio
- v-model="selectedLocale"
- class="text-text col-4 font10 fontmedium"
- color="primary"
- keep-color
- val="en"
- :label="$t('profile.lang_en')"
- @update:model-value="onLocaleChange"
- />
- <q-radio
- v-model="selectedLocale"
- class="text-text col-4 font10 fontmedium"
- color="primary"
- keep-color
- val="es"
- :label="$t('profile.lang_es')"
- @update:model-value="onLocaleChange"
- />
- </div>
- </div>
- <div>
- <div class="text-grey-8 q-mb-sm font12 fontbold">
- {{ $t("profile.notifications") }}
- </div>
- <div class="notification-card">
- <span class="font14 text-text">
- {{ $t("profile.push_notifications") }}
- </span>
- <q-toggle
- v-model="form.push_notifications_enabled"
- color="primary"
- />
- </div>
- </div>
- </div>
- <q-space />
- <div class="q-pa-xl q-mt-md">
- <q-btn
- class="full-width q-py-md"
- no-caps
- padding="8px 16px"
- rounded
- type="submit"
- unelevated
- :color="hasUpdatedFields || avatarFile ? 'primary' : 'grey-4'"
- :disable="!hasUpdatedFields && !avatarFile"
- :label="$t('profile.update')"
- :loading="submitting"
- />
- </div>
- </q-form>
- </q-scroll-area>
- </template>
- </q-card>
- </q-dialog>
- </template>
- <script setup>
- import { Cookies, useDialogPluginComponent } from "quasar";
- import { getProfileMediaUrl } from "src/helpers/profileMedia";
- import { i18n } from "src/boot/i18n";
- import { onMounted, ref } from "vue";
- import { updateMe } from "src/api/user";
- import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
- import { useInputRules } from "src/composables/useInputRules";
- import { useSubmitHandler } from "src/composables/useSubmitHandler";
- defineEmits([...useDialogPluginComponent.emits]);
- const props = defineProps({
- userData: { type: Object, default: null },
- });
- const { dialogRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
- const { form, getUpdatedFields, hasUpdatedFields, setUpdateFormAsOriginal } =
- useFormUpdateTracker({
- name: "",
- email: "",
- phone: "",
- document: "",
- push_notifications_enabled: false,
- });
- const {
- loading: submitting,
- serverErrors,
- execute: submitForm,
- } = useSubmitHandler((data) => {
- setUpdateFormAsOriginal(data);
- onDialogOK(data);
- });
- const { inputRules } = useInputRules();
- const avatarFile = ref(null);
- const avatarPreview = ref(null);
- const fileInputRef = ref(null);
- const formRef = ref(null);
- const loading = ref(false);
- const normalizeLocale = (loc) => {
- if (!loc) return "pt";
- const l = String(loc).toLowerCase();
- if (l.startsWith("pt")) return "pt";
- if (l.startsWith("en")) return "en";
- if (l.startsWith("es")) return "es";
- return "pt";
- };
- const selectedLocale = ref(
- normalizeLocale(i18n.global.locale.value ?? i18n.global.locale),
- );
- //
- const fileToBase64 = (file) =>
- new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = () => resolve(reader.result);
- reader.onerror = () => reject(reader.error);
- reader.readAsDataURL(file);
- });
- const onFileSelected = (event) => {
- const file = event.target.files[0];
- if (!file) return;
- avatarFile.value = file;
- avatarPreview.value = URL.createObjectURL(file);
- };
- const onLocaleChange = (val) => {
- i18n.global.locale.value = val;
- Cookies.set("locale", val, { expires: 365, path: "/" });
- };
- const onSubmit = async () => {
- const valid = await formRef.value.validate();
- if (!valid) return;
- const payload = { ...getUpdatedFields.value };
- if ("document" in payload) {
- payload.document = payload.document || null;
- }
- if (avatarFile.value) {
- payload.avatar_base64 = await fileToBase64(avatarFile.value);
- }
- await submitForm(() => updateMe(payload));
- };
- onMounted(async () => {
- if (props.userData) {
- const data = props.userData;
- form.name = data.name || "";
- form.email = data.email || "";
- form.phone = data.phone || "";
- form.document = (data.client_document || "").replace(/\D/g, "");
- form.push_notifications_enabled = data.push_notifications_enabled ?? false;
- avatarPreview.value = getProfileMediaUrl(data);
- setUpdateFormAsOriginal(data);
- return;
- }
- loading.value = true;
- });
- </script>
- <style scoped lang="scss">
- :deep(.q-field--outlined .q-field__control) {
- border-radius: 8px;
- &::before {
- border: 1px solid #e0e0e0;
- }
- }
- .hidden {
- display: none;
- }
- .notification-card {
- display: flex;
- align-items: center;
- justify-content: space-between;
- min-height: 56px;
- padding: 0 16px;
- border: 1px solid #e0e0e0;
- border-radius: 8px;
- background: #fff;
- }
- .notification-card :deep(.q-toggle__inner--falsy) {
- color: rgba(0, 0, 0, 0.62);
- .q-toggle__thumb:after {
- box-shadow:
- 0 0 0 1px #bdbdbd,
- 0 2px 2px 0 rgba(0, 0, 0, 0.14);
- }
- }
- </style>
|