Sfoglia il codice sorgente

feat: :sparkles: edicao do profile do prestador

edicao do profile do prestador
Gustavo Zanatta 1 mese fa
parent
commit
963dc81939

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

@@ -408,5 +408,32 @@
       "passives": "Passives",
       "detractors": "Detractors"
     }
+  },
+  "profile": {
+    "title": "Profile",
+    "edit_profile": "Edit profile",
+    "edit_data": "My data",
+    "change_photo": "Request photo change",
+    "full_name": "Full Name",
+    "email": "E-mail",
+    "phone": "Phone",
+    "update": "Update",
+    "update_success": "Profile updated successfully!",
+    "update_error": "Error updating profile",
+    "placeholder_name": "Enter your name",
+    "placeholder_email": "Enter your e-mail",
+    "placeholder_phone": "Enter your phone",
+    "bank_data": {
+      "title": "Bank data",
+      "description": "Pix, agency and account"
+    },
+    "availability": {
+      "title": "Availability",
+      "description": "Days I am available"
+    },
+    "service_data": {
+      "title": "Service data",
+      "description": "My specialties"
+    }
   }
 }

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

@@ -444,5 +444,32 @@
         }
       }
     }
+  },
+  "profile": {
+    "title": "Perfil",
+    "edit_profile": "Editar perfil",
+    "edit_data": "Mis datos",
+    "change_photo": "Solicitar cambio de foto",
+    "full_name": "Nombre completo",
+    "email": "Correo electrónico",
+    "phone": "Teléfono",
+    "update": "Actualizar",
+    "update_success": "¡Perfil actualizado con éxito!",
+    "update_error": "Error al actualizar el perfil",
+    "placeholder_name": "Escriba su nombre",
+    "placeholder_email": "Escriba su correo electrónico",
+    "placeholder_phone": "Escriba su teléfono",
+    "bank_data": {
+      "title": "Datos bancarios",
+      "description": "Pix, agencia y cuenta"
+    },
+    "availability": {
+      "title": "Disponibilidad",
+      "description": "Días que estoy disponible"
+    },
+    "service_data": {
+      "title": "Datos de servicio",
+      "description": "Mis especialidades"
+    }
   }
 }

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

@@ -262,6 +262,45 @@
     "opportunities": "Oportunidades",
     "plans": "Planos"
   },
