Просмотр исходного кода

feat: ✨ feat (contas bancarias prestador) adicionada possibilidade de ter mais de uma conta bancaria

adicionada possibilidade de ter multiplas contas bancarias no prestador, com funcao de atribuir principal, e foi removido campos de pix

fase:dev | origin:escopo
Gustavo Zanatta 23 часов назад
Родитель
Сommit
975b0348b1

+ 21 - 0
src/api/providerBankAccount.js

@@ -0,0 +1,21 @@
+import api from "src/api";
+
+export const getProviderBankAccounts = async () => {
+  const { data } = await api.get("/provider/bank-accounts");
+  return data.payload ?? [];
+};
+
+export const createProviderBankAccount = async (bankAccount) => {
+  const { data } = await api.post("/provider/bank-accounts", bankAccount);
+  return data.payload;
+};
+
+export const updateProviderBankAccount = async (id, bankAccount) => {
+  const { data } = await api.put(`/provider/bank-accounts/${id}`, bankAccount);
+  return data.payload;
+};
+
+export const deleteProviderBankAccount = async (id) => {
+  const { data } = await api.delete(`/provider/bank-accounts/${id}`);
+  return data.payload;
+};

+ 0 - 8
src/api/providerPaymentMethod.js

@@ -1,8 +0,0 @@
-import api from "src/api";
-
-export const updateProviderBankAccount = async (providerId, bankAccount) => {
-  const { data } = await api.patch(`/provider/${providerId}/bank-account`, {
-    recipient_default_bank_account: bankAccount,
-  });
-  return data.payload;
-};

+ 0 - 5
src/api/providerWithdrawal.js

@@ -5,11 +5,6 @@ export const getBalance = async () => {
   return data.payload
 }
 
