Bläddra i källkod

feat: codigo unico para favorito

Gustavo Zanatta 1 vecka sedan
förälder
incheckning
c8d7eb8135

+ 9 - 0
src/api/clientFavoriteProvider.js

@@ -19,3 +19,12 @@ export const deleteClientFavoriteProvider = async (id) => {
   const { data } = await api.delete(`/client/favorite-provider/${id}`);
   return data.payload;
 }
+
+export const createClientFavoriteProviderByCode = async (clientId, code) => {
+  const { data } = await api.post(
+    `/client/favorite-provider/by-code`,
+    { client_id: clientId, code },
+  );
+
+  return data.payload;
+};

+ 162 - 0
src/components/profile/ProfileFavoriteCodeDialog.vue

@@ -0,0 +1,162 @@
+<template>
+  <q-dialog ref="dialogRef">
+    <div class="code-dialog-wrapper column items-center">
+      <q-card class="code-card bg-white">
+        <q-card-section class="q-pt-lg q-pb-none q-px-lg text-center">
+          <p class="code-title text-primary font16 fontbold">
+            {{ $t('profile.favorites.code_title') }}
+          </p>
+
+          <p class="code-subtitle text-grey-7 font12 fontregular q-mt-sm">
+            {{ $t('profile.favorites.code_subtitle') }}
+          </p>
+        </q-card-section>
+
+        <q-card-section class="q-px-lg q-pt-md q-pb-none">
+          <q-input
+            v-model="code"
+            autofocus
+            class="code-input"
+            input-class="text-center text-text font16 fontbold"
+            mask="XXXXXX"
+            outlined
+            :disable="loading"
+            :error="!!errorMessage"
+            :error-message="errorMessage"
+            :placeholder="$t('profile.favorites.code_placeholder')"
+            @keyup.enter="confirm"
+            @update:model-value="onCodeInput"
+          />
+        </q-card-section>
+
+        <q-card-actions class="row q-px-lg q-pb-lg q-gutter-x-sm justify-center">
+          <q-btn
+            class="col code-btn font12 fontbold"
+            color="grey-6"
+            no-caps
+            rounded
+            text-color="grey-2"
+            unelevated
+            :disable="loading"
+            :label="$t('profile.favorites.code_cancel')"
+            @click="onDialogCancel"
+          />
+
+          <q-btn
+            class="col code-btn font12 fontbold"
+            color="secondary"
+            no-caps
+            rounded
+            unelevated
+            :disable="!isValid"
+            :label="$t('profile.favorites.code_confirm')"
+            :loading="loading"
+            @click="confirm"
+          />
+        </q-card-actions>
+      </q-card>
+    </div>
+  </q-dialog>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue';
+import { useDialogPluginComponent } from 'quasar';
+import { useI18n } from 'vue-i18n';
+
+import { createClientFavoriteProviderByCode } from 'src/api/clientFavoriteProvider';
+import { userStore } from 'src/stores/user';
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
+
+const { t } = useI18n();
+const store = userStore();
+
+const CODE_LENGTH = 6;
+
+const code = ref('');
+const errorMessage = ref('');
+const loading = ref(false);
+
+const isValid = computed(() => code.value.length === CODE_LENGTH && !loading.value);
+
+const onCodeInput = (value) => {
+  code.value = String(value ?? '').toUpperCase();
+
+  errorMessage.value = '';
+};
+
+const confirm = async () => {
+  if (!isValid.value) {
+    return;
+  }
+
+  const clientId = store.user?.client_id;
+
+  if (!clientId) {
+    errorMessage.value = t('profile.favorites.code_error');
+
+    return;
+  }
+
+  loading.value = true;
+
+  try {
+    const favorite = await createClientFavoriteProviderByCode(clientId, code.value);
+
+    onDialogOK(favorite);
+  } catch (error) {
+    errorMessage.value = error?.response?.status === 422
+      ? (error?.response?.data?.message ?? t('profile.favorites.code_error'))
+      : '';
+  } finally {
+    loading.value = false;
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.code-dialog-wrapper {
+  width: 320px;
+  max-width: 90vw;
+}
+
+.code-card {
+  width: 100%;
+  border-radius: 20px;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
+}
+
+.code-title {
+  line-height: 1.4;
+  margin: 0;
+}
+
+.code-subtitle {
+  line-height: 1.4;
+  margin: 0;
+}
+
+.code-input :deep(input) {
+  letter-spacing: 4px;
+  text-transform: uppercase;
+}
+
+.code-input :deep(input::placeholder) {
+  color: #bbbbbb;
+  font-weight: 400;
+  letter-spacing: 4px;
+  opacity: 1;
+}
+
+.code-input :deep(input::selection) {
+  background: rgba(139, 92, 246, 0.2);
+  color: #555555;
+}
+
+.code-btn {
+  padding: 6px 12px;
+}
+</style>

+ 41 - 2
src/components/profile/ProfileFavoritesDialog.vue

@@ -74,6 +74,17 @@
           :label="$t('profile.favorites.search_btn')"
           @click="goToSearch"
         />
+
+        <q-btn
+          class="full-width q-py-sm q-mt-sm"
+          color="primary"
+          no-caps
+          outline
+          padding="10px 16px"
+          rounded
+          :label="$t('profile.favorites.code_btn')"
+          @click="openCodeDialog"
+        />
       </div>
 
       <div
@@ -85,6 +96,21 @@
             {{ $t("profile.favorites.indicate_title") }}
           </p>
 
+          <div class="row justify-center q-mb-md">
+            <q-btn
+              color="primary"
+              dense
+              flat
+              no-caps
+              padding="2px 12px"
+              rounded
+              size="sm"
+              icon="mdi-ticket-confirmation-outline"
+              :label="$t('profile.favorites.code_btn')"
+              @click="openCodeDialog"
+            />
+          </div>
+
           <div class="row items-center">
             <div class="col column items-center">
               <q-avatar
@@ -358,6 +384,7 @@ import { useI18n } from "vue-i18n";
 import { useRouter } from "vue-router";
 import { userStore } from "src/stores/user";
 
+import ProfileFavoriteCodeDialog from "./ProfileFavoriteCodeDialog.vue";
 import ProfileFavoriteRemoveDialog from "./ProfileFavoriteRemoveDialog.vue";
 
 defineEmits([...useDialogPluginComponent.emits]);
@@ -438,6 +465,16 @@ const nextSelectedHours = () => {
   }
 };
 
+const openCodeDialog = () => {
+  $q.dialog({
+    component: ProfileFavoriteCodeDialog,
+  }).onOk(async () => {
+    await loadFavorites();
+
+    highlightIndex.value = 0;
+  });
+};
+
 const openIndicateDialog = async (item) => {
   const message = t("profile.favorites.indicate_whatsapp_message", {
     city: item?.city_name ?? "",
@@ -474,7 +511,7 @@ const prevSelectedHours = () => {
   }
 };
 
-onMounted(async () => {
+const loadFavorites = async () => {
   loading.value = true;
 
   try {
@@ -488,7 +525,9 @@ onMounted(async () => {
   } finally {
     loading.value = false;
   }
-});
+};
+
+onMounted(loadFavorites);
 </script>
 
 <style scoped lang="scss">

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

@@ -632,7 +632,14 @@
       "schedule_btn": "book",
       "remove_title": "Do you want to remove the cleaner from favorites?",
       "remove_confirm": "Remove",
-      "remove_cancel": "Cancel"
+      "remove_cancel": "Cancel",
+      "code_btn": "I have a code",
+      "code_title": "Add by code",
+      "code_subtitle": "Enter the 6-character code the cleaner shared with you.",
+      "code_placeholder": "ABC123",
+      "code_confirm": "Add to favorites",
+      "code_cancel": "Cancel",
+      "code_error": "Could not add a favorite with this code. Please try again."
     },
     "address": {
       "title": "Addresses",

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

@@ -629,7 +629,14 @@
       "schedule_btn": "reservar",
       "remove_title": "¿Deseas eliminar al limpiador de favoritos?",
       "remove_confirm": "Eliminar",
-      "remove_cancel": "Cancelar"
+      "remove_cancel": "Cancelar",
+      "code_btn": "Tengo un código",
+      "code_title": "Añadir por código",
+      "code_subtitle": "Escribe el código de 6 caracteres que el limpiador compartió contigo.",
+      "code_placeholder": "ABC123",
+      "code_confirm": "Añadir a favoritos",
+      "code_cancel": "Cancelar",
+      "code_error": "No se pudo añadir a favoritos con este código. Inténtalo de nuevo."
     },
     "address": {
       "title": "Direcciones",

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

@@ -632,7 +632,14 @@
       "schedule_btn": "agendar",
       "remove_title": "Deseja remover o diarista dos favoritos?",
       "remove_confirm": "Remover",
-      "remove_cancel": "Cancelar"
+      "remove_cancel": "Cancelar",
+      "code_btn": "Tenho um código",
+      "code_title": "Adicionar por código",
+      "code_subtitle": "Digite o código de 6 caracteres que o diarista compartilhou com você.",
+      "code_placeholder": "ABC123",
+      "code_confirm": "Favoritar",
+      "code_cancel": "Cancelar",
+      "code_error": "Não foi possível favoritar com esse código. Tente novamente."
     },
     "address": {
       "title": "Endereços",