Procházet zdrojové kódy

✨ feat(privacidade): adicionar aba de contas bloqueadas no perfil do cliente

- ProfilePrivacyDialog: lista prestadores bloqueados com avatar por iniciais, bairro, nota e botão desbloquear
- ProfilePage: item 'Privacidade' no menu de perfil
- api/clientProviderBlock: getClientProviderBlocks e deleteClientProviderBlock

Fase: dev | Origin: privacidade
Gustavo Zanatta před 4 dny
rodič
revize
63aa11480f

+ 11 - 0
src/api/clientProviderBlock.js

@@ -0,0 +1,11 @@
+import api from 'src/api'
+
+export const getClientProviderBlocks = async (clientId) => {
+  const { data } = await api.get(`/client-provider-blocks/${clientId}`)
+  return data.payload
+}
+
+export const deleteClientProviderBlock = async (id) => {
+  const { data } = await api.delete(`/client-provider-blocks/${id}`)
+  return data.payload
+}

+ 158 - 0
src/components/profile/ProfilePrivacyDialog.vue

@@ -0,0 +1,158 @@
+<template>
+  <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
+    <div class="bg-page full-height column 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.privacy.title') }}</span>
+        <q-space />
+        <div style="width: 32px"></div>
+      </div>
+
+      <div v-if="loading" class="col flex flex-center">
+        <q-spinner color="primary" size="3em" />
+      </div>
+
+      <div v-else-if="blocks.length === 0" class="col column items-center justify-center q-px-xl q-pb-xl">
+        <q-img
+          :src="diarinho"
+          style="width: 220px; height: 220px;"
+          fit="contain"
+          class="q-mb-lg"
+        />
+        <p class="text-text text-center text-weight-bold q-mb-xs" style="font-size: 15px;">
+          {{ $t('profile.privacy.empty_message') }}
+        </p>
+        <p class="text-grey-6 text-center q-mb-xl" style="font-size: 13px;">
+          {{ $t('profile.privacy.empty_sub') }}
+        </p>
+      </div>
+
+      <div v-else class="col overflow-auto q-pb-xl">
+        <div class="q-px-md q-mt-md">
+          <p class="text-weight-bold text-primary q-mb-md blocked-title">
+            {{ $t('profile.privacy.blocked_title') }}
+          </p>
+
+          <div
+            v-for="block in blocks"
+            :key="block.id"
+            class="block-item row items-center no-wrap q-mb-md"
+          >
+            <q-avatar size="48px" class="flex-shrink-0 q-mr-sm">
+              <span
+                :style="avatarStyle(block)"
+                class="text-weight-bold full-width full-height flex flex-center"
+                style="font-size: 16px; border-radius: 50%;"
+              >
+                {{ block.provider_name?.slice(0, 2).toUpperCase() ?? '??' }}
+              </span>
+            </q-avatar>
+
+            <div class="col column no-wrap overflow-hidden">
+              <span class="text-weight-bold text-text" style="font-size: 14px; line-height: 1.3;">
+                {{ block.provider_name }}
+              </span>
+              <span v-if="block.provider_district" class="text-grey-6" style="font-size: 12px;">
+                {{ block.provider_district }}
+              </span>
+              <div v-if="block.provider_rating" class="row items-center q-mt-xs">
+                <q-icon name="mdi-star" color="amber" size="13px" class="q-mr-xs" />
+                <span class="text-grey-7" style="font-size: 12px;">{{ Number(block.provider_rating).toFixed(1) }}</span>
+              </div>
+            </div>
+
+            <q-btn
+              outline
+              no-caps
+              rounded
+              color="primary"
+              size="sm"
+              class="flex-shrink-0 unblock-btn"
+              :loading="unblockingId === block.id"
+              :label="$t('profile.privacy.unblock_btn')"
+              @click="unblock(block)"
+            />
+          </div>
+        </div>
+      </div>
+
+    </div>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useDialogPluginComponent } from 'quasar'
+import { userStore } from 'src/stores/user'
+import { getClientProviderBlocks, deleteClientProviderBlock } from 'src/api/clientProviderBlock'
+import diarinho from 'src/assets/diarinho_perfil_cliente_favoritos.svg'
+
+defineEmits([...useDialogPluginComponent.emits])
+const { dialogRef } = useDialogPluginComponent()
+const store = userStore()
+
+const blocks = ref([])
+const loading = ref(false)
+const unblockingId = ref(null)
+
+const avatarColors = [
+  { background: '#ffd5df', color: '#932e57' },
+  { background: '#d7e8ff', color: '#2158a8' },
+  { background: '#dfd',    color: '#2a7a3b' },
+  { background: '#ffe5cc', color: '#8a4500' },
+  { background: '#ede0ff', color: '#6200ea' },
+]
+
+const avatarStyle = (block) => {
+  const idx = (block?.provider_id ?? 0) % avatarColors.length
+  return avatarColors[idx]
+}
+
+const unblock = async (block) => {
+  unblockingId.value = block.id
+  try {
+    await deleteClientProviderBlock(block.id)
+    blocks.value = blocks.value.filter(b => b.id !== block.id)
+  } finally {
+    unblockingId.value = null
+  }
+}
+
+onMounted(async () => {
+  loading.value = true
+  try {
+    blocks.value = await getClientProviderBlocks(store.user.client.id) ?? []
+  } finally {
+    loading.value = false
+  }
+})
+</script>
+
+<style scoped lang="scss">
+.shadow-profile {
+  box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.1);
+}
+
+.blocked-title {
+  font-size: 18px;
+}
+
+.block-item {
+  background: white;
+  border-radius: 12px;
+  padding: 12px;
+  box-shadow: 0 1px 6px rgba(0,0,0,0.07);
+}
+
+.unblock-btn {
+  font-size: 12px;
+  font-weight: 600;
+  padding: 4px 14px;
+}
+
+.flex-shrink-0 {
+  flex-shrink: 0;
+}
+</style>

+ 16 - 0
src/pages/profile/ProfilePage.vue

@@ -73,6 +73,15 @@
         <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" @click="openPrivacyDialog">
+        <div class="column">
+          <span class="menu-title gradient-diarista text-weight-bold">{{ $t('profile.privacy.title') }}</span>
+          <span class="menu-description text-text">{{ $t('profile.privacy.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" @click="handleLogout">
@@ -97,6 +106,7 @@ import ProfileAddressDialog from 'src/components/profile/ProfileAddressDialog.vu
 import ProfilePaymentsDialog from 'src/components/profile/ProfilePaymentsDialog.vue';
 import ProfileFavoritesDialog from 'src/components/profile/ProfileFavoritesDialog.vue';
 import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue';
+import ProfilePrivacyDialog from 'src/components/profile/ProfilePrivacyDialog.vue';
 import { useRouter } from 'vue-router';
 
 const $q = useQuasar();
@@ -144,6 +154,12 @@ const openHelpDialog = () => {
   });
 };
 
+const openPrivacyDialog = () => {
+  $q.dialog({
+    component: ProfilePrivacyDialog
+  });
+};
+
 const handleLogout = async () => {
   await logout();
   router.push('/login');