-export const getWithdrawalConfiguration = async () => {
-  const { data } = await api.get('/provider/withdrawals/config')
-  return data.payload
-}
-
 export const getWithdrawalFees = async () => {
   const { data } = await api.get('/provider/withdrawals/fees')
   return data.payload

+ 494 - 0
src/components/profile/BankAccountForm.vue

@@ -0,0 +1,494 @@
+<template>
+  <q-card
+    class="q-pa-md bg-white row shadow-card q-mb-md"
+    style="border-radius: 20px;"
+    :flat="false"
+  >
+    <div class="text-center text-text q-mb-sm col-12 font14 fontbold">
+      {{ isEditing ? $t('profile.bank_data.edit_account') : $t('profile.bank_data.bank_account_title') }}
+    </div>
+
+    <div class="col-12 field-label q-mb-xs">
+      {{ $t('profile.bank_data.bank_code') }}
+    </div>
+
+    <q-select
+      ref="bankSelectRef"
+      v-model="form.bank_code"
+      behavior="menu"
+      class="q-mb-sm col-12"
+      dense
+      emit-value
+      fill-input
+      hide-dropdown-icon
+      hide-selected
+      input-class="text-text"
+      input-debounce="0"
+      map-options
+      menu-anchor="bottom left"
+      menu-self="top left"
+      options-dense
+      use-input
+      outlined
+      popup-content-class="bank-select-menu"
+      :loading="loadingBanks"
+      :menu-offset="[0, 4]"
+      :options="bankOptions"
+      :options-dark="false"
+      :placeholder="$t('profile.bank_data.bank_code_placeholder')"
+      :popup-content-style="bankPopupStyle"
+      @blur="clearInvalidBankInput"
+      @filter="filterBanks"
+      @input-value="onBankInputValue"
+      @popup-show="updateBankSelectWidth"
+    >
+      <template #append>
+        <q-icon color="grey-7" name="mdi-magnify" />
+      </template>
+
+      <template #option="scope">
+        <q-item v-bind="scope.itemProps">
+          <q-item-section
+            class="bank-select-menu__code"
+            side
+          >
+            {{ scope.opt.code }}
+          </q-item-section>
+
+          <q-item-section class="bank-select-menu__name">
+            <q-item-label class="ellipsis">
+              {{ scope.opt.name }}
+            </q-item-label>
+          </q-item-section>
+        </q-item>
+      </template>
+
+      <template #no-option>
+        <q-item>
+          <q-item-section class="text-grey">
+            {{ $t("http.errors.no_records_found") }}
+          </q-item-section>
+        </q-item>
+      </template>
+    </q-select>
+
+    <div class="row q-mb-sm q-my-auto col-12 row items-center">
+      <q-radio
+        v-model="form.type"
+        class="q-mr-lg text-text"
+        color="primary"
+        keep-color
+        val="checking"
+        :label="$t('profile.bank_data.checking')"
+      />
+
+      <q-radio
+        v-model="form.type"
+        class="text-text"
+        color="primary"
+        keep-color
+        val="savings"
+        :label="$t('profile.bank_data.savings')"
+      />
+    </div>
+
+    <div class="row col-12 q-col-gutter-sm q-mb-sm">
+      <div class="col-8">
+        <div class="field-label q-mb-xs">
+          {{ $t('profile.bank_data.agency') }}
+        </div>
+
+        <q-input
+          v-model="form.branch_number"
+          dense
+          input-class="text-text"
+          inputmode="numeric"
+          maxlength="4"
+          outlined
+          :placeholder="$t('profile.bank_data.agency_placeholder')"
+        />
+      </div>
+
+      <div class="col-4">
+        <div class="field-label q-mb-xs">
+          {{ $t('profile.bank_data.agency_digit') }}
+        </div>
+
+        <q-input
+          v-model="form.branch_check_digit"
+          dense
+          input-class="text-text"
+          maxlength="2"
+          outlined
+          :placeholder="$t('profile.bank_data.digit_placeholder')"
+        />
+      </div>
+    </div>
+
+    <div class="row col-12 q-col-gutter-sm q-mb-sm">
+      <div class="col-8">
+        <div class="field-label q-mb-xs">
+          {{ $t('profile.bank_data.account') }}
+        </div>
+
+        <q-input
+          v-model="form.account_number"
+          dense
+          input-class="text-text"
+          inputmode="numeric"
+          maxlength="13"
+          outlined
+          :placeholder="$t('profile.bank_data.account_placeholder')"
+        />
+      </div>
+
+      <div class="col-4">
+        <div class="field-label q-mb-xs">
+          {{ $t('profile.bank_data.account_digit') }}
+        </div>
+
+        <q-input
+          v-model="form.account_check_digit"
+          dense
+          input-class="text-text"
+          maxlength="2"
+          outlined
+          :placeholder="$t('profile.bank_data.digit_placeholder')"
+        />
+      </div>
+    </div>
+
+    <q-checkbox
+      v-if="canBecomePrimary"
+      v-model="form.is_primary"
+      class="col-12 q-mt-sm text-text font12"
+      color="primary"
+      dense
+      keep-color
+      :label="$t('profile.bank_data.set_as_primary')"
+    />
+
+    <q-btn
+      class="full-width q-mt-lg"
+      color="primary"
+      no-caps
+      padding="8px 16px"
+      rounded
+      unelevated
+      :disable="!canSave"
+      :label="isEditing ? $t('profile.bank_data.att_bank') : $t('profile.bank_data.save_account')"
+      :loading="saving"
+      @click="save"
+    />
+
+    <q-btn
+      class="full-width q-mt-sm"
+      color="grey-7"
+      flat
+      no-caps
+      padding="4px 16px"
+      rounded
+      :disable="saving"
+      :label="$t('profile.bank_data.cancel')"
+      @click="$emit('cancel')"
+    />
+  </q-card>
+</template>
+
+<script setup>
+import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
+import { createProviderBankAccount, updateProviderBankAccount } from 'src/api/providerBankAccount';
+import { getBanks } from 'src/api/banks';
+import { useQuasar } from 'quasar';
+import { useI18n } from 'vue-i18n';
+
+const props = defineProps({
+  account: {
+    type: Object,
+    default: null,
+  },
+});
+
+const emit = defineEmits(['saved', 'cancel']);
+
+const { t } = useI18n();
+const $q = useQuasar();
+
+const banks = ref([]);
+const bankOptions = ref([]);
+const bankSelectRef = ref(null);
+const bankSelectWidth = ref(null);
+const loadingBanks = ref(false);
+const saving = ref(false);
+
+const isEditing = computed(() => !!props.account?.id);
+
+const form = reactive({
+  bank_code: '',
+  type: 'checking',
+  branch_number: '',
+  branch_check_digit: '',
+  account_number: '',
+  account_check_digit: '',
+  is_primary: false,
+});
+
+const canBecomePrimary = computed(() => isEditing.value && !props.account?.is_primary);
+
+const bankPopupStyle = computed(() => ({
+  maxHeight: '188px',
+  overflowY: 'auto',
+  width: bankSelectWidth.value ? `${bankSelectWidth.value}px` : undefined,
+  maxWidth: bankSelectWidth.value ? `${bankSelectWidth.value}px` : undefined,
+}));
+
+const isSelectedBankValid = (code) => {
+  if (!code) {
+    return false;
+  }
+
+  return banks.value.some((bank) => getBankCode(bank) === code);
+};
+
+const canSave = computed(
+  () =>
+    isSelectedBankValid(form.bank_code) &&
+    !!form.branch_number &&
+    !!form.account_number &&
+    !!form.account_check_digit,
+);
+
+const getBankCode = (bank) => {
+  const code = bank?.code ?? bank?.bank_code ?? bank?.number ?? bank?.value ?? '';
+  return String(code).padStart(3, '0');
+};
+
+const getBankName = (bank) => bank?.name ?? bank?.label ?? bank?.title ?? '';
+
+const getBankList = (response) => {
+  if (Array.isArray(response)) {
+    return response;
+  }
+
+  if (Array.isArray(response?.data)) {
+    return response.data;
+  }
+
+  if (Array.isArray(response?.payload)) {
+    return response.payload;
+  }
+
+  if (response && typeof response === 'object') {
+    return Object.entries(response).map(([code, name]) => ({
+      code,
+      name,
+    }));
+  }
+
+  return [];
+};
+
+const filterBanks = (val, update) => {
+  update(() => {
+    const needle = normalizeText(val).trim();
+
+    if (!needle) {
+      setBankOptions(banks.value);
+      return;
+    }
+
+    const filtered = banks.value.filter((bank) => {
+      const code = getBankCode(bank);
+      const name = normalizeText(getBankName(bank));
+
+      return code.includes(needle) || name.includes(needle);
+    });
+
+    setBankOptions(filtered);
+  });
+};
+
+const formatBankOption = (bank) => {
+  const code = getBankCode(bank);
+  const name = getBankName(bank);
+
+  return {
+    code,
+    name,
+    label: [code, name].filter(Boolean).join(' '),
+    value: code,
+  };
+};
+
+const bankLabel = (code) => {
+  const bank = banks.value.find((item) => getBankCode(item) === code);
+
+  return bank ? [getBankCode(bank), getBankName(bank)].filter(Boolean).join(' ') : '';
+};
+
+const showSelectedBankLabel = () => {
+  bankSelectRef.value?.updateInputValue(bankLabel(form.bank_code), true);
+};
+
+const normalizeText = (text) =>
+  String(text ?? '')
+    .normalize('NFD')
+    .replace(/[\u0300-\u036f]/g, '')
+    .toLowerCase();
+
+const setBankOptions = (list) => {
+  bankOptions.value = list.map(formatBankOption).filter((bank) => bank.code);
+};
+
+const updateBankSelectWidth = async () => {
+  await nextTick();
+
+  bankSelectWidth.value = bankSelectRef.value?.$el?.getBoundingClientRect()?.width ?? null;
+};
+
+const clearInvalidBankInput = () => {
+  if (!isSelectedBankValid(form.bank_code)) {
+    form.bank_code = '';
+
+    bankSelectRef.value?.updateInputValue('', true);
+
+    return;
+  }
+
+  showSelectedBankLabel();
+};
+
+const onBankInputValue = (value) => {
+  const typed = String(value ?? '');
+
+  if (typed !== form.bank_code && typed !== bankLabel(form.bank_code)) {
+    form.bank_code = '';
+  }
+};
+
+const fillForm = (account) => {
+  form.bank_code = account?.bank_code ? String(account.bank_code).padStart(3, '0') : '';
+  form.type = account?.type || 'checking';
+  form.branch_number = account?.branch_number || '';
+  form.branch_check_digit = account?.branch_check_digit || '';
+  form.account_number = account?.account_number || '';
+  form.account_check_digit = account?.account_check_digit || '';
+  form.is_primary = false;
+};
+
+watch(() => props.account, async (account) => {
+  fillForm(account);
+
+  await nextTick();
+
+  showSelectedBankLabel();
+});
+
+
+const save = async () => {
+  if (!canSave.value) {
+    $q.notify({ type: 'negative', message: t('profile.bank_data.bank_fields_required') });
+
+    return;
+  }
+
+  saving.value = true;
+
+  try {
+    const payload = {
+      bank_code: form.bank_code,
+      type: form.type,
+      branch_number: form.branch_number,
+      branch_check_digit: form.branch_check_digit || null,
+      account_number: form.account_number,
+      account_check_digit: form.account_check_digit,
+    };
+
+    if (canBecomePrimary.value && form.is_primary) {
+      payload.is_primary = true;
+    }
+
+    const account = isEditing.value
+      ? await updateProviderBankAccount(props.account.id, payload)
+      : await createProviderBankAccount(payload);
+
+    emit('saved', account);
+  } catch (error) {
+    console.log(error);
+  } finally {
+    saving.value = false;
+  }
+};
+
+const loadBanks = async () => {
+  loadingBanks.value = true;
+
+  try {
+    const response = await getBanks();
+
+    banks.value = getBankList(response);
+
+    setBankOptions(banks.value);
+  } catch (error) {
+    console.log(error);
+  } finally {
+    loadingBanks.value = false;
+  }
+};
+
+onMounted(async () => {
+  fillForm(props.account);
+
+  await loadBanks();
+
+  if (form.bank_code) {
+    await nextTick();
+
+    showSelectedBankLabel();
+  }
+
+  await updateBankSelectWidth();
+
+  window.addEventListener('resize', updateBankSelectWidth);
+});
+
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', updateBankSelectWidth);
+});
+</script>
+
+<style scoped>
+.field-label {
+  color: #8C8C8C;
+  font-size: 12px;
+  line-height: 1.2;
+}
+</style>
+
+<style>
+.bank-select-menu {
+  background: #FFFFFF !important;
+  color: #1F1F1F !important;
+  border: 1px solid #E7E7E7;
+  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.14);
+  max-height: 188px !important;
+  overflow-y: auto !important;
+}
+
+.bank-select-menu .q-item {
+  min-height: 32px;
+  padding: 6px 10px;
+  color: #1F1F1F;
+}
+
+.bank-select-menu__name {
+  min-width: 0;
+}
+
+.bank-select-menu__code {
+  min-width: 36px;
+  color: #8C8C8C;
+  font-family: monospace;
+  padding-right: 8px;
+}
+</style>

