| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638 |
- <!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
- <template>
- <q-page class="login-page bg-surface-dark">
- <Transition name="fade-slide" mode="out-in">
- <div
- v-if="!clicked"
- key="splash"
- class="splash-screen"
- @click="clicked = true"
- >
- <img :src="BackgroundLogin" class="splash-layer splash-layer--bg" />
- <img :src="FotoDiarista" class="splash-layer splash-layer--photo" />
- <img :src="LogoLogin" class="splash-layer splash-layer--logo" />
- </div>
- <div v-else key="flow" class="flow-screen">
- <div v-if="!showSubStep" class="flow-logo q-my-xl">
- <q-img :src="LogoDiariaCampos" style="max-width: 180px" />
- </div>
- <q-form
- ref="loginForm"
- class="flow-form"
- autocorrect="off"
- autocapitalize="off"
- autocomplete="off"
- spellcheck="false"
- @submit="onSubmit"
- >
- <div
- class="flow-content"
- :class="{ 'flow-content--centered': steps <= 2 && !showSubStep }"
- >
- <LoginStepOnePanel
- v-if="steps === 1"
- v-model:email="email"
- v-model:phone="phone"
- />
- <LoginStepTwoPanel v-else-if="steps === 2" v-model:code="code" />
- <LoginStepThreePanel
- v-else-if="steps === 3"
- v-model="stepThreeForm"
- />
- <LoginStepFourPanel
- v-else-if="steps === 4"
- v-model="stepFourForm"
- @update:show-sub-step="showSubStep = $event"
- />
- <LoginStepFivePanel
- v-else-if="steps === 5"
- v-model="stepFiveForm"
- />
- <q-card-section v-else-if="steps === 6" class="no-padding">
- <div
- class="text-subtitle1 text-center text-weight-bold text-text q-mb-md"
- >
- Dados bancários
- </div>
- <div class="text-caption text-grey-7 text-center q-mb-md">
- Informe uma conta vinculada ao mesmo CPF do cadastro para
- receber os pagamentos.
- </div>
- <div class="row q-mb-sm items-center">
- <q-radio
- v-model="stepSixForm.account_type"
- val="checking"
- label="Conta corrente"
- color="primary"
- keep-color
- class="q-mr-lg text-text"
- />
- <q-radio
- v-model="stepSixForm.account_type"
- val="savings"
- label="Poupança"
- color="primary"
- keep-color
- class="text-text"
- />
- </div>
- <div class="text-text">
- <span class="text-weight-medium">Código do banco</span>
- </div>
- <q-input
- v-model="stepSixForm.bank"
- no-error-icon
- outlined
- rounded
- class="bg-surface q-mt-sm q-mb-md"
- input-class="text-text"
- placeholder="Ex: 001"
- hide-bottom-space
- :rules="[requiredRule]"
- lazy-rules
- maxlength="20"
- />
- <div class="row q-col-gutter-sm">
- <div class="col-8">
- <div class="text-text">
- <span class="text-weight-medium">Agência</span>
- </div>
- <q-input
- v-model="stepSixForm.branch_number"
- no-error-icon
- outlined
- rounded
- class="bg-surface q-mt-sm q-mb-md"
- input-class="text-text"
- placeholder="0000"
- hide-bottom-space
- :rules="[requiredRule]"
- lazy-rules
- maxlength="20"
- />
- </div>
- <div class="col-4">
- <div class="text-text">
- <span class="text-weight-medium">Dígito</span>
- </div>
- <q-input
- v-model="stepSixForm.branch_check_digit"
- no-error-icon
- outlined
- rounded
- class="bg-surface q-mt-sm q-mb-md"
- input-class="text-text"
- placeholder="0"
- hide-bottom-space
- lazy-rules
- maxlength="10"
- />
- </div>
- </div>
- <div class="row q-col-gutter-sm">
- <div class="col-8">
- <div class="text-text">
- <span class="text-weight-medium">Conta</span>
- </div>
- <q-input
- v-model="stepSixForm.account_number"
- no-error-icon
- outlined
- rounded
- class="bg-surface q-mt-sm q-mb-md"
- input-class="text-text"
- placeholder="000000"
- hide-bottom-space
- :rules="[requiredRule]"
- lazy-rules
- maxlength="20"
- />
- </div>
- <div class="col-4">
- <div class="text-text">
- <span class="text-weight-medium">Dígito</span>
- </div>
- <q-input
- v-model="stepSixForm.account_check_digit"
- no-error-icon
- outlined
- rounded
- class="bg-surface q-mt-sm q-mb-md"
- input-class="text-text"
- placeholder="0"
- hide-bottom-space
- :rules="[requiredRule]"
- lazy-rules
- maxlength="10"
- />
- </div>
- </div>
- <div class="text-text">
- <span class="text-weight-medium">Chave Pix</span>
- </div>
- <q-input
- v-model="stepSixForm.pix_key"
- no-error-icon
- outlined
- rounded
- class="bg-surface q-mt-sm q-mb-md"
- input-class="text-text"
- placeholder="Opcional"
- hide-bottom-space
- lazy-rules
- maxlength="255"
- />
- </q-card-section>
- <LoginStepSixPanel
- v-else-if="steps === 7"
- v-model="stepSevenForm"
- />
- <div
- v-else-if="steps === 8"
- class="column items-center justify-center q-gutter-md text-center"
- >
- <q-icon name="mdi-clock-outline" size="64px" color="warning" />
- <div class="text-h6 text-text">
- {{ $t("provider.login.pending_approval.title") }}
- </div>
- <div class="text-body2 text-grey-5">
- {{ $t("provider.login.pending_approval.description") }}
- </div>
- </div>
- </div>
- <div v-if="!showSubStep && steps !== 8" class="flow-footer">
- <q-btn
- color="primary-button"
- :label="actionLabel"
- rounded
- padding="8px 16px"
- type="submit"
- class="full-width"
- :loading="submitting"
- >
- <template #loading>
- <q-spinner />
- </template>
- </q-btn>
- </div>
- </q-form>
- </div>
- </Transition>
- </q-page>
- </template>
- <script setup>
- import { computed, ref } from "vue";
- import { useQuasar } from "quasar";
- import { useRouter } from "vue-router";
- import { useI18n } from "vue-i18n";
- import { createUserAndProvider, sendCode, validateCode } from "src/api/user";
- import { useAuth } from "src/composables/useAuth";
- import BackgroundLogin from "src/assets/background-login.svg";
- import FotoDiarista from "src/assets/foto_diarista_login.svg";
- import LogoLogin from "src/assets/logo_diaria_login.svg";
- import LogoDiariaCampos from "src/assets/logo_diaria_campos_login.svg";
- import LoginStepOnePanel from "src/components/login/LoginStepOnePanel.vue";
- import LoginStepTwoPanel from "src/components/login/LoginStepTwoPanel.vue";
- import LoginStepThreePanel from "src/components/login/LoginStepThreePanel.vue";
- import LoginStepFourPanel from "src/components/login/LoginStepFourPanel.vue";
- import LoginStepFivePanel from "src/components/login/LoginStepFivePanel.vue";
- import LoginStepSixPanel from "src/components/login/LoginStepSixPanel.vue";
- const { t } = useI18n();
- const $q = useQuasar();
- const router = useRouter();
- const { setAuthDataFromPayload } = useAuth();
- const clicked = ref(false);
- const showSubStep = ref(false);
- const steps = ref(1);
- const submitting = ref(false);
- const loginForm = ref(null);
- const isLogin = ref(false);
- const email = ref("");
- const phone = ref("");
- const code = ref("");
- const stepThreeForm = ref({
- name: "",
- phone: "",
- email: "",
- rg: "",
- document: "",
- birth_date: "",
- zip_code: "",
- address: "",
- complement: "",
- no_complement: false,
- city: "",
- state: "",
- address_type: "home",
- nickname: "Principal",
- instructions: "",
- });
- const stepFourForm = ref({
- selfie: null,
- document_front: null,
- document_back: null,
- });
- const stepFiveForm = ref({
- daily_price_8h: null,
- daily_price_6h: null,
- daily_price_4h: null,
- daily_price_2h: null,
- services_types_ids: [],
- });
- const stepSixForm = ref({
- account_type: "checking",
- bank: "",
- branch_number: "",
- branch_check_digit: "",
- account_number: "",
- account_check_digit: "",
- pix_key: "",
- });
- const stepSevenForm = ref({
- working_days: {},
- });
- const actionLabel = computed(() => {
- if (steps.value === 1) return t("provider.login.steps.step_1.action");
- if (steps.value === 2) return t("provider.login.steps.step_2.action");
- if (steps.value === 7) return t("provider.login.steps.step_6.action");
- return t("provider.login.steps.step_3.action");
- });
- const requiredRule = (value) => !!value || t("validation.rules.required");
- const toISODate = (value) => {
- const matches = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value || "");
- if (!matches) return null;
- return `${matches[3]}-${matches[2]}-${matches[1]}`;
- };
- const mapWorkingDays = () => {
- const mapped = [];
- const workingDays = stepSevenForm.value.working_days || {};
- Object.entries(workingDays).forEach(([dayKey, periods]) => {
- if (periods?.morning) {
- mapped.push({ day: Number(dayKey), period: "morning" });
- }
- if (periods?.afternoon) {
- mapped.push({ day: Number(dayKey), period: "afternoon" });
- }
- });
- return mapped;
- };
- const hasWorkingDaySelected = () => {
- return mapWorkingDays().length > 0;
- };
- const normalizeCurrency = (value) => {
- const numberValue = Number(value);
- return Number.isFinite(numberValue) ? numberValue : 0;
- };
- const validateCurrentStep = async () => {
- const isValid = await loginForm.value?.validate();
- if (!isValid) {
- return false;
- }
- if(steps.value === 4) {
- const hasSelfie = !!stepFourForm.value.selfie;
- const hasDocumentFront = !!stepFourForm.value.document_front;
- const hasDocumentBack = !!stepFourForm.value.document_back;
- if (!hasSelfie || !hasDocumentFront || !hasDocumentBack) {
- $q.notify({
- type: "negative",
- message: t("provider.login.steps.step_4.upload_all_photos"),
- });
- return false;
- }
- }
- if (steps.value === 5) {
- const dailyPrice8h = normalizeCurrency(stepFiveForm.value.daily_price_8h);
- if (dailyPrice8h < 100 || dailyPrice8h > 500) {
- $q.notify({
- type: "negative",
- message: "Informe uma diária entre R$ 100,00 e R$ 500,00.",
- });
- return false;
- }
- }
- if (steps.value === 7 && !hasWorkingDaySelected()) {
- $q.notify({
- type: "negative",
- message: t("provider.login.steps.step_6.select_at_least_one"),
- });
- return false;
- }
- return true;
- };
- const sendValidationCode = async () => {
- const response = await sendCode(email.value, phone.value);
- if (response.status === 201) {
- steps.value = 2;
- }
- return response;
- };
- const validateCodeInput = async () => {
- const response = await validateCode(
- email.value,
- phone.value,
- code.value,
- isLogin.value,
- );
- if (response.status === 200) {
- if (isLogin.value === true) {
- await setAuthDataFromPayload(response.data.payload);
- router.push({ name: "DashboardPage" });
- return;
- }
- stepThreeForm.value.email = email.value;
- stepThreeForm.value.phone = phone.value;
- steps.value = 3;
- }
- };
- const registerUserAndProvider = async () => {
- const workingDays = mapWorkingDays();
- const form = new FormData();
- const append = (key, val) => {
- if (val === null || val === undefined) return;
- if (typeof val === 'boolean') form.append(key, val ? '1' : '0');
- else form.append(key, val);
- };
- append('name', stepThreeForm.value.name);
- append('email', stepThreeForm.value.email || email.value);
- append('phone', stepThreeForm.value.phone || phone.value);
- append('code', code.value);
- append('rg', stepThreeForm.value.rg);
- append('document', stepThreeForm.value.document);
- append('birth_date', toISODate(stepThreeForm.value.birth_date));
- append('zip_code', stepThreeForm.value.zip_code);
- append('address', stepThreeForm.value.address);
- append('has_complement', !stepThreeForm.value.no_complement);
- append('complement', stepThreeForm.value.no_complement ? null : stepThreeForm.value.complement);
- append('nickname', stepThreeForm.value.nickname);
- append('instructions', stepThreeForm.value.instructions);
- append('city', stepThreeForm.value.city);
- append('state', stepThreeForm.value.state);
- append('address_type', stepThreeForm.value.address_type);
- append('daily_price_8h', Number(stepFiveForm.value.daily_price_8h));
- append('daily_price_6h', Number(stepFiveForm.value.daily_price_6h));
- append('daily_price_4h', Number(stepFiveForm.value.daily_price_4h));
- append('daily_price_2h', Number(stepFiveForm.value.daily_price_2h));
- (stepFiveForm.value.services_types_ids ?? []).forEach(id => form.append('services_types_ids[]', id));
- workingDays.forEach((wd, i) => {
- form.append(`working_days[${i}][day]`, wd.day);
- form.append(`working_days[${i}][period]`, wd.period);
- });
- form.append('selfie', stepFourForm.value.selfie);
- form.append('document_front', stepFourForm.value.document_front);
- form.append('document_back', stepFourForm.value.document_back);
- const response = await createUserAndProvider(form);
- if (response.status === 200) {
- steps.value = 7;
- }
- };
- const onSubmit = async () => {
- if (showSubStep.value) return;
- const isValid = await loginForm.value.validate();
- if (!isValid) return;
- submitting.value = true;
- try {
- switch (steps.value) {
- case 1: {
- const response = await sendValidationCode();
- isLogin.value = response?.data?.payload?.isLogin === true;
- break;
- }
- case 2:
- await validateCodeInput();
- break;
- case 3: {
- if (await validateCurrentStep()) {
- steps.value = 4;
- }
- break;
- }
- case 4: {
- if (await validateCurrentStep()) {
- steps.value = 5;
- }
- break;
- }
- case 5: {
- if (await validateCurrentStep()) {
- steps.value = 6;
- }
- break;
- }
- case 6: {
- if (await validateCurrentStep()) {
- steps.value = 7;
- }
- break;
- }
- case 7: {
- if (await validateCurrentStep()) {
- await registerUserAndProvider();
- }
- break;
- }
- default:
- break;
- }
- } catch (error) {
- console.error(error);
- } finally {
- submitting.value = false;
- }
- };
- </script>
- <style lang="scss" scoped>
- .fade-slide-enter-active,
- .fade-slide-leave-active {
- transition:
- opacity 0.35s ease,
- transform 0.35s ease;
- }
- .fade-slide-enter-from {
- opacity: 0;
- transform: translateY(6px);
- }
- .fade-slide-leave-to {
- opacity: 0;
- transform: translateY(-6px);
- }
- .login-page {
- min-height: 100vh;
- display: flex;
- justify-content: center;
- background: var(--q-surface-dark);
- }
- .splash-screen {
- position: relative;
- width: 100vw;
- min-height: 100vh;
- overflow: hidden;
- cursor: pointer;
- .splash-layer {
- position: absolute;
- &--bg {
- inset: 0;
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- &--photo {
- inset: 0;
- width: 100%;
- height: 100%;
- object-fit: cover;
- opacity: 0.15;
- mix-blend-mode: multiply;
- }
- &--logo {
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 180px;
- z-index: 1;
- }
- }
- }
- .flow-screen {
- display: flex;
- flex-direction: column;
- width: 100%;
- max-width: 500px;
- min-height: 100vh;
- padding: 16px 20px;
- background: var(--q-surface-dark);
- }
- .flow-header {
- min-height: 36px;
- }
- .flow-logo {
- display: flex;
- justify-content: center;
- padding: 12px 0 20px;
- }
- .flow-form {
- flex: 1;
- display: flex;
- flex-direction: column;
- min-height: 0;
- }
- .flow-content {
- flex: 1;
- overflow-y: auto;
- &--centered {
- display: flex;
- align-items: center;
- justify-content: center;
- }
- }
- .flow-footer {
- padding: 20px 0 12px;
- }
- </style>
|