+  "profile": {
+    "title": "Perfil",
+    "edit_profile": "Editar perfil",
+    "edit_data": "Meu dados",
+    "change_photo": "Solicitar alteração de foto",
+    "full_name": "Nome Completo",
+    "email": "E-mail",
+    "phone": "Telefone",
+    "update": "Atualizar",
+    "update_success": "Perfil atualizado com sucesso!",
+    "update_error": "Erro ao atualizar perfil",
+    "placeholder_name": "Digite seu nome",
+    "placeholder_email": "Digite seu e-mail",
+    "placeholder_phone": "Digite seu telefone",
+    "bank_data": {
+      "title": "Dados bancários",
+      "description": "Pix, agência e conta"
+    },
+    "availability": {
+      "title": "Disponibilidade",
+      "description": "Dias que estou disponível"
+    },
+    "service_data": {
+      "title": "Dados de serviço",
+      "description": "Valores e serviços"
+    },
+    "address": {
+      "title": "Endereço",
+      "description": "Localização para serviços"
+    },
+    "help": {
+      "title": "Ajuda",
+      "description": "Dúvidas e suporte"
+    },
+    "logout": {
+      "title": "Sair",
+      "description": "Desconectar da sua conta"
+    }
+  },
   "validation": {
     "rules": {
       "required": "Este campo é obrigatório",

+ 154 - 0
src/pages/profile/ProfileEditDialog.vue

@@ -0,0 +1,154 @@
+<template>
+  <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
+    <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 flat round dense icon="mdi-chevron-left" color="primary" @click="onDialogCancel" />
+        <q-space />
+        <span class="text-subtitle1 text-weight-bold text-primary">{{ $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 size="140px" color="indigo-1" text-color="indigo-4" class="text-weight-bold text-h2 shadow-1">
+              {{ form.name ? form.name.charAt(0).toUpperCase() : '' }}
+            </q-avatar>
+            <q-btn flat no-caps color="grey-6" class="q-mt-sm" :label="$t('profile.change_photo')" />
+          </div>
+
+          <div class="q-px-xl q-gutter-y-lg">
+            <div>
+              <div class="text-weight-bold text-grey-8 q-mb-sm">{{ $t('profile.full_name') }}</div>
+              <q-input
+                v-model="form.name"
+                outlined
+                dense
+                input-class="text-text"
+                :placeholder="$t('profile.placeholder_name')"
+              />
+            </div>
+
+            <div>
+              <div class="text-weight-bold text-grey-8 q-mb-sm">{{ $t('profile.email') }}</div>
+              <q-input
+                v-model="form.email"
+                outlined
+                dense
+                input-class="text-text"
+                :placeholder="$t('profile.placeholder_email')"
+              />
+            </div>
+
+            <div>
+              <div class="text-weight-bold text-grey-8 q-mb-sm">{{ $t('profile.phone') }}</div>
+              <q-input
+                v-model="form.phone"
+                outlined
+                dense
+                input-class="text-text"
+                mask="(##) #####-####"
+                unmasked-value
+                :placeholder="$t('profile.placeholder_phone')"
+              />
+            </div>
+          </div>
+
+          <q-space/>
+
+          <div class="q-pa-xl q-mt-md">
+            <q-btn
+              unelevated
+              rounded
+              no-caps
+              padding="8px 16px"
+              class="full-width q-py-md text-weight-bold"
+              :label="$t('profile.update')"
+              :color="hasUpdatedFields ? 'primary' : 'grey-4'"
+              :disable="!hasUpdatedFields"
+              :loading="submitting"
+              @click="submitUpdate"
+            />
+          </div>
+        </q-scroll-area>
+      </template>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import { useDialogPluginComponent } from 'quasar';
+import { updateUser } from 'src/api/user';
+import { useFormUpdateTracker } from 'src/composables/useFormUpdateTracker';
+
+const props = defineProps({
+  userData: {
+    type: Object,
+    default: null
+  }
+});
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
+const loading = ref(false);
+const submitting = ref(false);
+const userId = ref(null);
+
+const { form, hasUpdatedFields, setUpdateFormAsOriginal } = useFormUpdateTracker({
+  name: '',
+  email: '',
+  phone: ''
+});
+
+const submitUpdate = async () => {
+  if (!hasUpdatedFields.value) return;
+
+  submitting.value = true;
+  try {
+    const data = await updateUser({
+      name: form.name,
+      email: form.email,
+      phone: form.phone
+    }, userId.value);
+    console.log(data)
+    setUpdateFormAsOriginal(data);
+    onDialogOK(data);
+  } catch (error) {
+    console.error('Erro ao atualizar perfil:', error);
+  } finally {
+    submitting.value = false;
+  }
+};
+
+onMounted(async () => {
+  if (props.userData) {
+    const data = props.userData;
+    userId.value = data.id;
+    form.name = data.name || '';
+    form.email = data.email || '';
+    form.phone = data.phone || '';
+    setUpdateFormAsOriginal(data);
+    return;
+  }
+
+  loading.value = true;
+});
+</script>
+
+<style scoped lang="scss">
+.shadow-profile {
+  box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.05);
+}
+
+:deep(.q-field--outlined .q-field__control) {
+  border-radius: 8px;
+  &::before { border: 1px solid #e0e0e0; }
+}
+</style>

+ 184 - 38
src/pages/profile/ProfilePage.vue

@@ -1,52 +1,198 @@
 <!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
 <template>
-  <section class="mobile-placeholder">
-    <div class="mobile-placeholder__badge">
-      <q-icon name="mdi-account-circle-outline" />
+  <q-page class="bg-page q-pb-xl">
+    <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile">
+      <q-space />
+      <span class="text-subtitle1 text-weight-bold text-primary">{{ $t('profile.title') }}</span>
+      <q-space />
     </div>
-    <h1 class="mobile-placeholder__title">Perfil</h1>
-    <p class="mobile-placeholder__description">
-      Área reservada para informações da conta, dados profissionais e preferências.
-    </p>
-  </section>
+
+    <div class="q-px-md q-mt-md">
+      <q-card class="profile-card bg-surface shadow-card card-border" :flat="false">
+        <q-btn flat round dense icon="mdi-share-variant-outline" color="grey-6" class="absolute-top-right q-ma-sm" />
+        
+        <q-card-section class="column items-center q-pb-md">
+          <q-avatar size="70px" class="shadow-card">
+            <img src="https://cdn.quasar.dev/img/avatar.png">
+          </q-avatar>
+          
+          <div class="fonte-nome-profile text-weight-bold q-mt-md text-dark">{{ user.name || '—' }}</div>
+          <div class="fonte-email-profile text-grey-6 q-my-sm">{{ user.email || '—' }}</div>
+          <div class="fonte-telefone-profile text-grey-7">{{ user.phone || '—' }}</div> 
+          
+          <q-btn 
+            outline 
+            no-caps 
+            padding="6px 16px"
+            class="full-width q-mt-sm btn-edit-profile text-text" 
+            @click="openEditProfile"
+          >
+            <div class="row items-center q-gutter-x-sm">
+              <q-icon name="mdi-account-outline" size="18px" />
+              <span class="text-weight-medium">{{ $t('profile.edit_profile') }}</span>
+            </div>
+          </q-btn>
+        </q-card-section>
+      </q-card>
+    </div>
+
+    <div class="q-mt-md q-px-md column">
+      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm">
+        <div class="column">
+          <span class="menu-title gradient-diarista text-weight-bold">{{ $t('profile.bank_data.title') }}</span>
+          <span class="menu-description text-text">{{ $t('profile.bank_data.description') }}</span>
+        </div>
+        <q-space/>
+        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+      </div>
+
+      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm">
+        <div class="column">
+          <span class="menu-title gradient-diarista text-weight-bold">{{ $t('profile.availability.title') }}</span>
+          <span class="menu-description text-text">{{ $t('profile.availability.description') }}</span>
+        </div>
+        <q-space/>
+        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+      </div>
+
+      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm">
+        <div class="column">
+          <span class="menu-title gradient-diarista text-weight-bold">{{ $t('profile.service_data.title') }}</span>
+          <span class="menu-description text-text">{{ $t('profile.service_data.description') }}</span>
+        </div>
+        <q-space/>
+        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+      </div>
+
+      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm">
+        <div class="column">
+          <span class="menu-title gradient-diarista text-weight-bold">{{ $t('profile.address.title') }}</span>
+          <span class="menu-description text-text">{{ $t('profile.address.description') }}</span>
+        </div>
+        <q-space/>
+        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+      </div>
+
+      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm">
+        <div class="column">
+          <span class="menu-title gradient-diarista text-weight-bold">{{ $t('profile.help.title') }}</span>
+          <span class="menu-description text-text">{{ $t('profile.help.description') }}</span>
+        </div>
+        <q-space/>
+        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+      </div>
+
+      <q-separator class="q-my-sm bg-grey-3" inset />
+
+      <div class="menu-item row items-center no-wrap cursor-pointer q-py-sm">
+        <div class="column">
+          <span class="menu-title text-weight-bold text-text">{{ $t('profile.logout.title') }}</span>
+          <span class="menu-description text-text">{{ $t('profile.logout.description') }}</span>
+        </div>
+        <q-space/>
+        <q-icon name="mdi-chevron-right" color="primary" size="md" />
+      </div>
+    </div>
+  </q-page>
 </template>
 
-<style scoped>
-.mobile-placeholder {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  min-height: calc(100dvh - 240px);
-  padding: 32px 20px;
-  text-align: center;
+<script setup>
+import { ref, onMounted } from 'vue';
+import { useQuasar } from 'quasar';
+import { getUser } from 'src/api/user';
+import ProfileEditDialog from './ProfileEditDialog.vue';
+
+const $q = useQuasar();
+
+const user = ref({
+  name: '',
+  email: '',
+  phone: ''
+});
+
+const openEditProfile = () => {
+  $q.dialog({
+    component: ProfileEditDialog,
+    componentProps: {
+      userData: user.value
+    }
+  }).onOk((data) => {
+    user.value = { ...user.value, ...data };
+  });
+};
+
+onMounted(async () => {
+  try {
+    const data = await getUser();
+    user.value = data;
+  } catch (error) {
+    console.error('Erro ao carregar perfil:', error);
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.profile-card {
+  position: relative;
 }
 
-.mobile-placeholder__badge {
-  display: grid;
-  place-items: center;
-  width: 88px;
-  height: 88px;
-  border-radius: 28px;
-  margin-bottom: 20px;
-  background: linear-gradient(180deg, rgba(255, 0, 234, 0.14), rgba(107, 17, 203, 0.08));
-  color: #ff00ea;
-  font-size: 44px;
+.btn-edit-profile {
+  border-radius: 6px;
 }
 
-.mobile-placeholder__title {
-  margin: 0 0 8px;
-  font-size: 28px;
+.menu-item {
+  transition: background 0.3s;
+  &:active {
+    background: rgba(0,0,0,0.05);
+  }
+}
+
+.menu-title {
+  font-size: 18px;
+  line-height: 1.4;
+}
+
+.menu-description {
+  font-size: 14px;
+}
+
+.flex-1 {
+  flex: 1;
+}
+
+.text-dark {
+  color: #2c3e50;
+}
+
+.fonte-email-profile {
+  font-family: Inter;
+  font-weight: 500;
+  font-style: Medium;
+  font-size: 12px;
+  line-height: 100%;
+  letter-spacing: 0%;
+}
+
+.fonte-telefone-profile {
+  font-family: Inter;
+  font-weight: 500;
+  font-style: Medium;
+  font-size: 12px;
+  line-height: 100%;
+  letter-spacing: 0%;
+}
+
+.fonte-nome-profile {
+  font-family: Inter;
   font-weight: 700;
-  line-height: 1.1;
-  color: #4d4d4d;
+  font-style: Bold;
+  font-size: 18px;
+  line-height: 100%;
+  letter-spacing: 0%;
+  text-align: center;
 }
 
-.mobile-placeholder__description {
-  max-width: 280px;
-  margin: 0;
-  font-size: 16px;
-  line-height: 1.5;
-  color: #8d8d8d;
+.shadow-profile {
+  box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.1);
 }
 </style>