فهرست منبع

feat: :sparkles: perfil prestador dados bancarios

perfil prestador dados bancarios
Gustavo Zanatta 1 ماه پیش
والد
کامیت
683b480b3e

+ 21 - 0
src/api/providerPaymentMethod.js

@@ -0,0 +1,21 @@
+import api from "src/api";
+
+export const getProviderPaymentMethods = async (providerId) => {
+  const { data } = await api.get(`/provider/payment-methods/${providerId}`);
+  return data.payload;
+};
+
+export const createProviderPaymentMethod = async (info) => {
+  const { data } = await api.post("/provider/payment-method", info);
+  return data.payload;
+};
+
+export const updateProviderPaymentMethod = async (id, info) => {
+  const { data } = await api.put(`/provider/payment-method/${id}`, info);
+  return data.payload;
+};
+
+export const deleteProviderPaymentMethod = async (id) => {
+  const { data } = await api.delete(`/provider/payment-method/${id}`);
+  return data.payload;
+};

+ 275 - 0
src/components/profile/ProfileBankDataDialog.vue

@@ -0,0 +1,275 @@
+<template>
+  <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
+    <div class="bg-page full-height no-shadow">
+      <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile bg-surface">
+        <q-btn v-close-popup icon="mdi-chevron-left" flat round dense color="primary" />
+        <q-space />
+        <span class="text-subtitle1 text-weight-bold text-primary">{{ $t('profile.bank_data.title') }}</span>
+        <q-space />
+        <div style="width: 32px"></div>
+      </div>
+
+      <q-card-section class="col">
+        <div class="q-px-md q-pt-lg text-text">
+          <div class="text-h6 text-weight-bold gradient-diarista">{{ $t('profile.bank_data.my_bank_data_title') }}</div>
+          <div class="text-caption text-grey-7 q-mb-md">{{ $t('profile.bank_data.subtitle') }}</div>
+
+          <div class="pix-warning q-pa-md q-mb-lg row items-start q-col-gutter-sm">
+            <q-icon name="mdi-alert-outline" color="warning" size="20px" class="q-mt-xs" />
+            <div class="col text-caption">
+              <span class="text-weight-bold">{{ $t('profile.bank_data.warning_title') }}</span>
+              {{ $t('profile.bank_data.warning_message') }}
+            </div>
+          </div>
+
+          <div class="text-subtitle1 text-weight-bold text-center text-text q-mb-md">
+            {{ $t('profile.bank_data.pix_title') }}
+          </div>
+
+          <q-card class="q-pa-lg bg-white shadow-card q-mb-lg" style="border-radius: 25px;" :flat="false">
+            <div class="text-weight-bold q-mb-xs text-text">{{ $t('profile.bank_data.pix_key') }}</div>
+            <q-input
+              v-model="pixForm.pix_key"
+              outlined
+              dense
+              input-class="text-text"
+              :placeholder="$t('profile.bank_data.pix_key_placeholder')"
+              class="q-mb-md"
+            />
+
+            <q-btn
+              unelevated
+              rounded
+              no-caps
+              color="primary"
+              class="full-width q-py-md text-weight-bold"
+              style="font-size: 1.1rem;"
+              :label="$t('profile.bank_data.save_pix')"
+              :loading="savingPix"
+              :disable="!hasPixUpdatedFields"
+              @click="savePix"
+            />
+          </q-card>
+
+          <div class="text-subtitle1 text-weight-bold text-center text-text q-mb-md">
+            {{ $t('profile.bank_data.bank_account_title') }}
+          </div>
+
+          <q-card class="q-pa-lg bg-white shadow-card q-mb-lg" style="border-radius: 25px;" :flat="false">
+            <div class="text-weight-bold q-mb-sm text-text">{{ $t('profile.bank_data.account_type') }}</div>
+            <div class="row q-mb-md">
+              <q-radio
+                v-model="bankForm.bank_account_type"
+                val="checking"
+                :label="$t('profile.bank_data.checking')"
+                color="primary"
+                class="q-mr-lg text-text"
+              />
+              <q-radio
+                v-model="bankForm.bank_account_type"
+                val="savings"
+                :label="$t('profile.bank_data.savings')"
+                color="primary"
+                class="text-text"
+              />
+            </div>
+
+            <div class="row q-col-gutter-sm q-mb-sm">
+              <div class="col-5">
+                <div class="text-weight-bold q-mb-xs text-text">{{ $t('profile.bank_data.agency') }}</div>
+                <q-input
+                  v-model="bankForm.agency"
+                  outlined
+                  dense
+                  input-class="text-text"
+                  placeholder="0001"
+                />
+              </div>
+              <div class="col-7">
+                <div class="text-weight-bold q-mb-xs text-text">{{ $t('profile.bank_data.account') }}</div>
+                <q-input
+                  v-model="bankForm.account"
+                  outlined
+                  dense
+                  input-class="text-text"
+                  placeholder="Ex: 0000000-0"
+                />
+              </div>
+            </div>
+
+            <div class="row q-mb-md">
+              <div class="col-4">
+                <div class="text-weight-bold q-mb-xs text-text">{{ $t('profile.bank_data.digit') }}</div>
+                <q-input
+                  v-model="bankForm.digit"
+                  outlined
+                  dense
+                  input-class="text-text"
+                  :placeholder="$t('profile.bank_data.digit')"
+                />
+              </div>
+            </div>
+
+            <q-btn
+              unelevated
+              rounded
+              no-caps
+              color="primary"
+              class="full-width q-py-md text-weight-bold"
+              style="font-size: 1.1rem;"
+              :label="$t('profile.bank_data.save_account')"
+              :loading="savingBank"
+              :disable="!hasBankUpdatedFields"
+              @click="saveBank"
+            />
+          </q-card>
+        </div>
+        <div class="q-pb-xl"></div>
+      </q-card-section>
+    </div>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import { useDialogPluginComponent, useQuasar } from 'quasar';
+import {
+  getProviderPaymentMethods,
+  createProviderPaymentMethod,
+  updateProviderPaymentMethod,
+} from 'src/api/providerPaymentMethod';
+import { userStore } from 'src/stores/user';
+import { useFormUpdateTracker } from 'src/composables/useFormUpdateTracker';
+import { useI18n } from 'vue-i18n';
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef } = useDialogPluginComponent();
+const $q = useQuasar();
+const { t } = useI18n();
+const user = userStore();
+
+const pixId = ref(null);
+const bankId = ref(null);
+const savingPix = ref(false);
+const savingBank = ref(false);
+
+const {
+  form: pixForm,
+  hasUpdatedFields: hasPixUpdatedFields,
+  getUpdatedFields: getPixUpdatedFields,
+  setUpdateFormAsOriginal: setPixOriginal,
+} = useFormUpdateTracker({
+  pix_key: '',
+});
+
+const {
+  form: bankForm,
+  hasUpdatedFields: hasBankUpdatedFields,
+  getUpdatedFields: getBankUpdatedFields,
+  setUpdateFormAsOriginal: setBankOriginal,
+} = useFormUpdateTracker({
+  bank_account_type: 'checking',
+  agency: '',
+  account: '',
+  digit: '',
+});
+
+const savePix = async () => {
+  if (!pixForm.pix_key) {
+    $q.notify({ type: 'negative', message: t('profile.bank_data.pix_key_required') });
+    return;
+  }
+
+  savingPix.value = true;
+  try {
+    const payload = {
+      provider_id: user.user.id,
+      account_type: 'pix',
+      pix_key: pixForm.pix_key,
+    };
+
+    if (pixId.value) {
+      await updateProviderPaymentMethod(pixId.value, getPixUpdatedFields.value);
+    } else {
+      const created = await createProviderPaymentMethod(payload);
+      pixId.value = created.id;
+    }
+
+    setPixOriginal();
+    $q.notify({ type: 'positive', message: t('profile.bank_data.pix_saved') });
+  } catch (error) {
+    $q.notify({ type: 'negative', message: t('profile.bank_data.save_error') });
+    console.log(error);
+  } finally {
+    savingPix.value = false;
+  }
+};
+
+const saveBank = async () => {
+  if (!bankForm.agency || !bankForm.account) {
+    $q.notify({ type: 'negative', message: t('profile.bank_data.bank_fields_required') });
+    return;
+  }
+
+  savingBank.value = true;
+  try {
+    const payload = {
+      provider_id: user.user.id,
+      account_type: 'bank_account',
+      bank_account_type: bankForm.bank_account_type,
+      agency: bankForm.agency,
+      account: bankForm.account,
+      digit: bankForm.digit,
+    };
+
+    if (bankId.value) {
+      await updateProviderPaymentMethod(bankId.value, getBankUpdatedFields.value);
+    } else {
+      const created = await createProviderPaymentMethod(payload);
+      bankId.value = created.id;
+    }
+
+    setBankOriginal();
+    $q.notify({ type: 'positive', message: t('profile.bank_data.account_saved') });
+  } catch (error) {
+    $q.notify({ type: 'negative', message: t('profile.bank_data.save_error') });
+    console.log(error);
+  } finally {
+    savingBank.value = false;
+  }
+};
+
+onMounted(async () => {
+  try {
+    const methods = await getProviderPaymentMethods(user.user.id);
+    if (!methods) return;
+
+    const pix = methods.find((m) => m.account_type === 'pix');
+    if (pix) {
+      pixId.value = pix.id;
+      pixForm.pix_key = pix.pix_key || '';
+      setPixOriginal();
+    }
+
+    const bank = methods.find((m) => m.account_type === 'bank_account');
+    if (bank) {
+      bankId.value = bank.id;
+      bankForm.bank_account_type = bank.bank_account_type || 'checking';
+      bankForm.agency = bank.agency || '';
+      bankForm.account = bank.account || '';
+      bankForm.digit = bank.digit || '';
+      setBankOriginal();
+    }
+  } catch (error) {
+    console.error('Erro ao carregar dados bancários:', error);
+  }
+});
+</script>
+
+<style scoped>
+.pix-warning {
+  background-color: #EEF0FF;
+  border-radius: 12px;
+}
+</style>

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