+ 141 - 414
src/components/profile/ProfileBankDataDialog.vue

@@ -35,222 +35,116 @@
           </div>
 
           <div class="text-grey-7 q-mb-md font12 fontregular">
-            {{ $t('profile.bank_data.subtitle') }}
+            {{ accounts.length ? $t('profile.bank_data.subtitle_manage') : $t('profile.bank_data.subtitle') }}
           </div>
 
-          <template v-if="pixEnabled">
-            <div class="pix-warning q-pa-md q-mb-md row items-start q-col-gutter-sm">
-              <q-icon
-                class="q-mt-xs"
-                color="warning"
-                name="mdi-alert-outline"
-                size="20px"
-              />
-
-              <div class="col font12">
-                <span class="fontbold">
-                  {{ $t('profile.bank_data.warning_title') }}
-                </span>
-
-                {{ $t('profile.bank_data.warning_message') }}
-              </div>
-            </div>
+          <div
+            v-if="!showForm"
+            class="column items-center q-mb-lg cursor-pointer new-account"
+            @click="openCreateForm"
+          >
+            <q-btn
+              class="new-account-btn shadow-card"
+              color="white"
+              icon="mdi-plus"
+              round
+              size="lg"
+              text-color="primary"
+              unelevated
+            />
 
-            <q-card
-              class="q-pa-md bg-white shadow-card q-mb-md"
-              style="border-radius: 20px;"
-              :flat="false"
-            >
-              <div class="text-center text-text q-mb-sm font14 fontbold">
-                <div>
-                  {{ $t('profile.bank_data.pix_title') }}
-
-                  <q-badge
-                    class="q-ml-xs"
-                    color="grey-5"
-                    :label="$t('profile.bank_data.in_development')"
-                  />
-                </div>
-              </div>
+            <span class="font12 fontregular text-text q-mt-sm">
+              {{ $t('profile.bank_data.new_account') }}
+            </span>
+          </div>
 
