Jelajahi Sumber

style: padroniza height do logo nas telas de login + ajusta menu inicial com os cards para quando falta oportunidades ou proximos servicos

Gustavo Mantovani 2 minggu lalu
induk
melakukan
93e6b1c9a8

+ 193 - 60
src/components/dashboard/DashboardNextSchedules.vue

@@ -1,74 +1,158 @@
 <template>
   <div class="q-mx-md q-mb-md">
-    <div class="section-title font16 fontbold gradient-diarista q-mb-sm">{{ $t('provider.dashboard.next_schedules.title') }}</div>
+    <div class="font16 fontbold gradient-diarista q-mb-sm section-title">
+      {{ $t("provider.dashboard.next_schedules.title") }}
+    </div>
+
+    <div v-if="!data.length" class="empty-alert row no-wrap items-start">
+      <q-icon class="empty-alert__icon" name="mdi-alert-outline" size="28px" />
+
+      <div class="empty-alert__text font10">
+        <span class="fontbold">
+          {{ $t("provider.dashboard.next_schedules.empty_alert_bold") }}
+        </span>
+
+        {{ $t("provider.dashboard.next_schedules.empty_alert_text") }}
+      </div>
+    </div>
 
-    <div class="scroll-wrapper">
+    <div v-else class="scroll-wrapper">
       <div class="scroll-track">
         <q-card
           v-for="item in data"
           :key="item.id"
-          class="schedule-card card-border shadow-card bg-surface"
           :flat="false"
+          class="schedule-card card-border shadow-card bg-surface"
         >
           <q-card-section class="q-pa-sm">
             <div class="row no-wrap items-center q-gutter-x-sm">
               <q-avatar size="48px">
-                <img v-if="item.customer_photo" :src="item.customer_photo" style="object-fit:cover" />
-                <span v-else class="full-width full-height flex flex-center" :style="avatarColors[item.id % avatarColors.length]" style="border-radius:50%">{{ (item.customer_name ?? item.client_name)?.slice(0,2).toUpperCase() ?? '??' }}</span>
+                <img
+                  v-if="item.customer_photo"
+                  :src="item.customer_photo"
+                  style="object-fit: cover"
+                />
+
+                <span
+                  v-else
+                  class="flex flex-center full-height full-width"
+                  style="border-radius: 50%"
+                  :style="avatarColors[item.id % avatarColors.length]"
+                >
+                  {{
+                    (item.customer_name ?? item.client_name)
+                      ?.slice(0, 2)
+                      .toUpperCase() ?? "??"
+                  }}
+                </span>
               </q-avatar>
+
               <div class="column flex-1">
                 <div class="row items-center q-gutter-x-xs">
-                  <span class="text-name ellipsis font12 fontmedium">{{ item.customer_name ?? item.client_name }}</span>
+                  <span class="ellipsis font12 fontmedium text-name">
+                    {{ item.customer_name ?? item.client_name }}
+                  </span>
+
                   <div v-if="item.customer_rating" class="row items-center">
-                    <q-icon name="mdi-star" color="warning" size="14px" />
-                    <span class="text-text font11">{{ item.customer_rating }}</span>
+                    <q-icon color="warning" name="mdi-star" size="14px" />
+
+                    <span class="text-text font11">
+                      {{ item.customer_rating }}
+                    </span>
                   </div>
                 </div>
+
                 <div class="row items-center no-wrap">
-                  <span class="text-schedule-date-bold font9 fontbold">{{ formatWeekday(item.date) }}</span>
-                  <span class="text-schedule-date-regular font9">{{ ', ' + formatDayMonth(item.date) }}</span>
+                  <span class="font9 fontbold text-schedule-date-bold">
+                    {{ formatWeekday(item.date) }}
+                  </span>
+
+                  <span class="font9 text-schedule-date-regular">
+                    {{ ", " + formatDayMonth(item.date) }}
+                  </span>
                 </div>
+
                 <div class="text-schedule-date-regular font9">
-                  {{ $t('common.from') }}
-                  <span class="text-schedule-date-bold fontbold">{{ item.start_time?.slice(0, 5) }}</span>
-                  {{ $t('common.to') }}
-                  <span class="text-schedule-date-bold fontbold">{{ item.end_time?.slice(0, 5) }}</span>
+                  {{ $t("common.from") }}
+
+                  <span class="text-schedule-date-bold fontbold">
+                    {{ item.start_time?.slice(0, 5) }}
+                  </span>
+
+                  {{ $t("common.to") }}
+
+                  <span class="text-schedule-date-bold fontbold">
+                    {{ item.end_time?.slice(0, 5) }}
+                  </span>
                 </div>
               </div>
+
               <div class="column items-end text-text">
-                <div class="text-price font14 fontbold">{{ formatCurrency(item.total_amount) }}</div>
-                <div class="text-type font10 fontmedium">{{ t(labelsPeriodTypes.find(label => label.value == item.period_type)?.label) }}</div>
-                <div class="text-distance font10">{{ item.distance_km ?? 0 }}{{ $t('common.km') }}</div>
+                <div class="font14 fontbold text-price">
+                  {{ formatCurrency(item.total_amount) }}
+                </div>
+
+                <div class="font10 fontmedium text-type">
+                  {{
+                    t(
+                      labelsPeriodTypes.find(
+                        (label) => label.value == item.period_type,
+                      )?.label,
+                    )
+                  }}
+                </div>
+
+                <div class="font10 text-distance">
+                  {{ item.distance_km ?? 0 }}{{ $t("common.km") }}
+                </div>
               </div>
             </div>
 
-            <div class="row q-mt-md items-center text-text q-px-xs font9 fontmedium">
+            <div
+              class="font9 fontmedium items-center q-mt-md q-px-xs row text-text"
+            >
               <div class="col ellipsis text-grey-7">
-                {{ formatAddress(item.address) || 'N/A' }}
+                {{ formatAddress(item.address) || "N/A" }}
               </div>
-              <q-icon name="mdi-content-copy" color="primary" size="12px" class="q-ml-xs" @click="copyAddress(item.address)"/>
+
+              <q-icon
+                class="q-ml-xs"
+                color="primary"
+                name="mdi-content-copy"
+                size="12px"
+                @click="copyAddress(item.address)"
+              />
             </div>
 
             <div class="row q-mt-sm items-center">
               <q-btn
-                unelevated
-                no-caps
-                flat
                 color="primary"
-                size="xs"
+                flat
+                no-caps
                 padding="4px 8px"
+                size="xs"
+                unelevated
                 :label="$t('provider.dashboard.next_schedules.details')"
                 @click="emit('view-details', item)"
               />
+
               <q-space />
-              <div class="row items-center q-gutter-x-xs text-grey-7 font9">
+
+              <div class="font9 items-center q-gutter-x-xs row text-grey-7">
                 <q-icon
-                  :name="item.offers_meal ? 'mdi-silverware' : 'mdi-close-outline'"
-                  :color="item.offers_meal ? 'secondary' : 'grey-5'"
                   size="14px"
+                  :color="item.offers_meal ? 'secondary' : 'grey-5'"
+                  :name="
+                    item.offers_meal ? 'mdi-silverware' : 'mdi-close-outline'
+                  "
                 />
+
                 <span :class="item.offers_meal ? 'fontmedium' : ''">
-                  {{ item.offers_meal ? $t('provider.dashboard.next_schedules.offers_meal') : $t('provider.dashboard.next_schedules.no_meal') }}
+                  {{
+                    item.offers_meal
+                      ? $t("provider.dashboard.next_schedules.offers_meal")
+                      : $t("provider.dashboard.next_schedules.no_meal")
+                  }}
                 </span>
               </div>
             </div>
@@ -80,61 +164,106 @@
 </template>
 
 <script setup>
-import { useI18n } from 'vue-i18n';
-import { formatCurrency } from 'src/helpers/utils';
-import { labelsPeriodTypes } from 'src/helpers/arraysOptions/labelsPeriodTypes.js';
-import { useQuasar } from 'quasar';
-import { formatAddress } from 'src/helpers/utils';
-defineProps({ data: { type: Array, default: () => [] } });
-const emit = defineEmits(['view-details']);
-
-const t = useI18n().t;
+import { formatAddress, formatCurrency } from "src/helpers/utils";
+import { labelsPeriodTypes } from "src/helpers/arraysOptions/labelsPeriodTypes.js";
+import { useI18n } from "vue-i18n";
+import { useQuasar } from "quasar";
+
+defineProps({
+  data: {
+    default: () => [],
+    type: Array,
+  },
+});
+
+const emit = defineEmits(["view-details"]);
+
 const $q = useQuasar();
+const { t } = useI18n();
 
 const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
+  { background: "#ffd5df", color: "#932e57" },
+  { background: "#d7e8ff", color: "#2158a8" },
+  { background: "#dfd", color: "#2a7a3b" },
+  { background: "#ffe5cc", color: "#8a4500" },
 ];
 