@@ -278,7 +278,28 @@
     "placeholder_phone": "Enter your phone",
     "bank_data": {
       "title": "Bank data",
-      "description": "Pix, agency and account"
+      "description": "Pix, agency and account",
+      "my_bank_data_title": "My bank data",
+      "subtitle": "Register your pix key and/or account to receive payments for your services.",
+      "warning_title": "Attention!",
+      "warning_message": " The pix key must be linked to a bank account of your CPF.",
+      "pix_title": "Pix key",
+      "pix_key": "Pix key",
+      "pix_key_placeholder": "Enter your pix key",
+      "pix_key_required": "Please enter your pix key",
+      "save_pix": "register pix",
+      "pix_saved": "Pix key saved successfully!",
+      "bank_account_title": "Agency and account",
+      "account_type": "Account type",
+      "checking": "Checking",
+      "savings": "Savings",
+      "agency": "Agency",
+      "account": "Account",
+      "digit": "Digit",
+      "bank_fields_required": "Please enter agency and account",
+      "save_account": "register account",
+      "account_saved": "Bank account saved successfully!",
+      "save_error": "Error saving. Please try again."
     },
     "availability": {
       "title": "Availability",

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

@@ -278,7 +278,28 @@
     "placeholder_phone": "Escriba su teléfono",
     "bank_data": {
       "title": "Datos bancarios",
-      "description": "Pix, agencia y cuenta"
+      "description": "Pix, agencia y cuenta",
+      "my_bank_data_title": "Mis datos bancarios",
+      "subtitle": "Registre su clave pix y/o cuenta para recibir los pagos por sus servicios.",
+      "warning_title": "¡Atención!",
+      "warning_message": " La clave pix debe estar vinculada a una cuenta bancaria de su CPF.",
+      "pix_title": "Clave pix",
+      "pix_key": "Clave pix",
+      "pix_key_placeholder": "Ingrese su clave pix",
+      "pix_key_required": "Por favor ingrese su clave pix",
+      "save_pix": "registrar pix",
+      "pix_saved": "¡Clave pix guardada con éxito!",
+      "bank_account_title": "Agencia y cuenta",
+      "account_type": "Tipo de cuenta",
+      "checking": "Corriente",
+      "savings": "Ahorro",
+      "agency": "Agencia",
+      "account": "Cuenta",
+      "digit": "Dígito",
+      "bank_fields_required": "Por favor ingrese agencia y cuenta",
+      "save_account": "registrar cuenta",
+      "account_saved": "¡Cuenta bancaria guardada con éxito!",
+      "save_error": "Error al guardar. Inténtelo de nuevo."
     },
     "availability": {
       "title": "Disponibilidad",

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

@@ -278,7 +278,28 @@
     "placeholder_phone": "Digite seu telefone",
     "bank_data": {
       "title": "Dados bancários",
-      "description": "Pix, agência e conta"
+      "description": "Pix, agência e conta",
+      "my_bank_data_title": "Meus dados bancários",
+      "subtitle": "Cadastre sua chave pix e/ou conta que deseja receber os pagamentos por seus serviços.",
+      "warning_title": "Atenção!",
+      "warning_message": " A chave pix precisa estar vinculada a uma conta bancária do seu CPF.",
+      "pix_title": "Chave pix",
+      "pix_key": "Chave pix",
+      "pix_key_placeholder": "Digite sua chave pix",
+      "pix_key_required": "Informe a chave pix",
+      "save_pix": "cadastrar pix",
+      "pix_saved": "Chave pix salva com sucesso!",
+      "bank_account_title": "Agência e conta",
+      "account_type": "Tipo de conta",
+      "checking": "Corrente",
+      "savings": "Poupança",
+      "agency": "Agência",
+      "account": "Conta",
+      "digit": "Dígito",
+      "bank_fields_required": "Informe agência e conta",
+      "save_account": "cadastrar conta",
+      "account_saved": "Conta bancária salva com sucesso!",
+      "save_error": "Erro ao salvar. Tente novamente."
     },
     "availability": {
       "title": "Disponibilidade",

+ 8 - 1
src/pages/profile/ProfilePage.vue

@@ -37,7 +37,7 @@
     </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="menu-item row items-center no-wrap cursor-pointer q-py-sm" @click="openBankDataDialog">
         <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>
@@ -102,6 +102,7 @@ import { useQuasar } from 'quasar';
 import { getUser } from 'src/api/user';
 import ProfileEditDialog from './ProfileEditDialog.vue';
 import ProfileAddressDialog from 'src/components/profile/ProfileAddressDialog.vue';
+import ProfileBankDataDialog from 'src/components/profile/ProfileBankDataDialog.vue';
 
 const $q = useQuasar();
 
@@ -122,6 +123,12 @@ const openEditProfile = () => {
   });
 };
 
+const openBankDataDialog = () => {
+  $q.dialog({
+    component: ProfileBankDataDialog
+  });
+};
+
 const openAddressDialog = () => {
   $q.dialog({
     component: ProfileAddressDialog