-              <q-input
-                class="q-mb-sm"
-                dense
-                disable
-                input-class="text-text"
-                outlined
-                :placeholder="$t('profile.bank_data.pix_key_placeholder')"
-              />
+          <BankAccountForm
+            v-if="showForm"
+            :account="editingAccount"
+            @cancel="closeForm"
+            @saved="onSaved"
+          />
 
-              <q-btn
-                class="full-width"
-                color="grey-4"
-                disable
-                no-caps
-                padding="4px 12px"
-                rounded
-                text-color="grey-7"
-                unelevated
-                :label="$t('profile.bank_data.in_development')"
-              />
-            </q-card>
-          </template>
+          <div
+            v-if="loading"
+            class="row justify-center q-py-lg"
+          >
+            <q-spinner color="primary" size="32px" />
+          </div>
 
           <q-card
-            class="q-pa-md bg-white row shadow-card q-mb-md"
+            v-for="account in accounts"
+            :key="account.id"
+            class="q-pa-md bg-white shadow-card q-mb-md row items-center no-wrap account-card"
+            :class="{ 'account-card--primary': account.is_primary }"
             style="border-radius: 20px;"
             :flat="false"
           >
-            <div class="text-center text-text q-mb-sm col-12 font14 fontbold">
-              {{ $t('profile.bank_data.bank_account_title') }}
-            </div>
-
-            <q-select
-              ref="bankSelectRef"
-              v-model="bankForm.bank"
-              behavior="menu"
-              class="q-mb-sm col-12"
-              dense
-              emit-value
-              fill-input
-              hide-dropdown-icon
-              hide-selected
-              input-class="text-text"
-              input-debounce="0"
-              map-options
-              menu-anchor="bottom left"
-              menu-self="top left"
-              options-dense
-              use-input
-              outlined
-              popup-content-class="bank-select-menu"
-              stack-label
-              :label="$t('profile.bank_data.bank_code')"
-              :loading="loadingBanks"
-              :menu-offset="[0, 4]"
-              :options="bankOptions"
-              :options-dark="false"
-              :placeholder="$t('profile.bank_data.bank_code_placeholder')"
-              :popup-content-style="bankPopupStyle"
-              @blur="clearInvalidBankInput"
-              @filter="filterBanks"
-              @input-value="onBankInputValue"
-              @popup-show="updateBankSelectWidth"
-            >
-              <template #append>
-                <q-icon color="grey-7" name="mdi-magnify" />
-              </template>
-
-              <template #option="scope">
-                <q-item v-bind="scope.itemProps">
-                  <q-item-section
-                    class="bank-select-menu__code"
-                    side
-                  >
-                    {{ scope.opt.code }}
-                  </q-item-section>
-
-                  <q-item-section class="bank-select-menu__name">
-                    <q-item-label class="ellipsis">
-                      {{ scope.opt.name }}
-                    </q-item-label>
-                  </q-item-section>
-                </q-item>
-              </template>
-
-              <template #no-option>
-                <q-item>
-                  <q-item-section class="text-grey">
-                    {{ $t("http.errors.no_records_found") }}
-                  </q-item-section>
-                </q-item>
-              </template>
-            </q-select>
-
-            <div class="row q-mb-sm q-my-auto col-12 row items-center">
-              <q-radio
-                v-model="bankForm.type"
-                class="q-mr-lg text-text"
-                color="primary"
-                keep-color
-                val="checking"
-                :label="$t('profile.bank_data.checking')"
-              />
+            <div class="relative-position q-mr-md">
+              <q-avatar
+                class="bg-white shadow-card"
+                size="44px"
+                text-color="primary"
+              >
+                <q-icon name="mdi-currency-usd" />
+              </q-avatar>
 
-              <q-radio
-                v-model="bankForm.type"
-                class="text-text"
+              <q-icon
+                v-if="account.is_primary"
+                class="account-card__check"
                 color="primary"
