AddressCompletionPage.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <template>
  2. <q-page class="address-completion-page bg-surface">
  3. <div class="address-completion-inner">
  4. <q-btn
  5. flat
  6. dense
  7. round
  8. color="primary"
  9. icon="mdi-arrow-left"
  10. class="address-completion-back"
  11. @click="router.back()"
  12. />
  13. <div class="address-completion-content">
  14. <div class="text-text">
  15. <span class="text-weight-medium">{{ $t('common.terms.address') }}</span>
  16. </div>
  17. <q-input
  18. :model-value="flowStore.confirmedAddress"
  19. outlined
  20. rounded
  21. readonly
  22. class="bg-surface q-mt-sm q-mb-md"
  23. input-class="text-text"
  24. />
  25. <div class="row q-col-gutter-sm">
  26. <div class="col-4">
  27. <div class="text-text">
  28. <span class="text-weight-medium">{{ $t('common.terms.address_number') }}</span>
  29. </div>
  30. <q-input
  31. v-model="form.number"
  32. outlined
  33. rounded
  34. class="bg-surface q-mt-sm q-mb-md"
  35. placeholder="0000"
  36. input-class="text-text"
  37. />
  38. </div>
  39. <div class="col-8">
  40. <div class="text-text">
  41. <span class="text-weight-medium">{{ $t('common.terms.district') }}</span>
  42. </div>
  43. <q-input
  44. :model-value="flowStore.confirmedDistrict"
  45. outlined
  46. rounded
  47. readonly
  48. class="bg-surface q-mt-sm q-mb-md"
  49. input-class="text-text"
  50. />
  51. </div>
  52. </div>
  53. <div class="row q-col-gutter-sm">
  54. <div class="col-8">
  55. <div class="text-text">
  56. <span class="text-weight-medium">{{ $t('common.terms.city') }}</span>
  57. </div>
  58. <q-input
  59. :model-value="flowStore.confirmedCity"
  60. outlined
  61. rounded
  62. readonly
  63. class="bg-surface q-mt-sm q-mb-md"
  64. input-class="text-text"
  65. />
  66. </div>
  67. <div class="col-4">
  68. <div class="text-text">
  69. <span class="text-weight-medium">{{ $t('common.terms.state') }}</span>
  70. </div>
  71. <q-input
  72. :model-value="flowStore.confirmedState"
  73. outlined
  74. rounded
  75. readonly
  76. class="bg-surface q-mt-sm q-mb-md"
  77. input-class="text-text"
  78. />
  79. </div>
  80. </div>
  81. <q-checkbox
  82. v-model="form.no_complement"
  83. :label="$t('auth.no_complement')"
  84. color="primary"
  85. keep-color
  86. class="q-mb-md text-text"
  87. />
  88. <template v-if="!form.no_complement">
  89. <div class="text-text">
  90. <span class="text-weight-medium">{{ $t('auth.complement') }}</span>
  91. </div>
  92. <q-input
  93. v-model="form.complement"
  94. outlined
  95. rounded
  96. class="bg-surface q-mt-sm q-mb-md"
  97. :placeholder="$t('auth.complement_placeholder')"
  98. input-class="text-text"
  99. />
  100. </template>
  101. <div class="text-text">
  102. <span class="text-weight-medium">{{ $t('auth.address_nickname') }}</span>
  103. </div>
  104. <q-input
  105. v-model="form.nickname"
  106. outlined
  107. rounded
  108. class="bg-surface q-mt-sm q-mb-md"
  109. :placeholder="$t('auth.address_nickname_placeholder')"
  110. input-class="text-text"
  111. />
  112. <div class="text-text">
  113. <span class="text-weight-medium">{{ $t('auth.address_instructions') }}</span>
  114. </div>
  115. <q-input
  116. v-model="form.instructions"
  117. outlined
  118. rounded
  119. class="bg-surface q-mt-sm q-mb-md"
  120. type="textarea"
  121. rows="3"
  122. autogrow
  123. input-class="text-text"
  124. />
  125. <div class="row q-gutter-sm q-mt-xs q-mb-xl">
  126. <q-chip
  127. v-for="type in addressTypes"
  128. :key="type.value"
  129. :selected="form.address_type === type.value"
  130. clickable
  131. color="primary"
  132. :outline="form.address_type !== type.value"
  133. text-color="surface"
  134. :icon="type.icon"
  135. :icon-selected="type.icon"
  136. @click="form.address_type = type.value"
  137. >
  138. {{ type.label }}
  139. </q-chip>
  140. </div>
  141. </div>
  142. <div class="address-completion-footer">
  143. <q-btn
  144. color="primary-button"
  145. :label="$t('auth.confirm_address')"
  146. rounded
  147. padding="14px 16px"
  148. class="full-width"
  149. :loading="submitting"
  150. @click="handleConfirm"
  151. />
  152. </div>
  153. </div>
  154. </q-page>
  155. </template>
  156. <script setup>
  157. import { ref, computed, onMounted } from 'vue';
  158. import { useRouter } from 'vue-router';
  159. import { useQuasar } from 'quasar';
  160. import { useI18n } from 'vue-i18n';
  161. import { useRegistrationFlowStore } from 'src/stores/registrationFlow';
  162. import { useAuth } from 'src/composables/useAuth';
  163. import { createUserAndClient } from 'src/api/user';
  164. const router = useRouter();
  165. const $q = useQuasar();
  166. const { t } = useI18n();
  167. const flowStore = useRegistrationFlowStore();
  168. const { setAuthDataFromPayload } = useAuth();
  169. const submitting = ref(false);
  170. const form = ref({
  171. number: '',
  172. no_complement: false,
  173. complement: '',
  174. nickname: '',
  175. instructions: '',
  176. address_type: 'home',
  177. });
  178. const addressTypes = computed(() => [
  179. { value: 'home', label: t('auth.address_type_home'), icon: 'mdi-home-outline' },
  180. { value: 'commercial', label: t('auth.address_type_commercial'), icon: 'mdi-office-building-outline' },
  181. { value: 'other', label: t('auth.address_type_other'), icon: 'mdi-map-marker-outline' },
  182. ]);
  183. const handleConfirm = async () => {
  184. if (!form.value.no_complement && !form.value.complement?.trim()) {
  185. $q.notify({ type: 'warning', message: t('auth.complement_required') });
  186. return;
  187. }
  188. submitting.value = true;
  189. try {
  190. const payload = {
  191. email: flowStore.email || undefined,
  192. phone: flowStore.phone || undefined,
  193. code: flowStore.code,
  194. zip_code: flowStore.confirmedZipCode || undefined,
  195. address: flowStore.confirmedAddress || undefined,
  196. number: form.value.number || undefined,
  197. district: flowStore.confirmedDistrict || undefined,
  198. city: flowStore.confirmedCity || undefined,
  199. state: flowStore.confirmedState || undefined,
  200. latitude: flowStore.confirmedLat,
  201. longitude: flowStore.confirmedLng,
  202. has_complement: !form.value.no_complement,
  203. complement: form.value.no_complement ? null : (form.value.complement || null),
  204. nickname: form.value.nickname || null,
  205. instructions: form.value.instructions || null,
  206. address_type: form.value.address_type,
  207. };
  208. const response = await createUserAndClient(payload);
  209. if (response.status >= 200 && response.status < 300) {
  210. await setAuthDataFromPayload(response.data.payload);
  211. flowStore.clear();
  212. router.push({ name: 'DashboardPage' });
  213. }
  214. } catch {
  215. $q.notify({ type: 'negative', message: t('auth.register_error') });
  216. } finally {
  217. submitting.value = false;
  218. }
  219. };
  220. onMounted(() => {
  221. if (!flowStore.hasConfirmedLocation() || !flowStore.hasCredentials()) {
  222. router.replace({ name: 'LoginPage' });
  223. return;
  224. }
  225. form.value.number = flowStore.confirmedNumber || '';
  226. });
  227. </script>
  228. <style lang="scss" scoped>
  229. .address-completion-page {
  230. min-height: 100dvh;
  231. display: flex;
  232. justify-content: center;
  233. }
  234. .address-completion-inner {
  235. width: 100%;
  236. max-width: 500px;
  237. min-height: 100dvh;
  238. display: flex;
  239. flex-direction: column;
  240. padding: 16px 20px;
  241. }
  242. .address-completion-back {
  243. align-self: flex-start;
  244. margin-bottom: 12px;
  245. margin-top: env(safe-area-inset-top);
  246. }
  247. .address-completion-content {
  248. flex: 1;
  249. }
  250. .address-type-row {
  251. display: flex;
  252. gap: 10px;
  253. flex-wrap: wrap;
  254. }
  255. .address-completion-footer {
  256. padding: 16px 0 calc(12px + env(safe-area-inset-bottom));
  257. }
  258. </style>