LoginPage.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. <!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
  2. <template>
  3. <q-page class="login-page bg-surface-dark">
  4. <Transition name="fade-slide" mode="out-in">
  5. <div v-if="!clicked" key="splash" class="splash-screen" @click="clicked = true">
  6. <img :src="BackgroundLogin" class="splash-layer splash-layer--bg" />
  7. <img :src="FotoDiarista" class="splash-layer splash-layer--photo" />
  8. <img :src="LogoLogin" class="splash-layer splash-layer--logo" />
  9. </div>
  10. <div v-else key="flow" class="flow-screen">
  11. <div v-if="!showSubStep" class="flow-logo q-my-xl">
  12. <q-img :src="LogoDiariaCampos" style="max-width: 180px" />
  13. </div>
  14. <q-form
  15. ref="loginForm"
  16. class="flow-form"
  17. autocorrect="off"
  18. autocapitalize="off"
  19. autocomplete="off"
  20. spellcheck="false"
  21. @submit="onSubmit"
  22. >
  23. <div class="flow-content" :class="{ 'flow-content--centered': steps <= 2 && !showSubStep }">
  24. <LoginStepOnePanel v-if="steps === 1" v-model:email="email" v-model:phone="phone" />
  25. <LoginStepTwoPanel v-else-if="steps === 2" v-model:code="code" />
  26. <LoginStepThreePanel v-else-if="steps === 3" v-model="stepThreeForm" />
  27. <LoginStepFourPanel
  28. v-else-if="steps === 4"
  29. v-model="stepFourForm"
  30. @update:show-sub-step="showSubStep = $event"
  31. />
  32. <LoginStepFivePanel v-else-if="steps === 5" v-model="stepFiveForm" />
  33. <LoginStepSixPanel v-else-if="steps === 6" v-model="stepSixForm" />
  34. <div v-else-if="steps === 7" class="column items-center justify-center q-gutter-md text-center">
  35. <q-icon name="mdi-clock-outline" size="64px" color="warning" />
  36. <div class="text-h6 text-text">{{ $t('provider.login.pending_approval.title') }}</div>
  37. <div class="text-body2 text-grey-5">{{ $t('provider.login.pending_approval.description') }}</div>
  38. </div>
  39. </div>
  40. <div v-if="!showSubStep && steps !== 7" class="flow-footer">
  41. <q-btn
  42. color="primary-button"
  43. :label="actionLabel"
  44. rounded
  45. padding="8px 16px"
  46. type="submit"
  47. class="full-width"
  48. :loading="submitting"
  49. >
  50. <template #loading>
  51. <q-spinner />
  52. </template>
  53. </q-btn>
  54. </div>
  55. </q-form>
  56. </div>
  57. </Transition>
  58. </q-page>
  59. </template>
  60. <script setup>
  61. import { computed, ref } from 'vue';
  62. import { useQuasar } from 'quasar';
  63. import { useRouter } from 'vue-router';
  64. import { useI18n } from 'vue-i18n';
  65. import { createUserAndProvider, sendCode, validateCode } from 'src/api/user';
  66. import { useAuth } from 'src/composables/useAuth';
  67. import BackgroundLogin from 'src/assets/background-login.svg';
  68. import FotoDiarista from 'src/assets/foto_diarista_login.svg';
  69. import LogoLogin from 'src/assets/logo_diaria_login.svg';
  70. import LogoDiariaCampos from 'src/assets/logo_diaria_campos_login.svg';
  71. import LoginStepOnePanel from 'src/components/login/LoginStepOnePanel.vue';
  72. import LoginStepTwoPanel from 'src/components/login/LoginStepTwoPanel.vue';
  73. import LoginStepThreePanel from 'src/components/login/LoginStepThreePanel.vue';
  74. import LoginStepFourPanel from 'src/components/login/LoginStepFourPanel.vue';
  75. import LoginStepFivePanel from 'src/components/login/LoginStepFivePanel.vue';
  76. import LoginStepSixPanel from 'src/components/login/LoginStepSixPanel.vue';
  77. const { t } = useI18n();
  78. const $q = useQuasar();
  79. const router = useRouter();
  80. const { setAuthDataFromPayload } = useAuth();
  81. const clicked = ref(false);
  82. const showSubStep = ref(false);
  83. const steps = ref(1);
  84. const submitting = ref(false);
  85. const loginForm = ref(null);
  86. const isLogin = ref(false);
  87. const email = ref('');
  88. const phone = ref('');
  89. const code = ref('');
  90. const stepThreeForm = ref({
  91. name: '',
  92. phone: '',
  93. email: '',
  94. rg: '',
  95. document: '',
  96. birth_date: '',
  97. zip_code: '',
  98. address: '',
  99. complement: '',
  100. no_complement: false,
  101. city: '',
  102. state: '',
  103. address_type: 'home',
  104. nickname: 'Principal',
  105. instructions: '',
  106. });
  107. const stepFourForm = ref({
  108. selfie_file: null,
  109. selfie_base64: '',
  110. document_front_file: null,
  111. document_front_base64: '',
  112. document_back_file: null,
  113. document_back_base64: '',
  114. });
  115. const stepFiveForm = ref({
  116. daily_price_8h: null,
  117. daily_price_6h: null,
  118. daily_price_4h: null,
  119. daily_price_2h: null,
  120. services_types_ids: [],
  121. });
  122. const stepSixForm = ref({
  123. working_days: {},
  124. });
  125. const actionLabel = computed(() => {
  126. if (steps.value === 1) return t('provider.login.steps.step_1.action');
  127. if (steps.value === 2) return t('provider.login.steps.step_2.action');
  128. if (steps.value === 6) return t('provider.login.steps.step_6.action');
  129. return t('provider.login.steps.step_3.action');
  130. });
  131. const toISODate = (value) => {
  132. const matches = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value || '');
  133. if (!matches) return null;
  134. return `${matches[3]}-${matches[2]}-${matches[1]}`;
  135. };
  136. const mapWorkingDays = () => {
  137. const mapped = [];
  138. const workingDays = stepSixForm.value.working_days || {};
  139. Object.entries(workingDays).forEach(([dayKey, periods]) => {
  140. if (periods?.morning) {
  141. mapped.push({ day: Number(dayKey), period: 'morning' });
  142. }
  143. if (periods?.afternoon) {
  144. mapped.push({ day: Number(dayKey), period: 'afternoon' });
  145. }
  146. });
  147. return mapped;
  148. };
  149. const hasWorkingDaySelected = () => {
  150. return mapWorkingDays().length > 0;
  151. };
  152. const validateCurrentStep = async () => {
  153. const isValid = await loginForm.value?.validate();
  154. if (!isValid) {
  155. return false;
  156. }
  157. if(steps.value === 4) {
  158. const hasSelfie = !!stepFourForm.value.selfie_base64;
  159. const hasDocumentFront = !!stepFourForm.value.document_front_base64;
  160. const hasDocumentBack = !!stepFourForm.value.document_back_base64;
  161. if (!hasSelfie || !hasDocumentFront || !hasDocumentBack) {
  162. $q.notify({
  163. type: 'negative',
  164. message: t('provider.login.steps.step_4.upload_all_photos'),
  165. });
  166. return false;
  167. }
  168. }
  169. if (steps.value === 6 && !hasWorkingDaySelected()) {
  170. $q.notify({
  171. type: 'negative',
  172. message: t('provider.login.steps.step_6.select_at_least_one'),
  173. });
  174. return false;
  175. }
  176. return true;
  177. };
  178. const sendValidationCode = async () => {
  179. const response = await sendCode(email.value, phone.value);
  180. if (response.status === 201) {
  181. steps.value = 2;
  182. }
  183. return response;
  184. };
  185. const validateCodeInput = async () => {
  186. const response = await validateCode(email.value, phone.value, code.value, isLogin.value);
  187. if (response.status === 200) {
  188. if (isLogin.value === true) {
  189. await setAuthDataFromPayload(response.data.payload);
  190. router.push({ name: 'DashboardPage' });
  191. return;
  192. }
  193. stepThreeForm.value.email = email.value;
  194. stepThreeForm.value.phone = phone.value;
  195. steps.value = 3;
  196. }
  197. };
  198. const registerUserAndProvider = async () => {
  199. const workingDays = mapWorkingDays();
  200. const payload = {
  201. ...stepThreeForm.value,
  202. email: stepThreeForm.value.email || email.value,
  203. phone: stepThreeForm.value.phone || phone.value,
  204. code: code.value,
  205. birth_date: toISODate(stepThreeForm.value.birth_date),
  206. has_complement: !stepThreeForm.value.no_complement,
  207. complement: stepThreeForm.value.no_complement ? null : stepThreeForm.value.complement,
  208. selfie_base64: stepFourForm.value.selfie_base64,
  209. document_front_base64: stepFourForm.value.document_front_base64,
  210. document_back_base64: stepFourForm.value.document_back_base64,
  211. daily_price_8h: Number(stepFiveForm.value.daily_price_8h),
  212. daily_price_6h: Number(stepFiveForm.value.daily_price_6h),
  213. daily_price_4h: Number(stepFiveForm.value.daily_price_4h),
  214. daily_price_2h: Number(stepFiveForm.value.daily_price_2h),
  215. services_types_ids: stepFiveForm.value.services_types_ids,
  216. working_days: workingDays,
  217. };
  218. const response = await createUserAndProvider(payload);
  219. if (response.status === 200) {
  220. steps.value = 7;
  221. }
  222. };
  223. const onSubmit = async () => {
  224. if (showSubStep.value) return; // Não submete o form principal se estiver em um sub-passo
  225. const isValid = await loginForm.value.validate();
  226. if (!isValid) return;
  227. submitting.value = true;
  228. try {
  229. switch (steps.value) {
  230. case 1: {
  231. const response = await sendValidationCode();
  232. isLogin.value = response?.data?.payload?.isLogin === true;
  233. break;
  234. }
  235. case 2:
  236. await validateCodeInput();
  237. break;
  238. case 3: {
  239. if (await validateCurrentStep()) {
  240. steps.value = 4;
  241. }
  242. break;
  243. }
  244. case 4: {
  245. if (await validateCurrentStep()) {
  246. steps.value = 5;
  247. }
  248. break;
  249. }
  250. case 5: {
  251. if (await validateCurrentStep()) {
  252. steps.value = 6;
  253. }
  254. break;
  255. }
  256. case 6: {
  257. if (await validateCurrentStep()) {
  258. await registerUserAndProvider();
  259. }
  260. break;
  261. }
  262. default:
  263. break;
  264. }
  265. } catch (error) {
  266. console.error(error);
  267. } finally {
  268. submitting.value = false;
  269. }
  270. };
  271. </script>
  272. <style lang="scss" scoped>
  273. .fade-slide-enter-active,
  274. .fade-slide-leave-active {
  275. transition: opacity 0.35s ease, transform 0.35s ease;
  276. }
  277. .fade-slide-enter-from {
  278. opacity: 0;
  279. transform: translateY(6px);
  280. }
  281. .fade-slide-leave-to {
  282. opacity: 0;
  283. transform: translateY(-6px);
  284. }
  285. .login-page {
  286. min-height: 100vh;
  287. display: flex;
  288. justify-content: center;
  289. background: var(--q-surface-dark);
  290. }
  291. .splash-screen {
  292. position: relative;
  293. width: 100vw;
  294. min-height: 100vh;
  295. overflow: hidden;
  296. cursor: pointer;
  297. .splash-layer {
  298. position: absolute;
  299. &--bg {
  300. inset: 0;
  301. width: 100%;
  302. height: 100%;
  303. object-fit: cover;
  304. }
  305. &--photo {
  306. inset: 0;
  307. width: 100%;
  308. height: 100%;
  309. object-fit: cover;
  310. opacity: 0.15;
  311. mix-blend-mode: multiply;
  312. }
  313. &--logo {
  314. top: 50%;
  315. left: 50%;
  316. transform: translate(-50%, -50%);
  317. width: 180px;
  318. z-index: 1;
  319. }
  320. }
  321. }
  322. .flow-screen {
  323. display: flex;
  324. flex-direction: column;
  325. width: 100%;
  326. max-width: 500px;
  327. min-height: 100vh;
  328. padding: 16px 20px;
  329. background: var(--q-surface-dark);
  330. }
  331. .flow-header {
  332. min-height: 36px;
  333. }
  334. .flow-logo {
  335. display: flex;
  336. justify-content: center;
  337. padding: 12px 0 20px;
  338. }
  339. .flow-form {
  340. flex: 1;
  341. display: flex;
  342. flex-direction: column;
  343. min-height: 0;
  344. }
  345. .flow-content {
  346. flex: 1;
  347. overflow-y: auto;
  348. &--centered {
  349. display: flex;
  350. align-items: center;
  351. justify-content: center;
  352. }
  353. }
  354. .flow-footer {
  355. padding: 20px 0 12px;
  356. }
  357. </style>