-                keep-color
-                val="savings"
-                :label="$t('profile.bank_data.savings')"
+                name="mdi-check-circle"
+                size="16px"
               />
             </div>
 
-            <div class="row col-12 q-col-gutter-sm q-mb-sm">
-              <q-input
-                v-model="bankForm.branch_number"
-                class="col-8"
-                dense
-                input-class="text-text"
-                inputmode="numeric"
-                outlined
-                stack-label
-                :label="$t('profile.bank_data.agency')"
-                :placeholder="$t('profile.bank_data.agency_placeholder')"
-              />
+            <div class="col">
+              <div class="font14 fontbold text-text">
+                {{ account.bank_name || account.bank_code }}
+              </div>
 
-              <q-input
-                v-model="bankForm.branch_check_digit"
-                class="col-4"
-                dense
-                input-class="text-text"
-                maxlength="2"
-                outlined
-                stack-label
-                :label="$t('profile.bank_data.agency_digit')"
-                :placeholder="$t('profile.bank_data.digit_placeholder')"
+              <div class="font12 fontregular text-grey-7">
+                {{ account.holder_name }}
+              </div>
+
+              <div class="font12 fontregular text-grey-7">
+                {{ $t('profile.bank_data.agency') }} {{ formatBranch(account) }}
+              </div>
+
+              <div class="font12 fontregular text-grey-7">
+                {{ $t('profile.bank_data.account') }} {{ formatAccount(account) }}
+              </div>
+
+              <q-badge
+                v-if="account.is_primary"
+                class="q-mt-xs"
+                color="primary"
+                :label="$t('profile.bank_data.primary_badge')"
               />
             </div>
 
-            <div class="row col-12 q-col-gutter-sm q-mb-sm">
-              <q-input
-                v-model="bankForm.account_number"
-                class="col-8"
+            <div class="column items-center">
+              <q-btn
+                color="grey-7"
                 dense
-                input-class="text-text"
-                inputmode="numeric"
-                outlined
-                stack-label
-                :label="$t('profile.bank_data.account')"
-                :placeholder="$t('profile.bank_data.account_placeholder')"
+                flat
+                icon="mdi-pencil-outline"
+                round
+                size="sm"
+                @click.stop="openEditForm(account)"
               />
 
-              <q-input
-                v-model="bankForm.account_check_digit"
-                class="col-4"
+              <q-btn
+                v-if="canDelete(account)"
+                color="grey-7"
                 dense
-                input-class="text-text"
-                maxlength="2"
-                outlined
-                stack-label
-                :label="$t('profile.bank_data.account_digit')"
-                :placeholder="$t('profile.bank_data.digit_placeholder')"
+                flat
+                icon="mdi-trash-can-outline"
+                round
+                size="sm"
+                @click.stop="confirmDelete(account)"
               />
             </div>
-
-            <q-btn
-              class="full-width q-mt-xl"
-              color="primary"
-              no-caps
-              padding="8px 16px"
-              rounded
-              unelevated
-              :disable="!canSaveBank"
-              :label="bankAlreadyRegistered ? $t('profile.bank_data.att_bank') : $t('profile.bank_data.save_account')"
-              :loading="savingBank"
-              @click="saveBank"
-            />
           </q-card>
         </div>
 
@@ -261,10 +155,9 @@
 </template>
 
 <script setup>
-import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
-import { getBanks } from 'src/api/banks';
-import { getWithdrawalConfiguration } from 'src/api/providerWithdrawal';
-import { updateProviderBankAccount } from 'src/api/providerPaymentMethod';
+import BankAccountForm from 'src/components/profile/BankAccountForm.vue';
+import { onMounted, ref } from 'vue';
+import { deleteProviderBankAccount, getProviderBankAccounts } from 'src/api/providerBankAccount';
 import { useDialogPluginComponent, useQuasar } from 'quasar';
 import { useI18n } from 'vue-i18n';
 import { userStore } from 'src/stores/user';
@@ -276,269 +169,103 @@ const { t } = useI18n();
 const $q = useQuasar();
 const user = userStore();
 
-const banks = ref([]);
-const bankAlreadyRegistered = ref(false);
-const bankOptions = ref([]);
-const bankSelectRef = ref(null);
-const bankSelectWidth = ref(null);
-const loadingBanks = ref(false);
-const pixEnabled = ref(false);
-const savingBank = ref(false);
-
-const bankForm = reactive({
-  bank: '',
-  type: 'checking',
-  branch_number: '',
-  branch_check_digit: '',
-  account_number: '',
-  account_check_digit: '',
-});
-
-const bankPopupStyle = computed(() => ({
-  maxHeight: '188px',
-  overflowY: 'auto',
-  width: bankSelectWidth.value ? `${bankSelectWidth.value}px` : undefined,
-  maxWidth: bankSelectWidth.value ? `${bankSelectWidth.value}px` : undefined,
-}));
-
-const providerId = computed(() => user.user?.provider?.id ?? user.user?.provider_id);
-
-const isSelectedBankValid = (code) => {
-  if (!code) {
-    return false;
-  }
-
-  return banks.value.some((bank) => getBankCode(bank) === code);
-};
+const accounts = ref([]);
+const editingAccount = ref(null);
+const loading = ref(false);
+const showForm = ref(false);
 