-const formatWeekday = (iso) => {
-  if (!iso) return '';
-  const d = new Date(iso);
-  const w = d.toLocaleDateString('pt-BR', { weekday: 'long' });
-  return w.charAt(0).toUpperCase() + w.slice(1);
+const copyAddress = (address) => {
+  const formatted = formatAddress(address);
+
+  if (formatted) {
+    navigator.clipboard.writeText(formatted);
+  }
+
+  $q.notify({
+    color: "positive",
+    message: t("provider.dashboard.next_schedules.address_copied"),
+  });
 };
 
 const formatDayMonth = (iso) => {
-  if (!iso) return '';
-  return new Date(iso).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' });
+  if (!iso) {
+    return "";
+  }
+
+  return new Date(iso).toLocaleDateString("pt-BR", {
+    day: "2-digit",
+    month: "2-digit",
+  });
 };
 
-const copyAddress = (address) => {
-  const formatted = formatAddress(address);
-  if (formatted) {
-    navigator.clipboard.writeText(formatted);
+const formatWeekday = (iso) => {
+  if (!iso) {
+    return "";
   }
-  $q.notify({ message: t('provider.dashboard.next_schedules.address_copied'), color: 'positive' });
+
+  const date = new Date(iso);
+  const weekday = date.toLocaleDateString("pt-BR", { weekday: "long" });
+
+  return weekday.charAt(0).toUpperCase() + weekday.slice(1);
 };
 </script>
 
 <style scoped lang="scss">
-.scroll-wrapper { overflow: hidden; }
+.scroll-wrapper {
+  overflow: hidden;
+}
+.empty-alert {
+  background: #e3efff;
+  border-radius: 8px;
+  gap: 12px;
+  padding: 13px 14px;
+}
+.empty-alert__icon {
+  color: #6554d9;
+  flex: 0 0 auto;
+}
+.empty-alert__text {
+  color: #6554d9;
+  line-height: 1.2;
+  padding-top: 1px;
+}
 .scroll-track {
   display: flex;
   flex-direction: row;
   gap: 12px;
-  overflow-x: auto;
   overscroll-behavior-x: contain;
-  scroll-snap-type: x proximity;
+  overflow-x: auto;
   padding-bottom: 8px;
-  &::-webkit-scrollbar { display: none; }
-  &::after { content: ''; flex: 0 0 1px; }
+  scroll-snap-type: x proximity;
+
+  &::-webkit-scrollbar {
+    display: none;
+  }
+
+  &::after {
+    content: "";
+    flex: 0 0 1px;
+  }
 }
 .schedule-card {
-  min-width: 85vw;
   border-radius: 12px;
+  min-width: 85vw;
 }
 .text-name {
   color: #3a3a4a;
@@ -145,7 +274,11 @@ const copyAddress = (address) => {
 .text-schedule-date-regular {
   color: #666;
 }
-.text-date, .text-time, .text-type, .text-region, .text-distance {
+.text-date,
+.text-time,
+.text-type,
+.text-region,
+.text-distance {
   color: #666;
 }
 .text-price {

+ 171 - 69
src/components/dashboard/DashboardOpportunities.vue

@@ -1,68 +1,147 @@
 <template>
   <div class="q-mx-md q-mb-md">
     <div class="row items-center justify-between q-mb-sm">
-      <div class="section-title font16 fontbold gradient-diarista">{{ $t('provider.dashboard.opportunities.title') }}</div>
-      <q-btn flat no-caps color="grey-6" size="sm" :label="$t('common.see_all')"  @click="openAllOpportunities"/>
+      <div class="font16 fontbold gradient-diarista section-title">
+        {{ $t("provider.dashboard.opportunities.title") }}
+      </div>
+
+      <q-btn
+        v-if="data.length"
+        color="grey-6"
+        flat
+        no-caps
+        size="sm"
+        :label="$t('common.see_all')"
+        @click="openAllOpportunities"
+      />
     </div>
 
-    <div class="scroll-wrapper">
+    <div v-if="!data.length" class="empty-alert row no-wrap items-start">
+      <q-icon class="empty-alert__icon" name="mdi-alert-outline" size="28px" />
+
+      <div class="empty-alert__text font10">
+        <span class="fontbold">
+          {{ $t("provider.dashboard.opportunities.empty_alert_bold") }}
+        </span>
+
+        {{ $t("provider.dashboard.opportunities.empty_alert_text") }}
+      </div>
+    </div>
+
+    <div v-else class="scroll-wrapper">
       <div class="scroll-track row no-wrap q-gutter-x-md">
         <q-card
           v-for="item in data"
           :key="item.id"
-          class="opportunity-card bg-surface shadow-card card-border"
           :flat="false"
+          class="opportunity-card bg-surface shadow-card card-border"
         >
           <q-card-section class="q-pa-sm">
             <div class="row no-wrap items-center q-gutter-x-sm">
               <q-avatar size="48px">
-                <img v-if="item.customer_photo" :src="item.customer_photo" style="object-fit:cover" />
-                <span v-else class="full-width full-height flex flex-center" :style="avatarColors[item.id % avatarColors.length]" style="border-radius:50%">{{ (item.client_name ?? item.client?.user?.name)?.slice(0,2).toUpperCase() ?? '??' }}</span>
+                <img
+                  v-if="item.customer_photo"
+                  :src="item.customer_photo"
+                  style="object-fit: cover"
+                />
+
+                <span
+                  v-else
+                  class="flex flex-center full-height full-width"
+                  style="border-radius: 50%"
+                  :style="avatarColors[item.id % avatarColors.length]"
+                >
+                  {{
+                    (item.client_name ?? item.client?.user?.name)
+                      ?.slice(0, 2)
+                      .toUpperCase() ?? "??"
+                  }}
+                </span>
               </q-avatar>
+
               <div class="column flex-1">
                 <div class="row items-center q-gutter-x-xs">
-                  <span class="ellipsis font12 fontmedium text-text">{{ item.client?.user?.name }}</span>
+                  <span class="ellipsis font12 fontmedium text-text">
+                    {{ item.client?.user?.name }}
+                  </span>
+
                   <div class="row items-center">
                     <div class="q-my-auto">
-                      <q-icon name="mdi-star" color="warning" size="12px"/>                  
+                      <q-icon color="warning" name="mdi-star" size="12px" />
                     </div>
+
                     <div class="q-my-auto">
-                      <span class="text-text font9 fontregular ">{{ item.client?.average_rating }}</span>
+                      <span class="font9 fontregular text-text">
+                        {{ item.client?.average_rating }}
+                      </span>
                     </div>
                   </div>
                 </div>
+
                 <div class="row items-center no-wrap">
-                  <span class="text-schedule-date-bold font9 fontbold">{{ formatWeekday(item.date) }}</span>
-                  <span class="text-schedule-date-regular font9">{{ ', ' + formatDayMonth(item.date) }}</span>
+                  <span class="font9 fontbold text-schedule-date-bold">
+                    {{ formatWeekday(item.date) }}
+                  </span>
+
+                  <span class="font9 text-schedule-date-regular">
+                    {{ ", " + formatDayMonth(item.date) }}
+                  </span>
                 </div>
+
                 <div class="text-schedule-date-regular font9">
-                  {{ $t('common.from') }}
-                  <span class="text-schedule-date-bold fontbold">{{ item.start_time?.slice(0, 5) }}</span>
-                  {{ $t('common.to') }}
-                  <span class="text-schedule-date-bold fontbold">{{ item.end_time?.slice(0, 5) }}</span>
+                  {{ $t("common.from") }}
+
+                  <span class="text-schedule-date-bold fontbold">
+                    {{ item.start_time?.slice(0, 5) }}
+                  </span>
+
+                  {{ $t("common.to") }}
+
+                  <span class="text-schedule-date-bold fontbold">
+                    {{ item.end_time?.slice(0, 5) }}
+                  </span>
                 </div>
               </div>
+
               <div class="column items-end text-text">
-                <div class="text-price font12 fontbold">{{ formatCurrency(item.total_amount) }}</div>
-                <div class="text-type font9">{{ $t(labelsPeriodTypes.find(label => label.value == item.period_type)?.label) }}</div>
-                <div class="text-region font9">{{ item.address?.district || 'N/A' }}</div>
-                <div class="text-distance font9">{{ item.distance_km ?? 0 }}{{ $t('common.km') }}</div>
+                <div class="font12 fontbold text-price">
+                  {{ formatCurrency(item.total_amount) }}
+                </div>
+
+                <div class="font9 text-type">
+                  {{
+                    $t(
+                      labelsPeriodTypes.find(
+                        (label) => label.value == item.period_type,
+                      )?.label,
+                    )
+                  }}
+                </div>
+
+                <div class="font9 text-region">
+                  {{ item.address?.district || "N/A" }}
+                </div>
+
+                <div class="font9 text-distance">
+                  {{ item.distance_km ?? 0 }}{{ $t("common.km") }}
+                </div>
               </div>
             </div>
 
             <div class="row q-mt-sm justify-between">
-              <div class="text-text text-primary font11 fontmedium">
-                  {{ item.custom_schedule?.address_type }}
+              <div class="font11 fontmedium text-primary text-text">
+                {{ item.custom_schedule?.address_type }}
               </div>
+
               <q-btn
-                 unelevated
-                 rounded
-                 no-caps
-                 color="primary"
-                 size="xs"
-                 :label="$t('common.details')"
-                 @click="openOpportunityDetails(item)"
-                />
+                color="primary"
+                no-caps
+                rounded
+                size="xs"
+                unelevated
+                :label="$t('common.details')"
+                @click="openOpportunityDetails(item)"
+              />
             </div>
           </q-card-section>
         </q-card>
@@ -72,77 +151,96 @@
 </template>
 
 <script setup>
-import { formatCurrency } from 'src/helpers/utils';
-import { labelsPeriodTypes } from 'src/helpers/arraysOptions/labelsPeriodTypes.js';
-import { useQuasar } from 'quasar';
-import { useRouter } from 'vue-router';
-import OpportunityDetailsDialog from 'src/pages/opportunities/components/OpportunityDetailsDialog.vue';
+import { formatCurrency } from "src/helpers/utils";
+import { labelsPeriodTypes } from "src/helpers/arraysOptions/labelsPeriodTypes.js";
+import { useQuasar } from "quasar";
+import { useRouter } from "vue-router";
+
+import OpportunityDetailsDialog from "src/pages/opportunities/components/OpportunityDetailsDialog.vue";
 
 defineProps({
   data: {
+    default: () => [],
     type: Array,
-    default: () => []
-  }
+  },
 });
 
-
-
 const $q = useQuasar();
 const router = useRouter();
 
 const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
+  { background: "#ffd5df", color: "#932e57" },
+  { background: "#d7e8ff", color: "#2158a8" },
+  { background: "#dfd", color: "#2a7a3b" },
+  { background: "#ffe5cc", color: "#8a4500" },
 ];
 
+const formatDayMonth = (iso) => {
+  if (!iso) {
+    return "";
+  }
 
-const openOpportunityDetails = (item) => {
-  $q.dialog({
-    component: OpportunityDetailsDialog,
-    componentProps: {
-      opportunityId: item.custom_schedule?.id,
-      distanceKm: item.distance_km ?? null,
-    }
-  })
-}
-
-// botão ver todas 
-const openAllOpportunities = () => {
-  router.push({
-    name: 'OpportunitiesPage'
-  })
-}
-
+  return new Date(iso).toLocaleDateString("pt-BR", {
+    day: "2-digit",
+    month: "2-digit",
+  });
+};
 
+const formatWeekday = (iso) => {
+  if (!iso) {
+    return "";
+  }
 
+  const date = new Date(iso);
+  const weekday = date.toLocaleDateString("pt-BR", { weekday: "long" });
 
-const formatWeekday = (iso) => {
-  if (!iso) return '';
-  const d = new Date(iso);
-  const w = d.toLocaleDateString('pt-BR', { weekday: 'long' });
-  return w.charAt(0).toUpperCase() + w.slice(1);
+  return weekday.charAt(0).toUpperCase() + weekday.slice(1);
 };
 
-const formatDayMonth = (iso) => {
-  if (!iso) return '';
-  return new Date(iso).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' });
+const openAllOpportunities = () => {
+  router.push({
+    name: "OpportunitiesPage",
+  });
 };
 
+const openOpportunityDetails = (item) => {
+  $q.dialog({
+    component: OpportunityDetailsDialog,
+    componentProps: {
+      distanceKm: item.distance_km ?? null,
+      opportunityId: item.custom_schedule?.id,
+    },
+  });
+};
 </script>
 
 <style scoped lang="scss">
+.empty-alert {
+  background: #e3efff;
+  border-radius: 8px;
+  gap: 12px;
+  padding: 13px 14px;
+}
+.empty-alert__icon {
+  color: #6554d9;
+  flex: 0 0 auto;
+}
+.empty-alert__text {
+  color: #6554d9;
+  line-height: 1.2;
+  padding-top: 1px;
+}
 .scroll-wrapper {
   overflow-x: auto;
   padding-bottom: 8px;
+
   &::-webkit-scrollbar {
     display: none;
   }
 }
 .opportunity-card {
-  min-width: 85vw;
   border-radius: 12px;
+  min-width: 85vw;
 }
 .text-schedule-date-bold {
   color: #3a3a4a;
@@ -150,7 +248,11 @@ const formatDayMonth = (iso) => {
 .text-schedule-date-regular {
   color: #666;
 }
-.text-date, .text-time, .text-type, .text-region, .text-distance {
+.text-date,
+.text-time,
+.text-type,
+.text-region,
+.text-distance {
   color: #666;
 }
 .text-price {

+ 34 - 39
src/components/dashboard/DashboardScrollAreaSchedules.vue

@@ -7,78 +7,73 @@
         class="promo-card"
         @click="goToCard(card)"
       >
-        <img :src="card.image" :alt="card.alt" class="promo-card__img" />
+        <img :alt="card.alt" class="promo-card__img" :src="card.image" />
       </div>
     </div>
   </section>
 </template>
+
 <script setup>
-import Banner1 from 'src/assets/banner_1.svg';
-import Banner2 from 'src/assets/banner_2.svg';
+import { useQuasar } from "quasar";
+import { useRouter } from "vue-router";
 
-import { useRouter } from 'vue-router';
-import { useQuasar } from 'quasar';
-import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue';
+import Banner1 from "src/assets/banner_1.svg";
+import Banner2 from "src/assets/banner_2.svg";
+import ProfileHelpDialog from "src/components/profile/ProfileHelpDialog.vue";
 
-const router = useRouter();
 const $q = useQuasar();
+const router = useRouter();
 
 const cards = [
-  { id: 1, image: Banner1, alt: 'Diária sob medida', routeName: 'OpportunitiesPage' },
-  { id: 2, image: Banner2, alt: 'Como aceitar uma diária', action: 'help' },
+  {
+    alt: "Diária sob medida",
+    id: 1,
+    image: Banner1,
+    routeName: "OpportunitiesPage",
+  },
+  {
+    action: "help",
+    alt: "Como aceitar uma diária",
+    id: 2,
+    image: Banner2,
+  },
 ];
 
 const goToCard = (card) => {
-  if (card.action === 'help') {
-    $q.dialog({ component: ProfileHelpDialog })
-    return
+  if (card.action === "help") {
+    $q.dialog({ component: ProfileHelpDialog });
+
+    return;
   }
+
   if (card.routeName) {
-    router.push({ name: card.routeName })
+    router.push({ name: card.routeName });
   }
-}
-
+};
 </script>
 
 <style scoped lang="scss">
 .promo-scroll-wrapper {
-  width: 100%;
   overflow: hidden;
 }
 
 .promo-scroll {
-  display: flex;
+  display: grid;
   gap: 12px;
-  overflow-x: auto;
-  overscroll-behavior-x: contain;
-  padding: 0 0 4px 16px;
-  scroll-snap-type: x mandatory;
-
-  &::-webkit-scrollbar {
-    display: none;
-  }
-
-  &::after {
-    content: '';
-    flex: 0 0 16px;
-  }
+  grid-template-columns: repeat(2, minmax(0, 1fr));
 }
 
 .promo-card {
-  height: 120px;
-  border-radius: 10px;
-  overflow: hidden;
-  scroll-snap-align: start;
   background: #e8e4f5;
-  flex-shrink: 0;
+  border-radius: 10px;
   cursor: pointer;
+  overflow: hidden;
 }
 
 .promo-card__img {
-  width: 100%;
-  height: 100%;
-  object-fit: cover;
-  display: block;
   border-radius: 10px;
+  display: block;
+  height: auto;
+  width: 100%;
 }
 </style>

+ 47 - 20
src/components/login/LoginStepOnePanel.vue → src/components/login/LoginStep1Panel.vue

@@ -1,47 +1,64 @@
 <template>
   <q-card-section class="full-width">
     <div class="text-text">
-      <span class="fontbold font12">{{ $t('common.terms.email') }}</span>
+      <span class="font12 fontbold">
+        {{ $t('common.terms.email') }}
+      </span>
     </div>
+
     <q-input
       v-model="email"
-      no-error-icon
-      outlined
-      rounded
       class="bg-surface q-mt-sm"
-      input-class="text-text font12 fontmedium"
-      :placeholder="$t('common.terms.email')"
       hide-bottom-space
-      :rules="!phone ? [inputRules.requiredHideMessage, inputRules.email] : [inputRules.email]"
+      input-class="font12 fontmedium text-text"
       lazy-rules
+      no-error-icon
+      outlined
+      rounded
       :bottom-slots="false"
+      :placeholder="$t('common.terms.email')"
+      :rules="!phone
+        ? [inputRules.requiredHideMessage, inputRules.email]
+        : [inputRules.email]"
     />
 
-    <div class="col-12 row q-py-lg">
+    <div class="col-12 q-py-lg row">
       <div class="col-5 q-my-auto">
-        <q-separator class="q-my-sm bg-grey-4" />
+        <q-separator class="bg-grey-4 q-my-sm" />
       </div>
-      <span class="col text-center text-text font12 fontmedium">{{ $t('common.ui.misc.or') }}</span>
+
+      <span class="col font12 fontmedium text-center text-text">
+        {{ $t('common.ui.misc.or') }}
+      </span>
+
       <div class="col-5 q-my-auto">
-        <q-separator class="q-my-sm bg-grey-4" />
+        <q-separator class="bg-grey-4 q-my-sm" />
       </div>
     </div>
 
     <div class="text-text">
-      <span class="fontbold font12">{{ $t('common.terms.phone') }}</span>
-      <span class="fontlight font12">{{ ' (' + $t('common.terms.celular') + ')' }}</span>
+      <span class="font12 fontbold">
+        {{ $t('common.terms.phone') }}
+      </span>
+
+      <span class="font12 fontlight">
+        {{ ` (${$t('common.terms.celular')})` }}
+      </span>
     </div>
+
     <q-input
       v-model="phone"
-      no-error-icon
       class="bg-surface q-mt-sm"
-      :placeholder="$t('common.terms.phone')"
-      input-class="text-text"
       hide-bottom-space
-      :rules="!email ? [inputRules.requiredHideMessage, validatePhone] : [validatePhone]"
+      input-class="text-text"
       lazy-rules
       mask="(##) #####-####"
+      no-error-icon
       :bottom-slots="false"
+      :placeholder="$t('common.terms.phone')"
+      :rules="!email
+        ? [inputRules.requiredHideMessage, validatePhone]
+        : [validatePhone]"
     />
   </q-card-section>
 </template>
@@ -49,13 +66,23 @@
 <script setup>
 import { useInputRules } from 'src/composables/useInputRules';
 
-const email = defineModel('email', { type: String, required: true });
-const phone = defineModel('phone', { type: String, required: true });
+const email = defineModel('email', {
+  required: true,
+  type: String,
+});
+
+const phone = defineModel('phone', {
+  required: true,
+  type: String,
+});
 
 const { inputRules } = useInputRules();
 
 const validatePhone = (value) => {
-  if (!value) return true;
+  if (!value) {
+    return true;
+  }
+
   return value.replace(/\D/g, '').length >= 10 || 'Telefone inválido';
 };
 </script>

+ 32 - 11
src/components/login/LoginStepTwoPanel.vue → src/components/login/LoginStep2Panel.vue

@@ -1,31 +1,47 @@
 <template>
   <q-card-section class="code-section no-padding q-mb-xl">
-    <div class="text-text q-mb-xs text-left">
-      <span class="fontmedium">{{ $t('auth.validation_code') }}</span>
+    <div class="q-mb-xs text-left text-text">
+      <span class="fontmedium">
+        {{ $t('auth.validation_code') }}
+      </span>
     </div>
 
-    <div class="code-boxes-wrapper" @click="focusCodeInput">
+    <div
+      class="code-boxes-wrapper"
+      @click="focusCodeInput"
+    >
       <div
         v-for="i in 6"
         :key="i"
-        class="code-box"
         :class="{ 'code-box--active': code.length === i - 1 }"
+        class="code-box"
       >
-        <span v-if="code[i - 1]" class="text-text">{{ code[i - 1] }}</span>
-        <span v-else class="text-text-light">{{ placeholder }}</span>
+        <span
+          v-if="code[i - 1]"
+          class="text-text"
+        >
+          {{ code[i - 1] }}
+        </span>
+
+        <span
+          v-else
+          class="text-text-light"
+        >
+          {{ placeholder }}
+        </span>
       </div>
     </div>
 
     <q-input
       ref="codeInputRef"
       v-model="code"
-      no-error-icon
       class="code-hidden-input"
       hide-bottom-space
-      :rules="[inputRules.required]"
+      inputmode="numeric"
       lazy-rules
       mask="######"
-      inputmode="numeric"
+      no-error-icon
+      :rules="[inputRules.required]"
     />
   </q-card-section>
 </template>
@@ -34,10 +50,15 @@
 import { ref } from 'vue';
 import { useInputRules } from 'src/composables/useInputRules';
 
-const code = defineModel('code', { type: String, required: true });
+const code = defineModel('code', {
+  required: true,
+  type: String,
+});
 
 const { inputRules } = useInputRules();
+
 const codeInputRef = ref(null);
+
 const placeholder = 'x';
 
 const focusCodeInput = () => {
@@ -59,7 +80,7 @@ const focusCodeInput = () => {
     max-width: 44px;
     aspect-ratio: 1;
     border: 2px solid #e4e4e4;
-    background-color: #F9FAFB;
+    background-color: #f9fafb;
     border-radius: 8px;
     display: flex;
     align-items: center;

+ 141 - 93
src/components/login/LoginStepThreePanel.vue → src/components/login/LoginStep3Panel.vue

@@ -1,221 +1,230 @@
 <template>
   <q-card-section class="no-padding">
     <div class="text-text">
-      <span class="font12 fontbold">{{ $t('provider.login.steps.step_3.full_name') }}</span>
+      <span class="font12 fontbold">
+        {{ $t('provider.login.steps.step_3.full_name') }}
+      </span>
     </div>
+
     <q-input
       v-model="form.name"
+      class="bg-surface q-mb-md q-mt-sm"
+      hide-bottom-space
+      input-class="font12 fontmedium text-text"
+      lazy-rules
       no-error-icon
       outlined
       rounded
-      class="bg-surface q-mt-sm q-mb-md"
-      input-class="text-text font12 fontmedium"
       :placeholder="$t('provider.login.steps.step_3.full_name')"
-      hide-bottom-space
       :rules="[inputRules.required]"
-      lazy-rules
     />
 
     <div class="text-text">
-      <span class="font12 fontbold">{{ $t('common.terms.phone') }}</span>
+      <span class="font12 fontbold">
+        {{ $t('common.terms.phone') }}
+      </span>
     </div>
+
     <q-input
       v-model="form.phone"
+      class="bg-surface q-mb-md q-mt-sm"
+      hide-bottom-space
+      input-class="font12 fontmedium text-text"
+      lazy-rules
+      mask="(##) #####-####"
       no-error-icon
       outlined
-      rounded
-      class="bg-surface q-mt-sm q-mb-md"
-      input-class="text-text font12 fontmedium"
       placeholder="(00) 00000-0000"
-      hide-bottom-space
+      rounded
       :rules="[inputRules.required]"
-      lazy-rules
-      mask="(##) #####-####"
     />
 
     <div class="text-text">
-      <span class="font12 fontbold">{{ $t('common.terms.email') }}</span>
+      <span class="font12 fontbold">
+        {{ $t('common.terms.email') }}
+      </span>
     </div>
+
     <q-input
       v-model="form.email"
+      class="bg-surface q-mb-md q-mt-sm"
+      hide-bottom-space
+      input-class="font12 fontmedium text-text"
+      lazy-rules
       no-error-icon
       outlined
-      rounded
-      class="bg-surface q-mt-sm q-mb-md"
-      input-class="text-text font12 fontmedium"
       placeholder="nome@email.com"
-      hide-bottom-space
       readonly
-      :rules="[inputRules.required, inputRules.email]"
-      lazy-rules
+      rounded
       type="email"
+      :rules="[inputRules.required, inputRules.email]"
     />
 
     <div class="text-text">
-      <span class="font12 fontbold">{{ $t('provider.login.steps.step_3.rg') }}</span>
+      <span class="font12 fontbold">
+        {{ $t('provider.login.steps.step_3.rg') }}
+      </span>
     </div>
+
     <q-input
       v-model="form.rg"
-      no-error-icon
-      outlined
-      rounded
-      class="bg-surface q-mt-sm q-mb-md"
-      input-class="text-text font12 fontmedium"
-      placeholder="00.000.000-0"
+      class="bg-surface q-mb-md q-mt-sm"
       hide-bottom-space
-      :rules="[inputRules.required, validateRG]"
+      input-class="font12 fontmedium text-text"
       lazy-rules
       mask="##.###.###-#"
+      no-error-icon
+      outlined
+      placeholder="00.000.000-0"
+      rounded
       type="tel"
+      :rules="[inputRules.required, validateRG]"
     />
 
     <div class="text-text">
-      <span class="font12 fontbold">{{ $t('common.terms.cpf') }}</span>
+      <span class="font12 fontbold">
+        {{ $t('common.terms.cpf') }}
+      </span>
     </div>
+
     <q-input
       v-model="form.document"
-      no-error-icon
-      outlined
-      rounded
-      class="bg-surface q-mt-sm q-mb-md"
-      input-class="text-text font12 fontmedium"
-      placeholder="000.000.000-00"
+      class="bg-surface q-mb-md q-mt-sm"
       hide-bottom-space
-      :rules="[inputRules.required, inputRules.cpf]"
+      input-class="font12 fontmedium text-text"
       lazy-rules
       mask="###.###.###-##"
+      no-error-icon
+      outlined
+      placeholder="000.000.000-00"
+      rounded
       type="tel"
+      :rules="[inputRules.required, inputRules.cpf]"
     />
 
     <div class="text-text">
-      <span class="font12 fontbold">{{ $t('provider.login.steps.step_3.birth_date') }}</span>
+      <span class="font12 fontbold">
+        {{ $t('provider.login.steps.step_3.birth_date') }}
+      </span>
     </div>
+
     <q-input
       v-model="form.birth_date"
+      class="bg-surface q-mb-md q-mt-sm"
+      hide-bottom-space
+      input-class="font12 fontmedium text-text"
+      lazy-rules
+      mask="##/##/####"
       no-error-icon
       outlined
       rounded
-      class="bg-surface q-mt-sm q-mb-md"
-      input-class="text-text font12 fontmedium"
       :placeholder="$t('provider.login.steps.step_3.birth_date_placeholder')"
-      hide-bottom-space
       :rules="[inputRules.required, validateBirthDate]"
-      lazy-rules
-      mask="##/##/####"
     />
 
     <div class="text-text">
-      <span class="font12 fontbold">{{ $t('common.terms.cep') }}</span>
+      <span class="font12 fontbold">
+        {{ $t('common.terms.cep') }}
+      </span>
     </div>
+
     <q-input
       v-model="form.zip_code"
-      no-error-icon
-      outlined
-      rounded
-      class="bg-surface q-mt-sm q-mb-md"
-      input-class="text-text font12 fontmedium"
-      placeholder="00000-000"
+      class="bg-surface q-mb-md q-mt-sm"
       hide-bottom-space
-      :rules="[inputRules.required, inputRules.cep]"
+      input-class="font12 fontmedium text-text"
       lazy-rules
       mask="#####-###"
+      no-error-icon
+      outlined
+      placeholder="00000-000"
+      rounded
       type="tel"
       :loading="loadingCep"
+      :rules="[inputRules.required, inputRules.cep]"
       @update:model-value="onCepChange"
     />
 
     <div class="text-text">
-      <span class="font12 fontbold">{{ $t('common.terms.address') }}</span>
+      <span class="font12 fontbold">
+        {{ $t('common.terms.address') }}
+      </span>
     </div>
+
     <q-input
       v-model="form.address"
+      class="bg-surface q-mb-md q-mt-sm"
+      hide-bottom-space
+      input-class="font12 fontmedium text-text"
+      lazy-rules
       no-error-icon
       outlined
       rounded
-      class="bg-surface q-mt-sm q-mb-md"
-      input-class="text-text font12 fontmedium"
       :placeholder="$t('provider.login.steps.step_3.address_placeholder')"
-      hide-bottom-space
       :rules="[inputRules.required]"
-      lazy-rules
     />
+
     <div class="text-center">
       <q-checkbox
         v-model="form.no_complement"
-        :label="$t('provider.login.steps.step_3.no_complement')"
         class="q-mb-md text-text"
         color="primary"
         keep-color
+        :label="$t('provider.login.steps.step_3.no_complement')"
       />
     </div>
 
     <template v-if="!form.no_complement">
       <div class="text-text">
-        <span class="font12 fontbold">{{ $t('common.terms.complement') }}</span>
+        <span class="font12 fontbold">
+          {{ $t('common.terms.complement') }}
+        </span>
       </div>
+
       <q-input
         v-model="form.complement"
+        class="bg-surface q-mb-md q-mt-sm"
+        hide-bottom-space
+        input-class="font12 fontmedium text-text"
+        lazy-rules
         no-error-icon
         outlined
         rounded
-        class="bg-surface q-mt-sm q-mb-md"
-        input-class="text-text font12 fontmedium"
         :placeholder="$t('provider.login.steps.step_3.complement_placeholder')"
-        hide-bottom-space
         :rules="[inputRules.required]"
-        lazy-rules
       />
     </template>
   </q-card-section>
 </template>
 
 <script setup>
-import { ref } from 'vue';
 import axios from 'axios';
-import { useI18n } from 'vue-i18n';
 import { useInputRules } from 'src/composables/useInputRules';
+import { ref } from 'vue';
+import { useI18n } from 'vue-i18n';
 
-const { t } = useI18n();
-const form = defineModel({ type: Object, required: true });
-const { inputRules } = useInputRules();
-const loadingCep = ref(false);
-
-const validateRG = (value) => {
-  const cleanedValue = value?.replace(/\D/g, '') || '';
-  return cleanedValue.length >= 8 || t('provider.login.steps.step_3.rg_invalid');
-};
-
-const validateBirthDate = (value) => {
-  if (!value) return t('validation.rules.required');
-
-  const matches = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value);
-  if (!matches) return t('provider.login.steps.step_3.date_invalid');
-
-  const day = Number(matches[1]);
-  const month = Number(matches[2]);
-  const year = Number(matches[3]);
-
-  const date = new Date(year, month - 1, day);
-  const isValidDate =
-    date.getFullYear() === year &&
-    date.getMonth() === month - 1 &&
-    date.getDate() === day;
+const form = defineModel({
+  required: true,
+  type: Object,
+});
 
-  if (!isValidDate) return t('provider.login.steps.step_3.date_invalid');
-  if (date > new Date()) return t('provider.login.steps.step_3.date_invalid');
+const { inputRules } = useInputRules();
+const { t } = useI18n();
 
-  return true;
-};
-// ...existing code...
+const loadingCep = ref(false);
 
 const fetchCep = async (rawCep) => {
   const cleaned = rawCep.replace(/\D/g, '');
-  if (cleaned.length !== 8) return;
+
+  if (cleaned.length !== 8) {
+    return;
+  }
 
   loadingCep.value = true;
 
   try {
     const { data } = await axios.get(`https://viacep.com.br/ws/${cleaned}/json/`);
+
     if (!data.erro) {
       form.value.address = `${data.logradouro}, ${data.bairro} - ${data.localidade}/${data.uf}`;
       form.value.city = data.localidade;
@@ -236,8 +245,47 @@ const fetchCep = async (rawCep) => {
 
 const onCepChange = (value) => {
   const cleaned = value?.replace(/\D/g, '') || '';
+
   if (cleaned.length === 8) {
     fetchCep(value);
   }
 };
+
+const validateBirthDate = (value) => {
+  if (!value) {
+    return t('validation.rules.required');
+  }
+
+  const matches = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value);
+
+  if (!matches) {
+    return t('provider.login.steps.step_3.date_invalid');
+  }
+
+  const day = Number(matches[1]);
+  const month = Number(matches[2]);
+  const year = Number(matches[3]);
+
+  const date = new Date(year, month - 1, day);
+  const isValidDate =
+    date.getFullYear() === year
+    && date.getMonth() === month - 1
+    && date.getDate() === day;
+
+  if (!isValidDate) {
+    return t('provider.login.steps.step_3.date_invalid');
+  }
+
+  if (date > new Date()) {
+    return t('provider.login.steps.step_3.date_invalid');
+  }
+
+  return true;
+};
+
+const validateRG = (value) => {
+  const cleanedValue = value?.replace(/\D/g, '') || '';
+
+  return cleanedValue.length >= 8 || t('provider.login.steps.step_3.rg_invalid');
+};
 </script>

+ 112 - 64
src/components/login/LoginStepFourPanel.vue → src/components/login/LoginStep4Panel.vue

@@ -9,7 +9,7 @@
         >
           <q-icon
             color="white"
-            name="photo_camera" 
+            name="photo_camera"
             size="52px"
           />
         </div>
@@ -24,11 +24,14 @@
             :loading="loadingSelfie"
             @click="takeSelfie"
           />
-          <div class="text-text text-center font12">{{ t('provider.login.steps.step_4.selfie_hint') }}</div>
+
+          <div class="font12 text-center text-text">
+            {{ t('provider.login.steps.step_4.selfie_hint') }}
+          </div>
         </template>
 
         <template v-else>
-          <div class="row items-center q-gutter-x-xs">
+          <div class="items-center q-gutter-x-xs row">
             <q-icon
               color="positive"
               name="check_circle"
@@ -66,13 +69,13 @@
             @click="takeDocFront"
           />
 
-          <div class="text-text text-center font12">
+          <div class="font12 text-center text-text">
             {{ t('provider.login.steps.step_4.document_hint') }}
           </div>
         </template>
 
         <template v-else>
-          <div class="row items-center q-gutter-x-xs">
+          <div class="items-center q-gutter-x-xs row">
             <q-icon
               color="positive"
               name="check_circle"
@@ -88,7 +91,10 @@
     </div>
 
     <Teleport to="body">
-      <div v-if="showResult" class="fs-overlay">
+      <div
+        v-if="showResult"
+        class="fs-overlay"
+      >
         <div class="fs-result">
           <q-img
             class="q-mb-xl"
@@ -109,18 +115,21 @@
 
           <template v-if="resultError">
             <q-icon
-              class="q-mt-md" 
-              color="negative" 
+              class="q-mt-md"
+              color="negative"
               name="cancel"
               size="28px"
             />
 
-            <div class="text-primary text-center q-mt-xs q-px-lg font14">
+            <div class="font14 q-mt-xs q-px-lg text-center text-primary">
               {{ t('provider.login.steps.step_4.error_message') }}
             </div>
           </template>
 
-          <div v-else class="text-primary text-center q-mt-md q-px-lg font14">
+          <div
+            v-else
+            class="font14 q-mt-md q-px-lg text-center text-primary"
+          >
             {{ resultMessage }}
           </div>
 
@@ -131,11 +140,16 @@
             padding="14px 48px"
             rounded
             style="min-width: 200px"
-            :label="resultError ? t('provider.login.steps.step_4.btn_retry') : t('provider.login.steps.step_4.btn_continue')"
+            :label="resultError
+              ? t('provider.login.steps.step_4.btn_retry')
+              : t('provider.login.steps.step_4.btn_continue')"
             @click="closeResult"
           />
 
-          <div v-if="!resultError && showDocHint" class="text-grey-6 text-center q-mt-sm font12">
+          <div
+            v-if="!resultError && showDocHint"
+            class="font12 q-mt-sm text-center text-grey-6"
+          >
             {{ t('provider.login.steps.step_4.document_hint_result') }}
           </div>
         </div>
@@ -145,44 +159,54 @@
 </template>
 
 <script setup>
-import { Camera, CameraDirection, CameraResultType, CameraSource } from '@capacitor/camera';
+import {
+  Camera,
+  CameraDirection,
+  CameraResultType,
+  CameraSource,
+} from '@capacitor/camera';
 import { computed, ref } from 'vue';
 import { useI18n } from 'vue-i18n';
 
 import LogoDiaria from 'src/assets/logo_diaria_campos_login.svg';
 
-const { t } = useI18n();
-
-const form = defineModel({ type: Object, required: true });
 const emit = defineEmits(['update:show-sub-step']);
 
-const loadingSelfie = ref(false);
-const loadingDoc = ref(false);
+const form = defineModel({
+  required: true,
+  type: Object,
+});
 
-const showResult = ref(false);
+const { t } = useI18n();
+
+const loadingDoc = ref(false);
+const loadingSelfie = ref(false);
 const resultError = ref(false);
 const resultIcon = ref('photo_camera');
 const resultMessage = ref('');
 const showDocHint = ref(false);
-
-// foto de documento frente — aguarda verso antes de fechar
+const showResult = ref(false);
 const tempDocFrontFile = ref(null);
 
-const docDone = computed(() => !!form.value.document_front && !!form.value.document_back);
+const docDone = computed(() => (
+  !!form.value.document_back
+  && !!form.value.document_front
+));
+
 const selfieDone = computed(() => !!form.value.selfie);
 
 const base64ToFile = (base64, filename, mimeType = 'image/jpeg') => {
   const byteString = atob(base64);
-
   const ab = new ArrayBuffer(byteString.length);
-
   const ia = new Uint8Array(ab);
 
   for (let i = 0; i < byteString.length; i++) {
     ia[i] = byteString.charCodeAt(i);
   }
 
-  return new File([ab], filename, { type: mimeType });
+  return new File([ab], filename, {
+    type: mimeType,
+  });
 };
 
 const closeResult = () => {
@@ -197,76 +221,94 @@ const closeResult = () => {
 
 const openCamera = async (direction) => {
   const photo = await Camera.getPhoto({
-    resultType: CameraResultType.Base64,
-    source: CameraSource.Camera,
-    quality: 85,
-    direction: direction === 'front' ? CameraDirection.Front : CameraDirection.Rear,
     allowEditing: false,
+    direction: direction === 'front'
+      ? CameraDirection.Front
+      : CameraDirection.Rear,
+    quality: 85,
+    resultType: CameraResultType.Base64,
     saveToGallery: false,
+    source: CameraSource.Camera,
   });
 
-  return base64ToFile(photo.base64String.replace(/\s/g, ''), 'photo.jpg', `image/${photo.format}`);
+  return base64ToFile(
+    photo.base64String.replace(/\s/g, ''),
+    'photo.jpg',
+    `image/${photo.format}`,
+  );
 };
 
-const takeDocFront = async () => {
-  if (loadingDoc.value) return;
-
-  loadingDoc.value = true;
-
+const takeDocBack = async () => {
   try {
     const file = await openCamera('rear');
 
-    tempDocFrontFile.value = file;
+    form.value.document_back = file;
+    form.value.document_front = tempDocFrontFile.value;
+    resultError.value = false;
+    resultIcon.value = 'mdi-card-account-details-outline';
+    resultMessage.value = t('provider.login.steps.step_4.document_success');
+    showDocHint.value = true;
+    showResult.value = true;
+    tempDocFrontFile.value = null;
 
-    await takeDocBack();
+    emit('update:show-sub-step', true);
   } catch (err) {
     const msg = (err?.message || '').toLowerCase();
 
-    if (!msg.includes('cancel') && !msg.includes('dismiss') && !msg.includes('no image')) {
-      resultIcon.value = 'mdi-card-account-details-outline';
+    if (
+      !msg.includes('cancel')
+      && !msg.includes('dismiss')
+      && !msg.includes('no image')
+    ) {
       resultError.value = true;
+      resultIcon.value = 'mdi-card-account-details-outline';
       showDocHint.value = false;
       showResult.value = true;
 
       emit('update:show-sub-step', true);
     }
-  } finally {
-    loadingDoc.value = false;
+
+    tempDocFrontFile.value = null;
   }
 };
 
-const takeDocBack = async () => {
+const takeDocFront = async () => {
+  if (loadingDoc.value) {
+    return;
+  }
+
+  loadingDoc.value = true;
+
   try {
     const file = await openCamera('rear');
 
-    form.value.document_front = tempDocFrontFile.value;
-    form.value.document_back = file;
-    tempDocFrontFile.value = null;
-    resultIcon.value = 'mdi-card-account-details-outline';
-    resultMessage.value = t('provider.login.steps.step_4.document_success');
-    resultError.value = false;
-    showDocHint.value = true;
-    showResult.value = true;
+    tempDocFrontFile.value = file;
 
-    emit('update:show-sub-step', true);
+    await takeDocBack();
   } catch (err) {
     const msg = (err?.message || '').toLowerCase();
 
-    if (!msg.includes('cancel') && !msg.includes('dismiss') && !msg.includes('no image')) {
-      resultIcon.value = 'mdi-card-account-details-outline';
+    if (
+      !msg.includes('cancel')
+      && !msg.includes('dismiss')
+      && !msg.includes('no image')
+    ) {
       resultError.value = true;
+      resultIcon.value = 'mdi-card-account-details-outline';
       showDocHint.value = false;
       showResult.value = true;
 
       emit('update:show-sub-step', true);
     }
-
-    tempDocFrontFile.value = null;
+  } finally {
+    loadingDoc.value = false;
   }
 };
 
 const takeSelfie = async () => {
-  if (loadingSelfie.value) return;
+  if (loadingSelfie.value) {
+    return;
+  }
 
   loadingSelfie.value = true;
 
@@ -274,9 +316,9 @@ const takeSelfie = async () => {
     const file = await openCamera('front');
 
     form.value.selfie = file;
+    resultError.value = false;
     resultIcon.value = 'photo_camera';
     resultMessage.value = t('provider.login.steps.step_4.selfie_success');
-    resultError.value = false;
     showDocHint.value = false;
     showResult.value = true;
 
@@ -284,9 +326,13 @@ const takeSelfie = async () => {
   } catch (err) {
     const msg = (err?.message || '').toLowerCase();
 
-    if (!msg.includes('cancel') && !msg.includes('dismiss') && !msg.includes('no image')) {
-      resultIcon.value = 'photo_camera';
+    if (
+      !msg.includes('cancel')
+      && !msg.includes('dismiss')
+      && !msg.includes('no image')
+    ) {
       resultError.value = true;
+      resultIcon.value = 'photo_camera';
       showDocHint.value = false;
       showResult.value = true;
 
@@ -309,15 +355,17 @@ const takeSelfie = async () => {
   cursor: pointer;
   transition: transform 0.15s ease;
 
-  &:active { transform: scale(0.95); }
-
-  &--pending {
-    background-color: rgba(139, 92, 246, 0.18);
+  &:active {
+    transform: scale(0.95);
   }
 
   &--done {
     background: linear-gradient(-90deg, #ec48d1 5%, #6b11cb 65%, #2574fc 100%);
   }
+
+  &--pending {
+    background-color: rgba(139, 92, 246, 0.18);
+  }
 }
 
 .fs-overlay {
@@ -353,4 +401,4 @@ const takeSelfie = async () => {
     background-color: rgba(139, 92, 246, 0.18);
   }
 }
-</style>
+</style>

+ 80 - 67
src/components/login/LoginStepFivePanel.vue → src/components/login/LoginStep5Panel.vue

@@ -1,7 +1,7 @@
 <template>
   <q-card-section class="no-padding">
-    <div class="text-text font12 fontbold q-mb-sm">
-      {{ $t('provider.login.steps.step_5.daily_price_title') }}
+    <div class="font12 fontbold q-mb-sm text-text">
+      {{ $t("provider.login.steps.step_5.daily_price_title") }}
     </div>
 
     <DefaultCurrencyInput
@@ -10,82 +10,83 @@
       hide-bottom-space
       input-class="text-text font12 fontmedium"
       label=""
+      no-error-icon
       placeholder="R$ 0,00"
       rounded
-      no-error-icon
       :error="!!priceError"
       :error-message="priceError"
       :rules="[validateDailyPrice]"
     />
 
-    <div class="text-center text-grey-7 font10 fontmedium q-mb-md">
+    <div class="font10 fontmedium q-mb-md text-center text-grey-7">
       {{ dailyPriceMinMaxText }}
     </div>
 
-    <q-banner class="bg-blue-1 text-primary q-mb-lg q-pa-md bannerRound">
+    <q-banner class="banner-round bg-blue-1 q-mb-lg q-pa-md text-primary">
       <template #avatar>
         <q-icon color="primary" name="mdi-alert-outline" />
       </template>
 
       <span class="fontbold font12">
-        {{ $t('provider.login.steps.step_5.dont_worry') }}
-      </span> 
+        {{ $t("provider.login.steps.step_5.dont_worry") }}
+      </span>
 
       <span class="font12 q-ml-xs">
-        {{ $t('provider.login.steps.step_5.change_anytime') }}
+        {{ $t("provider.login.steps.step_5.change_anytime") }}
       </span>
     </q-banner>
 
-    <div class="text-text text-center font12 fontmedium q-mb-sm">
-      {{ $t('provider.login.steps.step_5.shorter_services') }}
+    <div class="font12 fontmedium q-mb-sm text-center text-text">
+      {{ $t("provider.login.steps.step_5.shorter_services") }}
     </div>
 
     <div class="row q-col-gutter-sm q-mb-lg q-mt-md">
       <div class="col-4">
         <div class="text-text text-center fontbold font12">
-          {{ $t('provider.login.steps.step_5.up_to_6h') }}
+          {{ $t("provider.login.steps.step_5.up_to_6h") }}
         </div>
 
         <q-input
           :model-value="formatCurrency(form.daily_price_6h)"
           class="bg-surface"
-          input-class="text-text font12 fontmedium"
+          input-class="font12 fontmedium text-text"
           readonly
         />
       </div>
 
       <div class="col-4">
         <div class="text-text text-center fontbold font12">
-          {{ $t('provider.login.steps.step_5.up_to_4h') }}
+          {{ $t("provider.login.steps.step_5.up_to_4h") }}
         </div>
 
         <q-input
           :model-value="formatCurrency(form.daily_price_4h)"
           class="bg-surface"
-          input-class="text-text font12 fontmedium"
+          input-class="font12 fontmedium text-text"
           readonly
         />
       </div>
+
       <div class="col-4 column">
         <div class="text-text text-center fontbold font12">
-          {{ $t('provider.login.steps.step_5.up_to_2h') }}
+          {{ $t("provider.login.steps.step_5.up_to_2h") }}
         </div>
 
         <q-input
           :model-value="formatCurrency(form.daily_price_2h)"
           class="bg-surface"
-          input-class="text-text font12 fontmedium"
+          input-class="font12 fontmedium text-text"
           readonly
         />
       </div>
     </div>
 
-    <div class="text-text q-mb-xs text-center fontbold 12">
-      {{ $t('provider.login.steps.step_5.other_services') }}
+    <div class="font12 fontbold q-mb-xs text-center text-text">
+      {{ $t("provider.login.steps.step_5.other_services") }}
     </div>
 
     <div class="text-grey-7 q-mb-md text-center font10">
-      {{ $t('provider.login.steps.step_5.change_in_profile') }}
+      {{ $t("provider.login.steps.step_5.change_in_profile") }}
     </div>
 
     <div v-if="loadingServiceTypes" class="row justify-center q-mb-lg">
@@ -96,32 +97,38 @@
       <div v-for="service in serviceOptions" :key="service.value" class="col-6">
         <q-checkbox
           :model-value="selectedServices.includes(service.value)"
+          :label="service.label"
           class="q-mb-md text-text font12 fontmedium"
           color="primary"
           keep-color
-          :label="service.label"
-          @update:model-value="(checked) => onServiceToggle(service.value, checked)"
+          @update:model-value="
+            (checked) => onServiceToggle(service.value, checked)
+          "
         />
       </div>
     </div>
 
     <div v-else class="text-grey-7 q-mb-lg font12 fontmedium">
-      {{ $t('provider.login.steps.step_5.no_services') }}
+      {{ $t("provider.login.steps.step_5.no_services") }}
     </div>
 
     <div class="text-grey-7 text-center font12 fontmedium">
-      {{ $t('provider.login.steps.step_5.search_visibility') }}
+      {{ $t("provider.login.steps.step_5.search_visibility") }}
     </div>
   </q-card-section>
 </template>
 
 <script setup>
-import { computed, onMounted, ref, watch } from 'vue';
-import { getProviderDailyPriceMin } from 'src/api/provider';
-import { getPublicServiceTypes } from 'src/api/serviceType';
-import DefaultCurrencyInput from 'src/components/defaults/DefaultCurrencyInput.vue';
+import { computed, onMounted, ref, watch } from "vue";
+import { getProviderDailyPriceMin } from "src/api/provider";
+import { getPublicServiceTypes } from "src/api/serviceType";
+
+import DefaultCurrencyInput from "src/components/defaults/DefaultCurrencyInput.vue";
 
-const form = defineModel({ type: Object, required: true });
+const form = defineModel({
+  required: true,
+  type: Object,
+});
 
 const DEFAULT_DAILY_PRICE_MIN = 100;
 const DAILY_PRICE_MAX = 500;
@@ -130,31 +137,43 @@ const dailyPriceMin = ref(DEFAULT_DAILY_PRICE_MIN);
 const loadingServiceTypes = ref(false);
 const serviceOptions = ref([]);
 
+const dailyPriceMinMaxText = computed(() => {
+  return `Valor mínimo ${formatCurrency(dailyPriceMin.value)}. Valor máximo ${formatCurrency(DAILY_PRICE_MAX)}.`;
+});
+
+const priceError = computed(() =>
+  getDailyPriceError(form.value.daily_price_8h),
+);
+
+const selectedServices = computed(() => {
+  if (!Array.isArray(form.value.services_types_ids)) {
+    return [];
+  }
+
+  return form.value.services_types_ids;
+});
+
 const formatCurrency = (value) => {
   if (value === null || value === undefined || Number.isNaN(Number(value))) {
-    return 'R$ 0,00';
+    return "R$ 0,00";
   }
 
-  return Number(value).toLocaleString('pt-BR', {
-    style: 'currency',
-    currency: 'BRL',
+  return Number(value).toLocaleString("pt-BR", {
+    currency: "BRL",
     minimumFractionDigits: 2,
+    style: "currency",
   });
 };
 
-const dailyPriceMinMaxText = computed(() => {
-  return `Valor mínimo ${formatCurrency(dailyPriceMin.value)}. Valor máximo ${formatCurrency(DAILY_PRICE_MAX)}.`;
-});
-
 const getDailyPriceError = (value) => {
-  if (value === null || value === undefined || value === '') {
-    return 'Este campo é obrigatório';
+  if (value === null || value === undefined || value === "") {
+    return "Este campo é obrigatório";
   }
 
   const numericValue = Number(value);
 
   if (Number.isNaN(numericValue)) {
-    return 'Valor inválido';
+    return "Valor inválido";
   }
 
   if (numericValue < dailyPriceMin.value) {
@@ -165,18 +184,20 @@ const getDailyPriceError = (value) => {
     return `Valor máximo ${formatCurrency(DAILY_PRICE_MAX)}`;
   }
 
-  return '';
+  return "";
 };
 
-const priceError = computed(() => getDailyPriceError(form.value.daily_price_8h));
+const loadDailyPriceMin = async () => {
+  try {
+    const payload = await getProviderDailyPriceMin();
+
+    dailyPriceMin.value = parseDailyPriceMin(payload);
+  } catch (error) {
+    dailyPriceMin.value = DEFAULT_DAILY_PRICE_MIN;
 
-const selectedServices = computed(() => {
-  if (!Array.isArray(form.value.services_types_ids)) {
-    return [];
+    console.error(error);
   }
-
-  return form.value.services_types_ids;
-});
+};
 
 const loadServiceTypes = async () => {
   loadingServiceTypes.value = true;
@@ -200,18 +221,6 @@ const loadServiceTypes = async () => {
   }
 };
 
-const loadDailyPriceMin = async () => {
-  try {
-    const payload = await getProviderDailyPriceMin();
-
-    dailyPriceMin.value = parseDailyPriceMin(payload);
-  } catch (error) {
-    dailyPriceMin.value = DEFAULT_DAILY_PRICE_MIN;
-
-    console.error(error);
-  }
-};
-
 const normalizeSelectedServiceIds = () => {
   if (!Array.isArray(form.value.services_types_ids)) {
     form.value.services_types_ids = [];
@@ -233,15 +242,18 @@ const onServiceToggle = (value, checked) => {
     return;
   }
 
-  form.value.services_types_ids = form.value.services_types_ids.filter((serviceId) => serviceId !== value);
+  form.value.services_types_ids = form.value.services_types_ids.filter(
+    (serviceId) => serviceId !== value,
+  );
 };
 
 const parseDailyPriceMin = (payload) => {
-  const rawValue = payload?.daily_price_min
-    ?? payload?.dailyPriceMin
-    ?? payload?.min
-    ?? payload?.value
-    ?? payload;
+  const rawValue =
+    payload?.daily_price_min ??
+    payload?.dailyPriceMin ??
+    payload?.min ??
+    payload?.value ??
+    payload;
 
   const value = Number(rawValue);
 
@@ -270,7 +282,7 @@ watch(
 
     form.value.daily_price_6h = Number((price * 0.85).toFixed(2));
     form.value.daily_price_4h = Number((price * 0.55).toFixed(2));
-    form.value.daily_price_2h = Number((price * 0.30).toFixed(2));
+    form.value.daily_price_2h = Number((price * 0.3).toFixed(2));
   },
   { immediate: true },
 );
@@ -281,9 +293,10 @@ onMounted(() => {
 });
 </script>
 <style scoped>
-.bannerRound {
+.banner-round {
   border-radius: 18px;
 }
+
 :deep(.custom-currency-input .q-field__control) {
   border-radius: 28px;
 }

+ 91 - 41
src/components/login/LoginStepSixPanel.vue → src/components/login/LoginStep6Panel.vue

@@ -1,19 +1,29 @@
 <template>
   <q-card-section class="no-padding">
-    <div class="text-center text-grey-8 q-mb-md font12 fontbold">
-      {{ $t('provider.login.steps.step_6.title') }}
+    <div class="font12 fontbold q-mb-md text-center text-grey-8">
+      {{ $t("provider.login.steps.step_6.title") }}
     </div>
 
-    <q-card flat class="q-pa-lg q-mb-lg availability-card shadow-card bg-surface">
+    <q-card
+      class="availability-card bg-surface q-mb-lg q-pa-lg shadow-card"
+      flat
+    >
       <div class="row items-start q-mb-md">
-        <q-icon name="mdi-lock-outline" size="20px" color="grey-6" class="q-mr-sm" />
+        <q-icon
+          class="q-mr-sm"
+          color="grey-6"
+          name="mdi-lock-outline"
+          size="20px"
+        />
+
         <div class="col">
           <div class="text-grey-8 line-height-tight font12">
             <span class="fontbold">
-              {{ $t('provider.login.steps.step_6.lock_hint') }} 
+              {{ $t("provider.login.steps.step_6.lock_hint") }}
             </span>
-            <span class="">
-              {{ $t('provider.login.steps.step_6.lock_description') }}
+
+            <span>
+              {{ $t("provider.login.steps.step_6.lock_description") }}
             </span>
           </div>
         </div>
@@ -23,7 +33,11 @@
         <div v-for="day in days" :key="day.value" class="day-column">
           <div
             class="day-header font9 fontbold q-mb-sm text-center"
-            :class="isDayAvailable(day.value) ? 'gradient-diarista-bg text-white' : 'bg-grey-3 text-grey-6'"
+            :class="
+              isDayAvailable(day.value)
+                ? 'gradient-diarista-bg text-white'
+                : 'bg-grey-3 text-grey-6'
+            "
             @click="toggleDay(day.value)"
           >
             {{ day.label }}
@@ -31,60 +45,91 @@
 
           <div
             class="period-button q-mb-sm flex flex-center relative-position"
-            :class="isSelected(day.value, 'morning') ? 'active-morning' : 'inactive-period'"
+            :class="
+              isSelected(day.value, 'morning')
+                ? 'active-morning'
+                : 'inactive-period'
+            "
             @click="togglePeriod(day.value, 'morning')"
           >
-            <q-icon v-if="!isSelected(day.value, 'morning')" name="mdi-lock-outline" size="24px" color="grey-6" class="absolute-center lock-icon" />
-            <span class="period-label font9">{{ $t('provider.login.steps.step_6.morning') }}</span>
+            <q-icon
+              v-if="!isSelected(day.value, 'morning')"
+              class="absolute-center lock-icon"
+              color="grey-6"
+              name="mdi-lock-outline"
+              size="24px"
+            />
+
+            <span class="period-label font9">{{
+              $t("provider.login.steps.step_6.morning")
+            }}</span>
           </div>
 
           <div
             class="period-button flex flex-center relative-position"
-            :class="isSelected(day.value, 'afternoon') ? 'active-afternoon' : 'inactive-period'"
+            :class="
+              isSelected(day.value, 'afternoon')
+                ? 'active-afternoon'
+                : 'inactive-period'
+            "
             @click="togglePeriod(day.value, 'afternoon')"
           >
-            <q-icon v-if="!isSelected(day.value, 'afternoon')" name="mdi-lock-outline" size="24px" color="grey-6" class="absolute-center lock-icon" />
-            <span class="period-label font9">{{ $t('provider.login.steps.step_6.afternoon') }}</span>
+            <q-icon
+              v-if="!isSelected(day.value, 'afternoon')"
+              class="absolute-center lock-icon"
+              color="grey-6"
+              name="mdi-lock-outline"
+              size="24px"
+            />
+
+            <span class="period-label font9">
+              {{ $t("provider.login.steps.step_6.afternoon") }}
+            </span>
           </div>
         </div>
       </div>
 
       <div class="text-grey-6 text-center font9 fontmedium">
-        {{ $t('provider.login.steps.step_6.instruction') }}
+        {{ $t("provider.login.steps.step_6.instruction") }}
       </div>
     </q-card>
 
-    <q-banner rounded class="bg-blue-1 text-primary q-mb-md">
+    <q-banner class="bg-blue-1 q-mb-md text-primary" rounded>
       <template #avatar>
-        <q-icon name="mdi-alert-outline" color="primary" />
+        <q-icon color="primary" name="mdi-alert-outline" />
       </template>
+
       <span class="fontbold font12">
-        {{ $t('provider.login.steps.step_6.dont_worry') }}
+        {{ $t("provider.login.steps.step_6.dont_worry") }}
       </span>
+
       <span class="font12 q-ml-xs">
-        {{ $t('provider.login.steps.step_6.change_anytime') }}
+        {{ $t("provider.login.steps.step_6.change_anytime") }}
       </span>
     </q-banner>
   </q-card-section>
 </template>
 
 <script setup>
-import { onMounted } from 'vue';
+import { onMounted } from "vue";
 
-const form = defineModel({ type: Object, required: true });
+const form = defineModel({
+  required: true,
+  type: Object,
+});
 
 const days = [
-  { value: 0, label: 'DOM' },
-  { value: 1, label: 'SEG' },
-  { value: 2, label: 'TER' },
-  { value: 3, label: 'QUA' },
-  { value: 4, label: 'QUI' },
-  { value: 5, label: 'SEX' },
-  { value: 6, label: 'SÁB' },
+  { value: 0, label: "DOM" },
+  { value: 1, label: "SEG" },
+  { value: 2, label: "TER" },
+  { value: 3, label: "QUA" },
+  { value: 4, label: "QUI" },
+  { value: 5, label: "SEX" },
+  { value: 6, label: "SÁB" },
 ];
 
 const initializeWorkingDays = () => {
-  if (!form.value.working_days || typeof form.value.working_days !== 'object') {
+  if (!form.value.working_days || typeof form.value.working_days !== "object") {
     form.value.working_days = {};
   }
 
@@ -98,24 +143,28 @@ const initializeWorkingDays = () => {
   }
 };
 
-const isSelected = (day, period) => {
-  return !!form.value.working_days?.[day]?.[period];
-};
-
 const isDayAvailable = (day) => {
   const d = form.value.working_days?.[day];
+
   return d?.morning || d?.afternoon;
 };
 
+const isSelected = (day, period) => {
+  return !!form.value.working_days?.[day]?.[period];
+};
+
 const toggleDay = (day) => {
   initializeWorkingDays();
+
   const currentState = isDayAvailable(day);
+
   form.value.working_days[day].morning = !currentState;
   form.value.working_days[day].afternoon = !currentState;
 };
 
 const togglePeriod = (day, period) => {
   initializeWorkingDays();
+
   form.value.working_days[day][period] = !form.value.working_days[day][period];
 };
 
@@ -131,32 +180,32 @@ onMounted(() => {
 
 .days-grid {
   display: flex;
-  justify-content: space-between;
   gap: 4px;
+  justify-content: space-between;
 }
 
 .day-column {
-  flex: 1;
   display: flex;
+  flex: 1;
   flex-direction: column;
 }
 
 .day-header {
-  padding: 2px 0;
   border-radius: 10px;
   cursor: pointer;
+  padding: 2px 0;
   transition: all 0.3s;
 }
 
 .period-button {
+  border-radius: 50%;
+  cursor: pointer;
   height: 36px;
-  width: 36px;
   margin-left: auto;
   margin-right: auto;
-  border-radius: 50%;
-  cursor: pointer;
-  transition: all 0.3s;
   text-align: center;
+  transition: all 0.3s;
+  width: 36px;
 }
 
 .period-label {
@@ -168,7 +217,8 @@ onMounted(() => {
   z-index: 2;
 }
 
-.active-morning, .active-afternoon {
+.active-morning,
+.active-afternoon {
   background-color: #00ff7f;
   color: #000000;
 }

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

@@ -292,6 +292,8 @@
         "title": "Opportunities",
         "banner_text": "Client requests for Diária professionals. View the order details, apply for the service, and if the client accepts, you will receive a notification.",
         "empty": "No opportunities available at the moment.",
+        "empty_alert_bold": "There are no opportunities near you right now.",
+        "empty_alert_text": "Wait for a client request and keep an eye out in case opportunities appear here.",
         "distance_km": "{distance} km from you",
         "details": "View details",
         "currency": "R$ {value}"
@@ -342,7 +344,9 @@
         "copy_address": "Copy address",
         "address_copied": "Address copied!",
         "btn_cancel_service": "Cancel service",
-        "btn_need_help": "I need help"
+        "btn_need_help": "I need help",
+        "empty_alert_bold": "You do not have scheduled services right now.",
+        "empty_alert_text": "Look for opportunities or wait for a client to request a service."
       },
       "cancel_schedule": {
         "title": "Are you sure you want to cancel your booking?",

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

@@ -292,6 +292,8 @@
         "title": "Oportunidades",
         "banner_text": "Pedidos de clientes para os profissionais do Diária. Veja os detalhes do pedido, se candidate ao serviço e se o cliente aceitar, você receberá um aviso.",
         "empty": "No hay oportunidades disponibles en este momento.",
+        "empty_alert_bold": "En este momento no hay oportunidades cerca de ti.",
+        "empty_alert_text": "Espera la solicitud de un cliente y mantente atento por si aparecen oportunidades aquí.",
         "distance_km": "{distance} km de ti",
         "details": "Ver detalles",
         "currency": "R$ {value}"
@@ -342,7 +344,9 @@
         "copy_address": "Copiar dirección",
         "address_copied": "¡Dirección copiada!",
         "btn_cancel_service": "Cancelar servicio",
-        "btn_need_help": "Necesito ayuda"
+        "btn_need_help": "Necesito ayuda",
+        "empty_alert_bold": "En este momento todavía no tienes servicios programados.",
+        "empty_alert_text": "Busca oportunidades o espera a que un cliente solicite un servicio."
       },
       "cancel_schedule": {
         "title": "¿Está seguro de que desea cancelar su cita?",

+ 6 - 2
src/i18n/locales/pt.json

@@ -292,6 +292,8 @@
         "title": "Oportunidades",
         "banner_text": "Pedidos de clientes para os profissionais do Diária. Veja os detalhes do pedido, se candidate ao serviço e se o cliente aceitar, você receberá um aviso.",
         "empty": "Nenhuma oportunidade disponível no momento.",
+        "empty_alert_bold": "No momento não há oportunidade próximas de você.",
+        "empty_alert_text": "Aguarde a solicitação de um cliente e fique atento caso apareçam oportunidades aqui.",
         "distance_km": "{distance} km de você",
         "details": "Ver detalhes",
         "currency": "R$ {value}"
@@ -342,7 +344,9 @@
         "copy_address": "Copiar endereço",
         "address_copied": "Endereço copiado!",
         "btn_cancel_service": "Cancelar serviço",
-        "btn_need_help": "Preciso de ajuda"
+        "btn_need_help": "Preciso de ajuda",
+        "empty_alert_bold": "No momento, você ainda não possui serviços agendados.",
+        "empty_alert_text": "Busque oportunidades ou aguarde um cliente solicitar um serviço."
       },
       "cancel_schedule": {
         "title": "Tem certeza que deseja cancelar seu agendamento?",
@@ -607,7 +611,7 @@
       "greeting_message": "Olá! Como posso te ajudar hoje?"
     },
     "privacy": {
-      "title": "Privacidade",
+      "title": "Bloqueados",
       "description": "Clientes bloqueados",
       "empty_message": "Você não possui clientes bloqueados",
       "empty_sub": "Você pode bloquear clientes que não deseja receber novos pedidos.",

+ 21 - 14
src/pages/LoginPage.vue

@@ -17,10 +17,12 @@
 
       <div v-else key="flow" class="flow-screen">
         <div
-          v-if="steps >= 4 && !showSubStep"
+          v-if="!showSubStep"
           class="flow-header row items-center"
         >
           <q-btn
+            v-if="steps >= 4"
+            class="flow-back-btn"
             color="primary"
             dense
             flat
@@ -48,31 +50,31 @@
             class="flow-content"
             :class="{ 'flow-content--centered': steps <= 2 && !showSubStep }"
           >
-            <LoginStepOnePanel
+            <LoginStep1Panel
               v-if="steps === 1"
               v-model:email="email"
               v-model:phone="phone"
             />
 
-            <LoginStepTwoPanel v-else-if="steps === 2" v-model:code="code" />
+            <LoginStep2Panel v-else-if="steps === 2" v-model:code="code" />
 
-            <LoginStepThreePanel
+            <LoginStep3Panel
               v-else-if="steps === 3"
               v-model="stepThreeForm"
             />
 
-            <LoginStepFourPanel
+            <LoginStep4Panel
               v-else-if="steps === 4"
               v-model="stepFourForm"
               @update:show-sub-step="showSubStep = $event"
             />
 
-            <LoginStepFivePanel
+            <LoginStep5Panel
               v-else-if="steps === 5"
               v-model="stepFiveForm"
             />
 
-            <LoginStepSixPanel v-else-if="steps === 6" v-model="stepSixForm" />
+            <LoginStep6Panel v-else-if="steps === 6" v-model="stepSixForm" />
           </div>
 
           <div v-if="!showSubStep" class="flow-footer">
@@ -125,12 +127,12 @@ import BackgroundLogin from "src/assets/background-login.svg";
 import FotoDiarista from "src/assets/foto_diarista_login.svg";
 import LogoDiariaCampos from "src/assets/logo_diaria_campos_login.svg";
 import LogoLogin from "src/assets/logo_diaria_login.svg";
-import LoginStepFivePanel from "src/components/login/LoginStepFivePanel.vue";
-import LoginStepFourPanel from "src/components/login/LoginStepFourPanel.vue";
-import LoginStepOnePanel from "src/components/login/LoginStepOnePanel.vue";
-import LoginStepSixPanel from "src/components/login/LoginStepSixPanel.vue";
-import LoginStepThreePanel from "src/components/login/LoginStepThreePanel.vue";
-import LoginStepTwoPanel from "src/components/login/LoginStepTwoPanel.vue";
+import LoginStep1Panel from "src/components/login/LoginStep1Panel.vue";
+import LoginStep2Panel from "src/components/login/LoginStep2Panel.vue";
+import LoginStep3Panel from "src/components/login/LoginStep3Panel.vue";
+import LoginStep4Panel from "src/components/login/LoginStep4Panel.vue";
+import LoginStep5Panel from "src/components/login/LoginStep5Panel.vue";
+import LoginStep6Panel from "src/components/login/LoginStep6Panel.vue";
 
 const $q = useQuasar();
 const router = useRouter();
@@ -585,6 +587,11 @@ onMounted(() => {
 
 .flow-header {
   min-height: 36px;
+  padding-left: max(4px, env(safe-area-inset-left));
+}
+
+.flow-back-btn {
+  margin-left: -4px;
 }
 
 .flow-logo {
@@ -636,4 +643,4 @@ onMounted(() => {
     line-height: 1.45;
   }
 }
-</style>
+</style>

+ 87 - 66
src/pages/dashboard/DashboardPage.vue

@@ -1,20 +1,31 @@
 <template>
   <div class="dashboard-page bg-page">
     <template v-if="loading">
-      <div class="row items-center justify-center full-width bg-surface" style="height: 80vh">
+      <div
+        class="bg-surface full-width items-center justify-center row"
+        style="height: 80vh"
+      >
         <q-spinner-dots color="primary" />
       </div>
     </template>
+
     <template v-else>
       <q-pull-to-refresh color="primary" @refresh="onRefresh">
-        <DashboardHeaderBar
-          :data="headerBar"
-          :notifications="notifications"
-        />
+        <DashboardHeaderBar :data="headerBar" :notifications="notifications" />
+
         <DashboardSummaryInfos :data="summaryInfos" />
-        <DashboardPriceSuggest :data="priceSuggestion"/>
-        <DashboardTodayServices v-if="todayServices?.length > 0" :data="todayServices" @refresh="loadDashboard" @rate="openRatingDialog" />
+
+        <DashboardPriceSuggest :data="priceSuggestion" />
+
+        <DashboardTodayServices
+          v-if="todayServices?.length > 0"
+          :data="todayServices"
+          @refresh="loadDashboard"
+          @rate="openRatingDialog"
+        />
+
         <DashboardScrollAreaSchedules />
+
         <DashboardSolicitations
           v-if="solicitations?.length > 0"
           :data="solicitations"
@@ -22,71 +33,75 @@
           @reject="(item) => openDetailsDialog(item, 'confirm-reject')"
           @view-details="(item) => openDetailsDialog(item)"
         />
-        <DashboardNextSchedules v-if="nextSchedules?.length > 0" :data="nextSchedules" @view-details="(item) => openNextScheduleDialog(item)" />
-        <DashboardOpportunities v-if="opportunities?.length > 0" :data="opportunities"/>
+
+        <DashboardNextSchedules
+          :data="nextSchedules"
+          @view-details="(item) => openNextScheduleDialog(item)"
+        />
+
+        <DashboardOpportunities :data="opportunities" />
       </q-pull-to-refresh>
     </template>
   </div>
 </template>
 
 <script setup>
-import DashboardHeaderBar from 'src/components/dashboard/DashboardHeaderBar.vue';
-import DashboardSummaryInfos from 'src/components/dashboard/DashboardSummaryInfos.vue';
-import DashboardPriceSuggest from 'src/components/dashboard/DashboardPriceSuggest.vue';
-import DashboardScrollAreaSchedules from 'src/components/dashboard/DashboardScrollAreaSchedules.vue';
-import DashboardSolicitations from 'src/components/dashboard/DashboardSolicitations.vue';
-import DashboardTodayServices from 'src/components/dashboard/DashboardTodayServices.vue';
-import DashboardNextSchedules from 'src/components/dashboard/DashboardNextSchedules.vue';
-import DashboardOpportunities from 'src/components/dashboard/DashboardOpportunities.vue';
-import SolicitationDetailsDialog from 'src/components/dashboard/SolicitationDetailsDialog.vue';
-import NextSchedulesDetailsDialog from 'src/components/dashboard/NextSchedulesDetailsDialog.vue';
-import ScheduleRatingDialog from 'src/components/dashboard/ScheduleRatingDialog.vue';
-import OpportunityDialog from 'src/pages/opportunities/components/OpportunityDialog.vue';
-import { onMounted, ref, watch } from 'vue';
-import { useQuasar } from 'quasar';
-import { dadosDashboard } from 'src/api/dashboard';
-import { updateScheduleStatus } from 'src/api/schedule';
-import { useRouter, useRoute } from 'vue-router';
+import { dadosDashboard } from "src/api/dashboard";
+import { onMounted, ref, watch } from "vue";
+import { updateScheduleStatus } from "src/api/schedule";
+import { useQuasar } from "quasar";
+import { useRoute, useRouter } from "vue-router";
 
+import DashboardHeaderBar from "src/components/dashboard/DashboardHeaderBar.vue";
+import DashboardNextSchedules from "src/components/dashboard/DashboardNextSchedules.vue";
+import DashboardOpportunities from "src/components/dashboard/DashboardOpportunities.vue";
+import DashboardPriceSuggest from "src/components/dashboard/DashboardPriceSuggest.vue";
+import DashboardScrollAreaSchedules from "src/components/dashboard/DashboardScrollAreaSchedules.vue";
+import DashboardSolicitations from "src/components/dashboard/DashboardSolicitations.vue";
+import DashboardSummaryInfos from "src/components/dashboard/DashboardSummaryInfos.vue";
+import DashboardTodayServices from "src/components/dashboard/DashboardTodayServices.vue";
+import NextSchedulesDetailsDialog from "src/components/dashboard/NextSchedulesDetailsDialog.vue";
+import ScheduleRatingDialog from "src/components/dashboard/ScheduleRatingDialog.vue";
+import SolicitationDetailsDialog from "src/components/dashboard/SolicitationDetailsDialog.vue";
+import OpportunityDialog from "src/pages/opportunities/components/OpportunityDialog.vue";
+
+const $q = useQuasar();
 const router = useRouter();
 const route = useRoute();
 
 const headerBar = ref({});
-const summaryInfos = ref({});
+const loading = ref(true);
+const nextSchedules = ref([]);
+const notifications = ref([]);
+const opportunities = ref([]);
 const priceSuggestion = ref({});
 const solicitations = ref([]);
+const summaryInfos = ref({});
 const todayServices = ref([]);
-const nextSchedules = ref([]);
-const opportunities = ref([]);
-const notifications = ref([]);
-
-const $q = useQuasar();
 
-watch(() => route.query.showSuccessModal, (val) => {
-  if (val === 'true') {
-    $q.dialog({ component: OpportunityDialog })
-      .onDismiss(() => {
-        router.replace({ path: route.path, query: {} });
-      });
+const handleScheduleAction = async (id, status) => {
+  try {
+    await updateScheduleStatus(id, status);
+    await loadDashboard();
+  } catch (e) {
+    console.log(e);
   }
-}, { immediate: true });
-
-const loading = ref(true);
+};
 
 const loadDashboard = async () => {
   const response = await dadosDashboard();
+
   if (response) {
     headerBar.value = response.headerBar;
-    summaryInfos.value = response.summaryInfos;
+    nextSchedules.value = response.nextSchedules ?? [];
+    notifications.value = response.notifications ?? [];
+    opportunities.value = response.opportunities ?? [];
     priceSuggestion.value = response.priceSuggested;
     solicitations.value = response.solicitations ?? [];
+    summaryInfos.value = response.summaryInfos;
     todayServices.value = response.todayServices ?? [];
-    nextSchedules.value = response.nextSchedules ?? [];
-    opportunities.value = response.opportunities ?? [];
-    notifications.value = response.notifications ?? [];
   }
-
-}
+};
 
 const onRefresh = async (done) => {
   try {
@@ -96,30 +111,24 @@ const onRefresh = async (done) => {
   }
 };
 
-const handleScheduleAction = async (id, status) => {
-  try {
-    await updateScheduleStatus(id, status);
-    await loadDashboard();
-  } catch (e) {
-    console.log(e);
-  }
-};
-
-const openDetailsDialog = (solicitation, initialView = 'details') => {
+const openDetailsDialog = (solicitation, initialView = "details") => {
   $q.dialog({
     component: SolicitationDetailsDialog,
-    componentProps: { solicitation, initialView }
+    componentProps: { initialView, solicitation },
   }).onOk(async ({ action, id }) => {
-    await handleScheduleAction(id, action === 'accept' ? 'accepted' : 'rejected');
+    await handleScheduleAction(
+      id,
+      action === "accept" ? "accepted" : "rejected",
+    );
   });
 };
 
 const openNextScheduleDialog = (schedule) => {
   $q.dialog({
     component: NextSchedulesDetailsDialog,
-    componentProps: { schedule }
+    componentProps: { schedule },
   }).onOk(async ({ action }) => {
-    if (action === 'cancelled') {
+    if (action === "cancelled") {
       await loadDashboard();
     }
   });
@@ -128,12 +137,24 @@ const openNextScheduleDialog = (schedule) => {
 const openRatingDialog = (schedule) => {
   $q.dialog({
     component: ScheduleRatingDialog,
-    componentProps: { schedule }
+    componentProps: { schedule },
   }).onOk(() => {
-    loadDashboard()
-  })
+    loadDashboard();
+  });
 };
 
+watch(
+  () => route.query.showSuccessModal,
+  (val) => {
+    if (val === "true") {
+      $q.dialog({ component: OpportunityDialog }).onDismiss(() => {
+        router.replace({ path: route.path, query: {} });
+      });
+    }
+  },
+  { immediate: true },
+);
+
 onMounted(async () => {
   await loadDashboard();
   loading.value = false;
@@ -142,8 +163,8 @@ onMounted(async () => {
 
 <style scoped>
 .dashboard-page {
-  width: 100%;
-  min-height: 100%;
   box-sizing: border-box;
+  min-height: 100%;
+  width: 100%;
 }
 </style>