-const canSaveBank = computed(
-  () =>
-    !!providerId.value &&
-    isSelectedBankValid(bankForm.bank) &&
-    !!bankForm.branch_number &&
-    !!bankForm.account_number &&
-    !!bankForm.account_check_digit,
-);
-
-const getBankCode = (bank) => {
-  const code = bank?.code ?? bank?.bank_code ?? bank?.number ?? bank?.value ?? '';
-  return String(code).padStart(3, '0');
-};
+const canDelete = (account) => !account.is_primary && accounts.value.length > 1;
 
-const getBankName = (bank) => bank?.name ?? bank?.label ?? bank?.title ?? '';
+const formatBranch = (account) =>
+  [account.branch_number, account.branch_check_digit].filter(Boolean).join('-');
 
-const getBankList = (response) => {
-  if (Array.isArray(response)) {
-    return response;
-  }
+const formatAccount = (account) =>
+  [account.account_number, account.account_check_digit].filter(Boolean).join('-');
 
-  if (Array.isArray(response?.data)) {
-    return response.data;
-  }
-
-  if (Array.isArray(response?.payload)) {
-    return response.payload;
-  }
-
-  if (response && typeof response === 'object') {
-    return Object.entries(response).map(([code, name]) => ({
-      code,
-      name,
-    }));
-  }
-
-  return [];
-};
-
-//
-
-const filterBanks = (val, update) => {
-  update(() => {
-    const needle = normalizeText(val).trim();
-
-    if (!needle) {
-      setBankOptions(banks.value);
-      return;
-    }
-
-    const filtered = banks.value.filter((bank) => {
-      const code = getBankCode(bank);
-      const name = normalizeText(getBankName(bank));
-
-      return code.includes(needle) || name.includes(needle);
-    });
-
-    setBankOptions(filtered);
-  });
-};
-
-const formatBankOption = (bank) => {
-  const code = getBankCode(bank);
-  const name = getBankName(bank);
-
-  return {
-    code,
-    name,
-    label: code,
-    value: code,
-  };
+const openCreateForm = () => {
+  editingAccount.value = null;
+  showForm.value = true;
 };
 
-const normalizeText = (text) =>
-  String(text ?? '')
-    .normalize('NFD')
-    .replace(/[\u0300-\u036f]/g, '')
-    .toLowerCase();
-
-const setBankOptions = (list) => {
-  bankOptions.value = list.map(formatBankOption).filter((bank) => bank.code);
+const openEditForm = (account) => {
+  editingAccount.value = account;
+  showForm.value = true;
 };
 
-const updateBankSelectWidth = async () => {
-  await nextTick();
-
-  bankSelectWidth.value = bankSelectRef.value?.$el?.getBoundingClientRect()?.width ?? null;
+const closeForm = () => {
+  editingAccount.value = null;
+  showForm.value = false;
 };
 
-//
+const onSaved = async () => {
+  closeForm();
 
-const clearInvalidBankInput = () => {
-  if (!isSelectedBankValid(bankForm.bank)) {
-    bankForm.bank = '';
+  await loadAccounts();
 
-    bankSelectRef.value?.updateInputValue('', true);
-  }
+  await user.fetchUser();
 };
 
-const onBankInputValue = (value) => {
-  if (String(value ?? '') !== bankForm.bank) {
-    bankForm.bank = '';
-  }
+const confirmDelete = (account) => {
+  $q.dialog({
+    title: t('profile.bank_data.delete_title'),
+    message: t('profile.bank_data.delete_message'),
+    cancel: { flat: true, label: t('profile.bank_data.cancel'), noCaps: true },
+    ok: { color: 'negative', label: t('profile.bank_data.confirm'), noCaps: true, rounded: true, unelevated: true },
+    persistent: true,
+  }).onOk(() => removeAccount(account));
 };
 
-const saveBank = async () => {
-  if (!canSaveBank.value) {
-    $q.notify({ type: 'negative', message: t('profile.bank_data.bank_fields_required') });
-
-    return;
-  }
-
-  savingBank.value = true;
+const removeAccount = async (account) => {
+  loading.value = true;
 
   try {
-    // Holder (nome/CPF) é derivado do prestador no backend; aqui só os campos digitados.
-    const bankAccount = {
-      bank: bankForm.bank,
-      type: bankForm.type,
-      branch_number: bankForm.branch_number,
-      branch_check_digit: bankForm.branch_check_digit || null,
-      account_number: bankForm.account_number,
-      account_check_digit: bankForm.account_check_digit,
-    };
-
-    await updateProviderBankAccount(providerId.value, bankAccount);
+    await deleteProviderBankAccount(account.id);
 
-    await user.fetchUser();
-
-    bankAlreadyRegistered.value = true;
+    await loadAccounts();
 
-    $q.notify({ type: 'positive', message: t('profile.bank_data.account_saved') });
+    await user.fetchUser();
   } catch (error) {
-    $q.notify({ type: 'negative', message: t('profile.bank_data.save_error') });
-
     console.log(error);
   } finally {
-    savingBank.value = false;
+    loading.value = false;
   }
 };
 
 //
 
-const loadBanks = async () => {
-  loadingBanks.value = true;
+const loadAccounts = async () => {
+  loading.value = true;
 
   try {
-    const response = await getBanks();
-
-    banks.value = getBankList(response);
-
-    setBankOptions(banks.value);
+    accounts.value = await getProviderBankAccounts();
   } catch (error) {
     console.log(error);
   } finally {
-    loadingBanks.value = false;
+    loading.value = false;
   }
 };
 
-const loadWithdrawalConfiguration = async () => {
-  try {
-    const configuration = await getWithdrawalConfiguration();
-
-    pixEnabled.value = configuration?.pix_enabled === true;
-  } catch (error) {
-    pixEnabled.value = false;
-    console.log(error);
-  }
-};
-
-//
-
-onMounted(async () => {
-  const bank = user.user?.provider?.recipient_default_bank_account;
-
-  if (bank && Object.keys(bank).length) {
-    bankForm.bank = bank.bank ? String(bank.bank).padStart(3, '0') : '';
-    bankForm.type = bank.type || 'checking';
-    bankForm.branch_number = bank.branch_number || '';
-    bankForm.branch_check_digit = bank.branch_check_digit || '';
-    bankForm.account_number = bank.account_number || '';
-    bankForm.account_check_digit = bank.account_check_digit || '';
-    bankAlreadyRegistered.value = true;
-  }
-
-  await Promise.all([loadBanks(), loadWithdrawalConfiguration()]);
-
-  await updateBankSelectWidth();
-
-  window.addEventListener('resize', updateBankSelectWidth);
-});
-
-onBeforeUnmount(() => {
-  window.removeEventListener('resize', updateBankSelectWidth);
-});
+onMounted(loadAccounts);
 </script>
 
 <style scoped>
-.pix-warning {
-  background-color: #EEF0FF;
-  border-radius: 12px;
-}
-</style>
-
-<style>
-.bank-select-menu {
-  background: #FFFFFF !important;
-  color: #1F1F1F !important;
-  border: 1px solid #E7E7E7;
-  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.14);
-  max-height: 188px !important;
-  overflow-y: auto !important;
+.account-card--primary {
+  border: 1px solid var(--q-primary);
 }
 
-.bank-select-menu .q-item {
-  min-height: 32px;
-  padding: 6px 10px;
-  color: #1F1F1F;
+.account-card__check {
+  position: absolute;
+  top: -2px;
+  right: -2px;
+  background: #FFFFFF;
+  border-radius: 50%;
 }
 
-.bank-select-menu__name {
-  min-width: 0;
+.new-account {
+  width: fit-content;
 }
 
-.bank-select-menu__code {
-  min-width: 36px;
-  color: #8C8C8C;
-  font-family: monospace;
-  padding-right: 8px;
+.new-account-btn {
+  border-radius: 50%;
 }
 </style>

+ 14 - 19
src/i18n/locales/en.json

@@ -529,42 +529,37 @@
     "placeholder_phone": "Enter your phone",
     "bank_data": {
       "title": "Bank data",
-      "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",
-      "att_pix": "Update pix",
+      "description": "Accounts to receive your payments",
+      "my_bank_data_title": "Bank account",
+      "subtitle": "Register a bank account, under your CPF, to receive your payments.",
       "att_bank": "Update account",
-      "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",
       "agency_placeholder": "Agency",
       "account": "Account",
       "account_placeholder": "Account",
-      "digit": "Digit",
       "digit_placeholder": "Digit",
       "bank_code": "Bank (code)",
       "bank_code_placeholder": "Enter the bank name or code",
       "agency_digit": "Digit",
       "account_digit": "Digit",
-      "in_development": "Under development",
       "bank_fields_required": "Please enter bank, agency and account",
       "save_account": "register account",
-      "account_saved": "Bank account saved successfully!",
-      "save_error": "Error saving. Please try again.",
       "missing_alert_title": "Register your bank data",
       "missing_alert_message": "You need to register your bank data to appear to platform clients.",
       "missing_alert_action": "Register bank data",
-      "missing_alert_later": "Not now"
+      "missing_alert_later": "Not now",
+      "subtitle_manage": "Manage your registered accounts or add a new one to receive your payments.",
+      "new_account": "New account",
+      "edit_account": "Edit account",
+      "primary_badge": "Primary account",
+      "delete_title": "Delete account",
+      "delete_message": "Do you want to delete this bank account?",
+      "confirm": "Confirm",
+      "cancel": "Cancel",
+      "set_as_primary": "Make this the primary account"
     },
     "availability": {
       "title": "Availability",
@@ -910,4 +905,4 @@
       "user_message_header": "--- User message ---"
     }
   }
-}
+}

+ 14 - 19
src/i18n/locales/es.json

@@ -529,42 +529,37 @@
     "placeholder_phone": "Escriba su teléfono",
     "bank_data": {
       "title": "Datos bancarios",
-      "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",
-      "att_pix": "Actualizar pix",
+      "description": "Cuentas para recibir tus pagos",
+      "my_bank_data_title": "Cuenta bancaria",
+      "subtitle": "Registra una cuenta bancaria, con tu CPF, para recibir tus pagos.",
       "att_bank": "Actualizar cuenta",
-      "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",
       "agency_placeholder": "Agencia",
       "account": "Cuenta",
       "account_placeholder": "Cuenta",
-      "digit": "Dígito",
       "digit_placeholder": "Dígito",
       "bank_code": "Banco (código)",
       "bank_code_placeholder": "Ingrese el nombre o código del banco",
       "agency_digit": "Dígito",
       "account_digit": "Dígito",
-      "in_development": "En desarrollo",
       "bank_fields_required": "Por favor ingrese banco, agencia y cuenta",
       "save_account": "registrar cuenta",
-      "account_saved": "¡Cuenta bancaria guardada con éxito!",
-      "save_error": "Error al guardar. Inténtelo de nuevo.",
       "missing_alert_title": "Registre sus datos bancarios",
       "missing_alert_message": "Debe registrar sus datos bancarios para aparecer ante los clientes de la plataforma.",
       "missing_alert_action": "Registrar datos bancarios",
-      "missing_alert_later": "Ahora no"
+      "missing_alert_later": "Ahora no",
+      "subtitle_manage": "Gestiona las cuentas registradas o registra una nueva para recibir tus pagos.",
+      "new_account": "Nueva cuenta",
+      "edit_account": "Editar cuenta",
+      "primary_badge": "Cuenta principal",
+      "delete_title": "Eliminar cuenta",
+      "delete_message": "¿Deseas eliminar esta cuenta bancaria?",
+      "confirm": "Confirmar",
+      "cancel": "Cancelar",
+      "set_as_primary": "Convertir en cuenta principal"
     },
     "availability": {
       "title": "Disponibilidad",
@@ -910,4 +905,4 @@
       "user_message_header": "--- Mensaje del usuario ---"
     }
   }
-}
+}

+ 14 - 19
src/i18n/locales/pt.json

@@ -532,42 +532,37 @@
     "placeholder_phone": "Digite seu telefone",
     "bank_data": {
       "title": "Dados bancários",
-      "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",
-      "att_pix": "Atualizar pix",
+      "description": "Contas para receber seus pagamentos",
+      "my_bank_data_title": "Conta bancária",
+      "subtitle": "Cadastre uma conta bancária, em seu CPF, para receber seus pagamentos.",
       "att_bank": "Atualizar conta",
-      "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",
       "agency_placeholder": "Agência",
       "account": "Conta",
       "account_placeholder": "Conta",
-      "digit": "Dígito",
       "digit_placeholder": "Dígito",
       "bank_code": "Banco (código)",
       "bank_code_placeholder": "Digite o nome ou código do banco",
       "agency_digit": "Dígito",
       "account_digit": "Dígito",
-      "in_development": "Em desenvolvimento",
       "bank_fields_required": "Informe banco, agência e conta",
       "save_account": "cadastrar conta",
-      "account_saved": "Conta bancária salva com sucesso!",
-      "save_error": "Erro ao salvar. Tente novamente.",
       "missing_alert_title": "Cadastre seus dados bancários",
       "missing_alert_message": "Você precisa cadastrar seus dados bancários para aparecer para os clientes da plataforma.",
       "missing_alert_action": "Cadastrar dados bancários",
-      "missing_alert_later": "Agora não"
+      "missing_alert_later": "Agora não",
+      "subtitle_manage": "Gerencie as contas cadastradas ou cadastre uma nova conta para receber seus pagamentos.",
+      "new_account": "Nova conta",
+      "edit_account": "Editar conta",
+      "primary_badge": "Conta principal",
+      "delete_title": "Excluir conta",
+      "delete_message": "Deseja excluir esta conta bancária?",
+      "confirm": "Confirmar",
+      "cancel": "Cancelar",
+      "set_as_primary": "Tornar esta a conta principal"
     },
     "availability": {
       "title": "Disponibilidade",
@@ -913,4 +908,4 @@
       "user_message_header": "--- Mensagem do usuário ---"
     }
   }
-}
+}

+ 1 - 16
src/layouts/MainLayout.vue

@@ -131,22 +131,7 @@ const checkMissingBankData = async () => {
   openMissingBankDataAlert();
 };
 
-const hasCompleteBankAccount = () => {
-  const provider = user.user?.provider;
-
-  const bankAccount = provider?.recipient_default_bank_account;
-
-  if (!provider || !bankAccount || !Object.keys(bankAccount).length) {
-    return false;
-  }
-
-  return Boolean(
-    bankAccount.bank
-    && bankAccount.branch_number
-    && bankAccount.account_number
-    && bankAccount.account_check_digit,
-  );
-};
+const hasCompleteBankAccount = () => Boolean(user.user?.provider?.has_primary_bank_account);
 
 const isNavItemActive = (item) => route.name === item.name;
 

+ 0 - 6
src/pages/LoginPage.vue

@@ -239,7 +239,6 @@ const createInitialStepSixForm = () => ({
   bank: "",
   branch_check_digit: "",
   branch_number: "",
-  pix_key: "",
   working_days: {},
 });
 
@@ -537,11 +536,6 @@ const registerUserAndProvider = async () => {
       stepSixForm.value.account_check_digit,
     );
 
-    appendIfFilled(
-      "recipient_default_bank_account[pix_key]",
-      stepSixForm.value.pix_key,
-    );
-
     append(
       "recipient_default_bank_account[holder_name]",
       stepThreeForm.value.name,