Cambios semana 19-12
This commit is contained in:
parent
0f5b2627ec
commit
a7143e3048
|
@ -14,7 +14,6 @@
|
|||
"axios": "^1.2.1",
|
||||
"pinia": "^2.0.11",
|
||||
"quasar": "^2.6.0",
|
||||
"swiper": "^11.0.5",
|
||||
"v-mask": "^2.3.0",
|
||||
"vee-validate": "^4.12.2",
|
||||
"vue": "^3.0.0",
|
||||
|
@ -23,6 +22,7 @@
|
|||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.23.6",
|
||||
"@faker-js/faker": "^8.3.1",
|
||||
"@quasar/app-vite": "^1.3.0",
|
||||
"@types/node": "^12.20.21",
|
||||
|
@ -51,6 +51,24 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.23.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
|
||||
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz",
|
||||
|
@ -62,6 +80,20 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
|
||||
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.23.4",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
|
||||
|
@ -5287,24 +5319,6 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/swiper": {
|
||||
"version": "11.0.5",
|
||||
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.0.5.tgz",
|
||||
"integrity": "sha512-rhCwupqSyRnWrtNzWzemnBLMoyYuoDgGgspAm/8iBD3jCvAWycPLH4Z3TB0O5520DHLzMx94yUMH/B9Efpa48w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/swiperjs"
|
||||
},
|
||||
{
|
||||
"type": "open_collective",
|
||||
"url": "http://opencollective.com/swiper"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 4.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/table": {
|
||||
"version": "6.8.1",
|
||||
"resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
|
||||
|
@ -5383,6 +5397,15 @@
|
|||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
"dev": "quasar dev -m ssr",
|
||||
"backend": "json-server -p 5000 -d 3000 -w src/services/json-server/db.json",
|
||||
"build": "quasar build -m ssr",
|
||||
"start:build": "npm run build && cd dist/ssr && npm i && npm run start"
|
||||
"start:build": "npm run build && cd dist/ssr && npm i && npm run start",
|
||||
"typecheck": "tsc --project tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.16.4",
|
||||
|
@ -20,7 +21,6 @@
|
|||
"axios": "^1.2.1",
|
||||
"pinia": "^2.0.11",
|
||||
"quasar": "^2.6.0",
|
||||
"swiper": "^11.0.5",
|
||||
"v-mask": "^2.3.0",
|
||||
"vee-validate": "^4.12.2",
|
||||
"vue": "^3.0.0",
|
||||
|
@ -29,6 +29,7 @@
|
|||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.23.6",
|
||||
"@faker-js/faker": "^8.3.1",
|
||||
"@quasar/app-vite": "^1.3.0",
|
||||
"@types/node": "^12.20.21",
|
||||
|
|
|
@ -50,9 +50,9 @@
|
|||
import { toTypedSchema } from '@vee-validate/zod';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useFormStore } from 'src/stores/forms';
|
||||
import { availabilitySchema } from 'src/utils/zod/schemas/availabilitySchema';
|
||||
import { useForm } from 'vee-validate';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { z } from 'zod';
|
||||
import IconCalendar from '../icons/IconCalendar.vue';
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -70,34 +70,7 @@ export default defineComponent({
|
|||
const proxyDate = ref(fullCurrentDate);
|
||||
|
||||
const validationSchema = toTypedSchema(
|
||||
z.object({
|
||||
date: z.string().refine((val) => {
|
||||
const [day, month, year] = val.split('/');
|
||||
console.log({ day, month, year });
|
||||
const regex = /\//g;
|
||||
const valWithoutSlash = val.replace(regex, '');
|
||||
/* const daysOnMonth = (month: number, year: number) => {
|
||||
const data = new Date(year, month, 0);
|
||||
return data.getDate();
|
||||
};
|
||||
const daysOnMonthValue = daysOnMonth(+month, +year); */
|
||||
|
||||
const data = new Date(`${year}-${month}-${day}`);
|
||||
const today = new Date();
|
||||
|
||||
return (
|
||||
valWithoutSlash.length === 8 &&
|
||||
/* +year >= currentYear &&
|
||||
+month >= currentMonth &&
|
||||
+day >= +currentDay && */
|
||||
data >= today /* &&
|
||||
+month > 0 &&
|
||||
+month <= 12 &&
|
||||
+day > 0 &&
|
||||
+day <= daysOnMonthValue */
|
||||
);
|
||||
}, 'La fecha no puede ser inferior al día de hoy!'),
|
||||
})
|
||||
availabilitySchema.pick({ date: true })
|
||||
);
|
||||
const { errors, defineField } = useForm({
|
||||
validationSchema,
|
||||
|
|
|
@ -26,9 +26,9 @@
|
|||
import { toTypedSchema } from '@vee-validate/zod';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useFormStore } from 'src/stores/forms';
|
||||
import { availabilitySchema } from 'src/utils/zod/schemas/availabilitySchema';
|
||||
import { useForm } from 'vee-validate';
|
||||
import { defineComponent } from 'vue';
|
||||
import { z } from 'zod';
|
||||
import IconPostalCode from '../icons/IconPostalCode.vue';
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -38,19 +38,7 @@ export default defineComponent({
|
|||
const formStore = useFormStore();
|
||||
const { availability } = storeToRefs(formStore);
|
||||
const validationSchema = toTypedSchema(
|
||||
z
|
||||
.object({
|
||||
postalCode: z.string().refine((val) => {
|
||||
const valWithoutHifen = val.replace('-', '');
|
||||
const valLength = valWithoutHifen.length;
|
||||
const regex = /^[0-9]+$/;
|
||||
|
||||
return (
|
||||
regex.test(valWithoutHifen) && valLength === 8 && valLength > 0
|
||||
);
|
||||
}, 'El código postal no puede contener letras y debe constar de 8 caracteres!'),
|
||||
})
|
||||
.partial()
|
||||
availabilitySchema.pick({ postalCode: true }).partial()
|
||||
);
|
||||
const { errors, defineField } = useForm({
|
||||
validationSchema,
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<div class="order-values" role="select">
|
||||
<div
|
||||
role="option"
|
||||
class="order-values-option"
|
||||
@click="handleOrder('lowest-price')"
|
||||
>
|
||||
<p class="filter-paragraph">menor precio</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
role="option"
|
||||
class="order-values-option"
|
||||
@click="handleOrder('highest-price')"
|
||||
>
|
||||
<p class="filter-paragraph">mayor precio</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
role="option"
|
||||
class="order-values-option"
|
||||
@click="handleOrder('latest')"
|
||||
>
|
||||
<p class="filter-paragraph">más recientes</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
role="option"
|
||||
class="order-values-option"
|
||||
@click="handleOrder('recommended')"
|
||||
>
|
||||
<p class="filter-paragraph">recomendados</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { Order, useFormStore } from 'src/stores/forms';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SortSelect',
|
||||
components: {},
|
||||
setup() {
|
||||
const formStore = useFormStore();
|
||||
const { sortProductFilters } = storeToRefs(formStore);
|
||||
|
||||
function handleOrder(order: Order) {
|
||||
sortProductFilters.value.order = order;
|
||||
sortProductFilters.value.isOpenOrderFilter = false;
|
||||
}
|
||||
|
||||
return { sortProductFilters, handleOrder };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-values {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
right: -10px;
|
||||
left: -20px;
|
||||
z-index: 10;
|
||||
background-color: $secondary-10;
|
||||
padding: 0px 10px 10px;
|
||||
padding-right: 10px;
|
||||
border-radius: 0px 0px 10px 10px;
|
||||
|
||||
&-option {
|
||||
cursor: pointer;
|
||||
& .filter-paragraph {
|
||||
color: inherit;
|
||||
font-size: $font-14;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,64 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<p>{{ title }}</p>
|
||||
<ul>
|
||||
<li v-for="todo in todos" :key="todo.id" @click="increment">
|
||||
{{ todo.id }} - {{ todo.content }}
|
||||
</li>
|
||||
</ul>
|
||||
<p>Count: {{ todoCount }} / {{ meta.totalCount }}</p>
|
||||
<p>Active: {{ active ? 'yes' : 'no' }}</p>
|
||||
<p>Clicks on todos: {{ clickCount }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent,
|
||||
PropType,
|
||||
computed,
|
||||
ref,
|
||||
toRef,
|
||||
Ref,
|
||||
} from 'vue';
|
||||
import { Todo, Meta } from './models';
|
||||
|
||||
function useClickCount() {
|
||||
const clickCount = ref(0);
|
||||
function increment() {
|
||||
clickCount.value += 1
|
||||
return clickCount.value;
|
||||
}
|
||||
|
||||
return { clickCount, increment };
|
||||
}
|
||||
|
||||
function useDisplayTodo(todos: Ref<Todo[]>) {
|
||||
const todoCount = computed(() => todos.value.length);
|
||||
return { todoCount };
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ExampleComponent',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
todos: {
|
||||
type: Array as PropType<Todo[]>,
|
||||
default: () => []
|
||||
},
|
||||
meta: {
|
||||
type: Object as PropType<Meta>,
|
||||
required: true
|
||||
},
|
||||
active: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
return { ...useClickCount(), ...useDisplayTodo(toRef(props, 'todos')) };
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<svg
|
||||
width="29"
|
||||
height="19"
|
||||
viewBox="0 0 29 19"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_79_1742)">
|
||||
<rect width="29" height="19" rx="3" fill="#1D71B9" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M17.4941 15.4503V9.1377L29.0158 9.14802V10.8917L27.684 12.2791L29.0158 13.6799V15.4603H26.8897L25.7598 14.2445L24.6377 15.4652L17.4941 15.4503Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M18.2637 14.7579V9.83228H22.5468V10.967H19.6508V11.7373H22.4778V12.8533H19.6508V13.6098H22.5468V14.7579H18.2637Z"
|
||||
fill="#1D71B9"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M22.5254 14.7578L24.895 12.2923L22.5254 9.83252H24.3594L25.8076 11.3935L27.2598 9.83252H29.0154V9.87113L26.696 12.2923L29.0154 14.6882V14.7578H27.2422L25.7684 13.1811L24.3096 14.7578H22.5254Z"
|
||||
fill="#1D71B9"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M18.0319 3.53491H20.8092L21.7846 5.69494V3.53491H25.2135L25.8047 5.15321L26.3979 3.53491H29.016V9.84716H15.1797L18.0319 3.53491Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M18.5578 4.2207L16.3164 9.14212H17.8536L18.2763 8.15654H20.5674L20.9901 9.14212H22.5653L20.3334 4.2207H18.5578ZM18.7503 7.05206L19.4222 5.48537L20.0938 7.05206H18.7503Z"
|
||||
fill="#1D71B9"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M22.5449 9.14139V4.21997L24.7055 4.22723L25.8174 7.2528L26.9367 4.21997H29.0161V9.14139L27.6781 9.15286V5.77213L26.4149 9.14139H25.1932L23.9038 5.76066V9.14139H22.5449Z"
|
||||
fill="#1D71B9"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_79_1742">
|
||||
<rect width="29" height="19" rx="3" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<span>
|
||||
<svg
|
||||
width="21"
|
||||
height="13"
|
||||
viewBox="0 0 21 13"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="logo">
|
||||
<g id="icon">
|
||||
<path
|
||||
id="Vector"
|
||||
d="M13.1039 1.81055H7.75195V11.1896H13.1039V1.81055Z"
|
||||
fill="#FF5F00"
|
||||
/>
|
||||
<path
|
||||
id="<Path>"
|
||||
d="M8.091 6.50114C8.09001 5.59775 8.29983 4.70597 8.70458 3.89335C9.10933 3.08073 9.69839 2.36856 10.4272 1.81079C9.52482 1.11928 8.44119 0.689275 7.3001 0.569914C6.15901 0.450553 5.0065 0.646652 3.97429 1.1358C2.94208 1.62495 2.07182 2.38741 1.46298 3.33604C0.854126 4.28468 0.53125 5.38121 0.53125 6.50032C0.53125 7.61942 0.854126 8.71596 1.46298 9.66459C2.07182 10.6132 2.94208 11.3757 3.97429 11.8648C5.0065 12.354 6.15901 12.5501 7.3001 12.4307C8.44119 12.3114 9.52482 11.8814 10.4272 11.1898C9.69861 10.6322 9.10969 9.92035 8.70495 9.10803C8.30022 8.29571 8.09027 7.40426 8.091 6.50114Z"
|
||||
fill="#EB001B"
|
||||
/>
|
||||
<path
|
||||
id="Vector_2"
|
||||
d="M20.3245 6.50086C20.3245 7.62005 20.0016 8.71665 19.3926 9.66531C18.7836 10.614 17.9133 11.3764 16.8809 11.8655C15.8486 12.3546 14.696 12.5506 13.5548 12.431C12.4136 12.3115 11.33 11.8813 10.4277 11.1896C11.156 10.6315 11.7447 9.91937 12.1496 9.10701C12.5544 8.29465 12.7648 7.40323 12.7648 6.50003C12.7648 5.59682 12.5544 4.70541 12.1496 3.89305C11.7447 3.08069 11.156 2.3686 10.4277 1.8105C11.33 1.11878 12.4136 0.688563 13.5548 0.569031C14.696 0.449498 15.8486 0.645471 16.8809 1.13455C17.9133 1.62363 18.7836 2.38608 19.3926 3.33474C20.0016 4.28341 20.3245 5.38001 20.3245 6.4992V6.50086Z"
|
||||
fill="#F79E1B"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3.5px 4.5px;
|
||||
border-radius: 3px;
|
||||
background-color: #01326f;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="22" viewBox="0 0 23 22" fill="none">
|
||||
<ellipse cx="11.5" cy="11" rx="11.5" ry="11" transform="rotate(-180 11.5 11)" fill="#117564" />
|
||||
<path d="M13.6904 16.2383L9.61959 12.1516C8.84245 11.3714 8.84217 10.1098 9.61895 9.32931L13.6904 5.23828"
|
||||
stroke="#CDEBD2" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
After Width: | Height: | Size: 399 B |
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="22" viewBox="0 0 23 22" fill="none">
|
||||
<ellipse cx="11.5" cy="11" rx="11.5" ry="11" fill="#117564" />
|
||||
<path d="M9.30957 5.76172L13.3804 9.8484C14.1575 10.6286 14.1578 11.8902 13.381 12.6707L9.30957 16.7617"
|
||||
fill="#117564" />
|
||||
<path d="M9.30957 5.76172L13.3804 9.8484C14.1575 10.6286 14.1578 11.8902 13.381 12.6707L9.30957 16.7617"
|
||||
stroke="#CDEBD2" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
After Width: | Height: | Size: 493 B |
|
@ -1,8 +0,0 @@
|
|||
export interface Todo {
|
||||
id: number;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
totalCount: number;
|
||||
}
|
|
@ -1,29 +1,87 @@
|
|||
<template>
|
||||
<swiper
|
||||
:navigation="true"
|
||||
<Swiper
|
||||
:space-between="screenWidth > 1024 ? 56 : 25"
|
||||
:slides-per-view="'auto'"
|
||||
:centered-slides="true"
|
||||
:modules="modules"
|
||||
class="cards-carousel-container"
|
||||
:grabCursor="true"
|
||||
:loop="true"
|
||||
:pagination="{
|
||||
dynamicBullets: true,
|
||||
clickable: true,
|
||||
}"
|
||||
:keyboard="{
|
||||
enabled: true,
|
||||
}"
|
||||
@swiper="onSwiper"
|
||||
>
|
||||
<slot></slot>
|
||||
</swiper>
|
||||
</Swiper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Swiper from 'swiper';
|
||||
import { Navigation } from 'swiper/modules';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { Autoplay, Keyboard, Navigation, Pagination } from 'swiper/modules';
|
||||
import type { Swiper as SwiperTypes } from 'swiper/types';
|
||||
import { Swiper } from 'swiper/vue';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import { useMobileStore } from 'src/stores/mobileNav';
|
||||
import { useSwiperStore } from 'src/stores/swiper';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'horizontal-carousel',
|
||||
props: {},
|
||||
components: {
|
||||
Swiper,
|
||||
},
|
||||
setup() {
|
||||
const swiperStore = useSwiperStore();
|
||||
const { swiperCtx } = storeToRefs(swiperStore);
|
||||
function onSwiper(swiper: SwiperTypes) {
|
||||
swiperCtx.value = swiper;
|
||||
}
|
||||
|
||||
const mobileStore = useMobileStore();
|
||||
const { screenWidth } = storeToRefs(mobileStore);
|
||||
|
||||
return {
|
||||
modules: [Navigation],
|
||||
onSwiper,
|
||||
screenWidth,
|
||||
modules: [Navigation, Pagination, Keyboard, Autoplay],
|
||||
};
|
||||
},
|
||||
components: { Swiper },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.swiper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
& .swiper-wrapper .swiper-slide {
|
||||
width: 360px !important;
|
||||
}
|
||||
|
||||
& .swiper-button-prev,
|
||||
& .swiper-button-next {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
&::after,
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
& .swiper-button-prev {
|
||||
background-image: url('../../icons/svg/ArrowCircleFilledLeft.svg');
|
||||
}
|
||||
|
||||
& .swiper-button-next {
|
||||
background-image: url('../../icons/svg/ArrowCircleFilledRight.svg');
|
||||
}
|
||||
|
||||
& .swiper-pagination {
|
||||
& .swiper-pagination-bullet.swiper-pagination-bullet-active {
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
<template>
|
||||
<q-btn
|
||||
title="previous button"
|
||||
class="swiper-btn prev"
|
||||
color="primary"
|
||||
size="sm"
|
||||
@click="handlePrev"
|
||||
round
|
||||
flat
|
||||
>
|
||||
<IconArrowCircleFilledLeft />
|
||||
</q-btn>
|
||||
|
||||
<swiper-container
|
||||
class="swiper"
|
||||
:space-between="screenWidth > 1024 ? 56 : 25"
|
||||
:slides-per-view="'auto'"
|
||||
:centered-slides="true"
|
||||
:grabCursor="true"
|
||||
:navigation="true"
|
||||
:loop="true"
|
||||
:pagination="{
|
||||
dynamicBullets: true,
|
||||
clickable: true,
|
||||
}"
|
||||
:keyboard="{
|
||||
enabled: true,
|
||||
}"
|
||||
>
|
||||
<slot></slot>
|
||||
</swiper-container>
|
||||
|
||||
<q-btn
|
||||
title="next button"
|
||||
class="swiper-btn next"
|
||||
color="primary"
|
||||
size="sm"
|
||||
@click="handleNext"
|
||||
round
|
||||
flat
|
||||
>
|
||||
<IconArrowCircleFilledRight />
|
||||
</q-btn>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
|
||||
import { useMobileStore } from 'src/stores/mobileNav';
|
||||
import IconArrowCircleFilledLeft from '../icons/IconArrowCircleFilledLeft.vue';
|
||||
import IconArrowCircleFilledRight from '../icons/IconArrowCircleFilledRight.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SwiperComponent',
|
||||
components: { IconArrowCircleFilledLeft, IconArrowCircleFilledRight },
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { screenWidth } = storeToRefs(mobileStore);
|
||||
|
||||
const prevBtn = ref(null);
|
||||
const nextBtn = ref(null);
|
||||
const swiperContainer = ref(null);
|
||||
const prevSwiperBtn = ref(null);
|
||||
const nextSwiperBtn = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('Montado!');
|
||||
|
||||
swiperContainer.value =
|
||||
document.querySelector('swiper-container').shadowRoot;
|
||||
prevSwiperBtn.value = swiperContainer.value.querySelector(
|
||||
'.swiper-button-prev'
|
||||
);
|
||||
nextSwiperBtn.value = swiperContainer.value.querySelector(
|
||||
'.swiper-button-next'
|
||||
);
|
||||
const swiperDisplay = 'none';
|
||||
nextSwiperBtn.value.style.display = swiperDisplay;
|
||||
prevSwiperBtn.value.style.display = swiperDisplay;
|
||||
nextBtn.value = document.querySelector('.swiper-btn.next');
|
||||
prevBtn.value = document.querySelector('.swiper-btn.prev');
|
||||
});
|
||||
|
||||
/* onUpdated(() => {
|
||||
console.log('Atualizado!');
|
||||
|
||||
console.groupCollapsed('%c Custom', 'color: tomato;');
|
||||
console.log({ prevBtn: prevBtn.value, nextBtn: nextBtn.value });
|
||||
console.groupEnd();
|
||||
|
||||
console.groupCollapsed('%c Swiper', 'color: hotpink;');
|
||||
console.log(prevSwiperBtn.value);
|
||||
console.groupEnd();
|
||||
}); */
|
||||
|
||||
return {
|
||||
screenWidth,
|
||||
handlePrev() {
|
||||
// console.log('Prev click');
|
||||
prevSwiperBtn.value.click();
|
||||
},
|
||||
handleNext() {
|
||||
// console.log('Next click');
|
||||
nextSwiperBtn.value.click();
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.swiper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 409px;
|
||||
width: 150px;
|
||||
background: rgb(0, 0, 0);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(0, 0, 0, 0) 25%,
|
||||
rgba(255, 255, 255, 1) 100%
|
||||
);
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
& .swiper-slide {
|
||||
width: 360px !important;
|
||||
}
|
||||
|
||||
& .swiper-button-prev,
|
||||
& .swiper-button-next {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
&::after,
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
& .swiper-button-prev {
|
||||
background-image: url('../../icons/svg/ArrowCircleFilledLeft.svg');
|
||||
}
|
||||
|
||||
& .swiper-button-next {
|
||||
background-image: url('../../icons/svg/ArrowCircleFilledRight.svg');
|
||||
}
|
||||
|
||||
& .swiper-pagination {
|
||||
& .swiper-pagination-bullet.swiper-pagination-bullet-active {
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& .swiper-slide {
|
||||
width: 166px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.swiper-btn {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
top: calc(50% - 40px);
|
||||
transform: translateY(-50%);
|
||||
&.prev {
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
&.next {
|
||||
right: 125px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
top: -45px;
|
||||
transform: initial;
|
||||
&.prev {
|
||||
left: 13px;
|
||||
}
|
||||
|
||||
&.next {
|
||||
right: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -139,6 +139,9 @@ export default defineComponent({
|
|||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
&:focus-visible {
|
||||
outline: 2px solid $primary-light;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
& .tags {
|
||||
|
@ -170,6 +173,7 @@ export default defineComponent({
|
|||
font-family: $font-lora;
|
||||
user-select: none;
|
||||
font-weight: 600;
|
||||
font-size: $font-12;
|
||||
|
||||
&.new {
|
||||
color: $white;
|
||||
|
@ -180,6 +184,10 @@ export default defineComponent({
|
|||
background: $primary-light;
|
||||
color: $primary-dark;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
font-size: $font-10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
<template>
|
||||
<div class="mobile-nav-container" :class="!isOpenNav && 'hide'">
|
||||
<header class="mobile-nav-links">
|
||||
<RouterLink class="mobile-link" to="/">Ramos</RouterLink>
|
||||
<RouterLink class="mobile-link" to="/">Plantas</RouterLink>
|
||||
<RouterLink class="mobile-link" to="/">Floranet</RouterLink>
|
||||
<RouterLink class="mobile-link" to="/">FAQs</RouterLink>
|
||||
<RouterLink class="mobile-link" to="/">Contacta</RouterLink>
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/categoria/ramos"
|
||||
>Ramos</RouterLink
|
||||
>
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/categoria/plantas">
|
||||
Plantas
|
||||
</RouterLink>
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/"
|
||||
>Floranet</RouterLink
|
||||
>
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/">FAQs</RouterLink>
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/"
|
||||
>Contacta</RouterLink
|
||||
>
|
||||
</header>
|
||||
|
||||
<div class="mobile-nav-lang">
|
||||
|
@ -37,9 +45,14 @@ export default defineComponent({
|
|||
const mobileStore = useMobileStore();
|
||||
const { isOpenNav } = storeToRefs(mobileStore);
|
||||
|
||||
const setBodyStyle = (overflow: 'hidden' | 'visible') => {
|
||||
function setBodyStyle(overflow: 'hidden' | 'visible') {
|
||||
document.body.style.overflow = overflow;
|
||||
};
|
||||
}
|
||||
|
||||
function closeNav() {
|
||||
isOpenNav.value = false;
|
||||
console.log('foi click');
|
||||
}
|
||||
|
||||
watch(isOpenNav, (newValue) => {
|
||||
if (newValue) {
|
||||
|
@ -50,7 +63,7 @@ export default defineComponent({
|
|||
setBodyStyle('visible');
|
||||
});
|
||||
|
||||
return { isOpenNav };
|
||||
return { isOpenNav, closeNav };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -73,7 +73,6 @@ export default defineComponent({
|
|||
IconSearch,
|
||||
IconCloseModal,
|
||||
PriceRange,
|
||||
|
||||
Calendar,
|
||||
PostalCode,
|
||||
},
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<q-input
|
||||
v-model="firstName"
|
||||
v-bind="firstNameAttrs"
|
||||
:error-message="errors.fist_name"
|
||||
:error="!!errors.fist_name"
|
||||
:error-message="errors.name"
|
||||
:error="!!errors.name"
|
||||
bg-color="white"
|
||||
label="Nombre"
|
||||
class="name"
|
||||
|
@ -14,8 +14,8 @@
|
|||
<q-input
|
||||
v-model="secondName"
|
||||
v-bind="secondNameAttrs"
|
||||
:error-message="errors.second_name"
|
||||
:error="!!errors.second_name"
|
||||
:error-message="errors.surname"
|
||||
:error="!!errors.surname"
|
||||
bg-color="white"
|
||||
label="Apellidos"
|
||||
class="nickname"
|
||||
|
@ -33,10 +33,10 @@
|
|||
standout
|
||||
/>
|
||||
<q-input
|
||||
v-model="telephone"
|
||||
v-bind="telephoneAttrs"
|
||||
:error-message="errors.telephone"
|
||||
:error="!!errors.telephone"
|
||||
v-model="phone"
|
||||
v-bind="phoneAttrs"
|
||||
:error-message="errors.phone"
|
||||
:error="!!errors.phone"
|
||||
bg-color="white"
|
||||
type="tel"
|
||||
label="Teléfono"
|
||||
|
@ -83,7 +83,7 @@
|
|||
type="submit"
|
||||
class="question-submit-btn btn rounded"
|
||||
flat
|
||||
:disable="!terms"
|
||||
:disable="!meta.valid"
|
||||
>
|
||||
Enviar solicitud <IconArrowRightOne />
|
||||
</q-btn>
|
||||
|
@ -91,14 +91,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toTypedSchema } from '@vee-validate/zod';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useFormStore } from 'src/stores/forms';
|
||||
import { useForm } from 'vee-validate';
|
||||
import { defineComponent } from 'vue';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useQuasar } from 'quasar';
|
||||
import IconArrowRightOne from 'src/components/icons/IconArrowRightOne.vue';
|
||||
import { questionSchema } from 'src/utils/zod/schemas/questionSchema';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'QuestionForm',
|
||||
|
@ -107,35 +106,20 @@ export default defineComponent({
|
|||
const $q = useQuasar();
|
||||
const formStore = useFormStore();
|
||||
const { handleQuestionData } = formStore;
|
||||
const nameMessage = 'Sólo se aceptan una palabra y caracteres no numéricos';
|
||||
const requiredMessage = 'Campo obligatorio';
|
||||
const validationSchema = toTypedSchema(
|
||||
z
|
||||
.object({
|
||||
fist_name: z
|
||||
.string({ required_error: requiredMessage })
|
||||
.regex(/^[A-Za-z]+$/, nameMessage),
|
||||
second_name: z
|
||||
.string({ required_error: requiredMessage })
|
||||
.regex(/^[A-Za-z]+$/, nameMessage),
|
||||
email: z.string({ required_error: requiredMessage }).email(),
|
||||
telephone: z.string({ required_error: requiredMessage }),
|
||||
query: z.string({ required_error: requiredMessage }),
|
||||
message: z.string({ required_error: requiredMessage }),
|
||||
terms: z.boolean({ required_error: requiredMessage }),
|
||||
})
|
||||
.required({})
|
||||
);
|
||||
|
||||
const { errors, meta, defineField, handleSubmit, handleReset } = useForm({
|
||||
validationSchema,
|
||||
validationSchema: questionSchema,
|
||||
initialValues: {
|
||||
terms: false,
|
||||
},
|
||||
});
|
||||
const [firstName, firstNameAttrs] = defineField('fist_name', {});
|
||||
const [secondName, secondNameAttrs] = defineField('second_name', {});
|
||||
const [email, emailAttrs] = defineField('email', {});
|
||||
const [telephone, telephoneAttrs] = defineField('telephone', {});
|
||||
const [query, queryAttrs] = defineField('query', {});
|
||||
const [message, messageAttrs] = defineField('message', {});
|
||||
const [terms, termsAttrs] = defineField('terms', {});
|
||||
const [firstName, firstNameAttrs] = defineField('name');
|
||||
const [secondName, secondNameAttrs] = defineField('surname');
|
||||
const [email, emailAttrs] = defineField('email');
|
||||
const [phone, phoneAttrs] = defineField('phone');
|
||||
const [query, queryAttrs] = defineField('query');
|
||||
const [message, messageAttrs] = defineField('message');
|
||||
const [terms, termsAttrs] = defineField('terms');
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
console.log(values);
|
||||
|
@ -163,8 +147,8 @@ export default defineComponent({
|
|||
secondNameAttrs,
|
||||
email,
|
||||
emailAttrs,
|
||||
telephone,
|
||||
telephoneAttrs,
|
||||
phone,
|
||||
phoneAttrs,
|
||||
query,
|
||||
queryAttrs,
|
||||
message,
|
||||
|
@ -225,6 +209,9 @@ export default defineComponent({
|
|||
|
||||
& .question-submit-btn {
|
||||
align-self: flex-start;
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
align-self: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
@import './pages/faq.scss';
|
||||
@import './components/calendar-postalcode.scss';
|
||||
|
||||
:root {
|
||||
--swiper-theme-color: #117564;
|
||||
--swiper-pagination-color: #117564;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -39,6 +44,13 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
.checkout-padding {
|
||||
padding-top: 149px !important;
|
||||
@media only screen and (max-width: $med-md) {
|
||||
padding-top: 73px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
|
@ -86,7 +98,7 @@ html {
|
|||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
outline: 2px solid $primary-light;
|
||||
outline: 2px solid $primary-light !important;
|
||||
}
|
||||
|
||||
&.outlined {
|
||||
|
@ -232,7 +244,11 @@ ul {
|
|||
a {
|
||||
font-family: $font-questrial;
|
||||
text-decoration: none;
|
||||
font-size: font-xxxsm;
|
||||
font-size: $font-xxxsm;
|
||||
// display: inline-flex;
|
||||
&:focus-visible {
|
||||
outline: 2px solid $primary-light;
|
||||
}
|
||||
}
|
||||
|
||||
p,
|
||||
|
@ -260,7 +276,12 @@ p,
|
|||
|
||||
.header-title {
|
||||
margin-block: 90px 50px;
|
||||
.pege-title {
|
||||
&.success {
|
||||
width: min(100%, 676px);
|
||||
margin: 90px auto 64px;
|
||||
}
|
||||
|
||||
& .pege-title {
|
||||
font-family: $font-lora;
|
||||
font-size: 1.875rem;
|
||||
line-height: 1.1;
|
||||
|
@ -269,10 +290,14 @@ p,
|
|||
margin-bottom: 25px;
|
||||
color: $title-default;
|
||||
}
|
||||
.pege-subtitle {
|
||||
|
||||
& .pege-subtitle {
|
||||
font-family: $font-questrial;
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
&.checkout {
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ $positive: #21ba45;
|
|||
$negative: #c10015;
|
||||
$info: #31ccec;
|
||||
$warning: #f2c037;
|
||||
$blue: #00709f;
|
||||
|
||||
//! Media queries
|
||||
$med-hg: 1441px;
|
||||
|
@ -54,12 +55,14 @@ $primary-light: #cdebd2;
|
|||
//! Secondary pallete
|
||||
$white: #ffffff;
|
||||
$secondary-orange: #ff9900;
|
||||
$secondary-orange-light: #e3e3e3;
|
||||
$secondary-100: #141414;
|
||||
$secondary-80: #54544f;
|
||||
$secondary-60: #979797;
|
||||
$secondary-40: #ededed;
|
||||
$secondary-20: #f8f8f8;
|
||||
$secondary-10: #f9f9f9;
|
||||
$secondary-5: #fafafa;
|
||||
|
||||
//! Font color pallete
|
||||
$title-default: #117564;
|
||||
|
@ -135,6 +138,7 @@ $font-18: 1.125rem;
|
|||
$font-16: 1rem;
|
||||
$font-14: 0.875rem;
|
||||
$font-12: 0.75rem;
|
||||
$font-10: 0.625rem;
|
||||
|
||||
//! Button
|
||||
$btn-black-bg: $text-title-100;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</q-no-ssr>
|
||||
<mobile-nav />
|
||||
|
||||
<q-page-container class="no-padding padding-top more">
|
||||
<q-page-container class="no-padding padding-top checkout-padding">
|
||||
<router-view />
|
||||
</q-page-container>
|
||||
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import { fakerES } from '@faker-js/faker';
|
||||
|
||||
export const cardMock = Array.from({ length: 10 }, (_, i) => ({
|
||||
imgSrc: `assets/flowers/flower-${i + 1}.png`,
|
||||
discount: i % 2 === 0 ? '10' : '',
|
||||
title: 'Nombre del producto',
|
||||
isNew: i % 2 === 0,
|
||||
value: '25,90',
|
||||
export const cardMock = Array.from({ length: 8 }, (_, i) => ({
|
||||
id: i + 1,
|
||||
attributes: [''],
|
||||
imgSrc: `assets/flowers/flower-${i + 1}.png`,
|
||||
title: fakerES.commerce.productName(),
|
||||
discount: fakerES.commerce.price({ min: 5, max: 15, dec: 0 }),
|
||||
isNew: fakerES.datatype.boolean(),
|
||||
value: fakerES.commerce.price({ min: 20, max: 150 }),
|
||||
// title: 'Nombre del producto',
|
||||
// discount: i % 2 === 0 ? '10' : '',
|
||||
// isNew: i % 2 === 0,
|
||||
// value: '25,90',
|
||||
}));
|
||||
|
||||
interface GenerateFlowersParams {
|
||||
|
|
|
@ -48,13 +48,27 @@
|
|||
<IconFilter />
|
||||
</q-btn>
|
||||
|
||||
<div class="filter-item order-filter">
|
||||
<div
|
||||
class="filter-item order-filter"
|
||||
:class="sortProductFilters.isOpenOrderFilter && 'active'"
|
||||
>
|
||||
<div class="order-filters">
|
||||
<p class="filter-paragraph">
|
||||
Ordenar por:
|
||||
<span class="green-text">precio</span>
|
||||
<span class="green-text">{{
|
||||
orderText[sortProductFilters.order as Order] || ''
|
||||
}}</span>
|
||||
</p>
|
||||
|
||||
<q-btn flat type="button" class="btn filter-btn price-order">
|
||||
<SortSelect v-if="sortProductFilters.isOpenOrderFilter" />
|
||||
</div>
|
||||
|
||||
<q-btn
|
||||
flat
|
||||
@click="openOrderFilter"
|
||||
type="button"
|
||||
class="btn filter-btn price-order"
|
||||
>
|
||||
<IconArrowDownWhite />
|
||||
</q-btn>
|
||||
</div>
|
||||
|
@ -96,18 +110,23 @@
|
|||
<script lang="ts">
|
||||
import { fakerES } from '@faker-js/faker';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import {
|
||||
defineAsyncComponent,
|
||||
defineComponent,
|
||||
onMounted,
|
||||
onUpdated,
|
||||
reactive,
|
||||
ref,
|
||||
} from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import SortSelect from 'src/components/@inputs/SortSelect.vue';
|
||||
import IconArrowCircleFilledRight from 'src/components/icons/IconArrowCircleFilledRight.vue';
|
||||
import IconArrowDownWhite from 'src/components/icons/IconArrowDownWhite.vue';
|
||||
import IconFilter from 'src/components/icons/IconFilter.vue';
|
||||
import IconPencil from 'src/components/icons/IconPencil.vue';
|
||||
import DudasSection from 'src/components/sections/DudasSection.vue';
|
||||
import Card from 'src/components/ui/Card.vue';
|
||||
import Container from 'src/components/ui/Container.vue';
|
||||
import Modal from 'src/components/ui/Modal.vue';
|
||||
import { useFormStore } from 'src/stores/forms';
|
||||
import { Category, Order, useFormStore } from 'src/stores/forms';
|
||||
import { useModalStore } from 'src/stores/modalStore';
|
||||
|
||||
type MonthES =
|
||||
|
@ -132,15 +151,18 @@ export default defineComponent({
|
|||
IconPencil,
|
||||
IconFilter,
|
||||
Container,
|
||||
Modal,
|
||||
Card,
|
||||
DudasSection,
|
||||
DudasSection: defineAsyncComponent(
|
||||
() => import('src/components/sections/DudasSection.vue')
|
||||
),
|
||||
Modal: defineAsyncComponent(() => import('src/components/ui/Modal.vue')),
|
||||
Card: defineAsyncComponent(() => import('src/components/ui/Card.vue')),
|
||||
SortSelect,
|
||||
},
|
||||
setup() {
|
||||
const modalStore = useModalStore();
|
||||
const formStore = useFormStore();
|
||||
const { availability } = storeToRefs(formStore);
|
||||
const monthES: Record<number, MonthES> = {
|
||||
const { availability, sortProductFilters } = storeToRefs(formStore);
|
||||
const monthES: Record<number, MonthES> = reactive({
|
||||
0: 'Enero',
|
||||
1: 'Febrero',
|
||||
2: 'Marzo',
|
||||
|
@ -153,13 +175,10 @@ export default defineComponent({
|
|||
9: 'Octubre',
|
||||
10: 'Noviembre',
|
||||
11: 'Diciembre',
|
||||
};
|
||||
// monthES[] || console.error('Invalid date');
|
||||
|
||||
});
|
||||
const isOpenOrder = ref(false);
|
||||
const category = ref('');
|
||||
const { path } = useRoute();
|
||||
const [_a, _b, categoryValue] = path.split(/\//);
|
||||
|
||||
const route = useRoute();
|
||||
const cardsMock = Array.from({ length: 8 }, (_, i) => ({
|
||||
id: i + 1,
|
||||
imgSrc: `../assets/flowers/flower-${i + 1}.png`,
|
||||
|
@ -168,8 +187,37 @@ export default defineComponent({
|
|||
title: fakerES.commerce.product(),
|
||||
value: fakerES.commerce.price({ min: 30, max: 100 }),
|
||||
}));
|
||||
const orderText: Record<Order, string> = {
|
||||
'lowest-price': 'menor precio',
|
||||
'highest-price': 'mayor precio',
|
||||
recommended: 'recomendados',
|
||||
latest: 'más recientes',
|
||||
};
|
||||
|
||||
return { modalStore, availability, cardsMock, category };
|
||||
onMounted(() => {
|
||||
sortProductFilters.value.category = route.path.split('/')[3] as Category;
|
||||
});
|
||||
|
||||
onUpdated(() => {
|
||||
console.log('Atualizado!');
|
||||
console.log(sortProductFilters.value);
|
||||
});
|
||||
|
||||
function openOrderFilter() {
|
||||
sortProductFilters.value.isOpenOrderFilter =
|
||||
!sortProductFilters.value.isOpenOrderFilter;
|
||||
}
|
||||
|
||||
return {
|
||||
sortProductFilters,
|
||||
openOrderFilter,
|
||||
availability,
|
||||
isOpenOrder,
|
||||
modalStore,
|
||||
orderText,
|
||||
cardsMock,
|
||||
category,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -216,7 +264,16 @@ export default defineComponent({
|
|||
padding: 4px 30px 4px 14px;
|
||||
}
|
||||
&.order-filter {
|
||||
padding: 4px 30px 4px 14px;
|
||||
padding: 4px 10px 4px 20px;
|
||||
text-align: end;
|
||||
border-radius: 10px 0px 0px 10px;
|
||||
&.active {
|
||||
border-radius: 10px 0px 0px 0px;
|
||||
}
|
||||
|
||||
& .order-filters {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
&.filters {
|
||||
|
@ -230,6 +287,7 @@ export default defineComponent({
|
|||
& .filter-btn {
|
||||
padding: 8px;
|
||||
border-radius: 0 30px 30px 0;
|
||||
|
||||
&.availability,
|
||||
&.price-order {
|
||||
position: absolute;
|
||||
|
@ -243,6 +301,7 @@ export default defineComponent({
|
|||
&.price-order {
|
||||
right: -33px;
|
||||
padding: 9.5px;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,6 +309,7 @@ export default defineComponent({
|
|||
display: flex;
|
||||
gap: 40px;
|
||||
margin-right: 33px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $med-md) {
|
||||
|
|
|
@ -1,75 +1,86 @@
|
|||
<template>
|
||||
<q-page class="checkout-page">
|
||||
<Container tag="section">
|
||||
<header class="header-title">
|
||||
<h1 class="pege-title">¿A quién y dónde lo entregamos?</h1>
|
||||
<header class="header-title" :class="!checkoutBlock && 'success'">
|
||||
<h1 class="pege-title" v-if="checkoutBlock">
|
||||
¿A quién y dónde lo entregamos?
|
||||
</h1>
|
||||
<h1 class="pege-title" v-if="!checkoutBlock">¡Muchas gracias Jerom!</h1>
|
||||
|
||||
<p class="pege-subtitle">
|
||||
<p class="pege-subtitle checkout" v-if="checkoutBlock">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
</p>
|
||||
<p class="pege-subtitle checkout" v-if="!checkoutBlock">
|
||||
¡Tu pedido se ha realizado con éxito! Gracias por confiar en nosotros,
|
||||
en breves recibirás un correo con la confirmación de tu pedido.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="checkout-container">
|
||||
<div class="checkout-steps">
|
||||
<div
|
||||
v-for="({ active, description, name, value }, i) in stepsFormated()"
|
||||
class="step-item-container"
|
||||
v-for="(step, index) in stepsFormated()"
|
||||
:key="index"
|
||||
:key="i"
|
||||
>
|
||||
<div class="step-item">
|
||||
<div class="circle-step-container">
|
||||
<span class="border-step" :class="[i == 0 && 'transparent']" />
|
||||
|
||||
<div
|
||||
:class="['border-step', index == 0 && 'transparent']"
|
||||
></div>
|
||||
<div
|
||||
:class="['circle-step', step.active && 'active']"
|
||||
v-on:click="handleClickStep(step.value)"
|
||||
>
|
||||
<span class="step-value">{{ step.value }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="circle-step"
|
||||
:class="[
|
||||
'border-step',
|
||||
index == stepList['data'].length - 1 && 'transparent',
|
||||
(active || (meta.valid && i == 1) || !checkoutBlock) &&
|
||||
'active',
|
||||
]"
|
||||
></div>
|
||||
>
|
||||
<span class="step-value">{{ value }}</span>
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="border-step"
|
||||
:class="[i == stepList['data'].length - 1 && 'transparent']"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="step-content">
|
||||
<div class="title">
|
||||
<h4>{{ step.name }}</h4>
|
||||
<h4>{{ name }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="description">
|
||||
<p>{{ step.description }}</p>
|
||||
<p>{{ description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="checkoutBlock">
|
||||
<div class="checkout-content">
|
||||
<div class="checkout-form-container">
|
||||
<header class="checkout-header-form">
|
||||
<h3>Instrucciones para la entrega</h3>
|
||||
</header>
|
||||
|
||||
<div class="checkout-form-wrapper">
|
||||
<div class="checkout-form">
|
||||
<q-form
|
||||
action=""
|
||||
method="post"
|
||||
@submit.prevent="onSubmitPersonalData"
|
||||
id="checkout-form"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<div class="form-fields-container delivery">
|
||||
<header class="checkout-header-form">
|
||||
<h3>Instrucciones para la entrega</h3>
|
||||
</header>
|
||||
|
||||
<div class="checkout-fields">
|
||||
<div class="field-control field-input">
|
||||
<q-input
|
||||
placeholder="Nombre*"
|
||||
name="name"
|
||||
type="text"
|
||||
v-model="formPersonalData.data.name"
|
||||
:rules="[
|
||||
(val) => val.length > 0 || 'Campo obligatorio',
|
||||
]"
|
||||
v-model="name"
|
||||
v-bind:="nameAttrs"
|
||||
:error="!!errors.name"
|
||||
:error-message="errors.name"
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
|
@ -79,10 +90,10 @@
|
|||
placeholder="Apellidos*"
|
||||
name="surname"
|
||||
type="text"
|
||||
v-model="formPersonalData.data.surname"
|
||||
:rules="[
|
||||
(val) => val.length > 0 || 'Campo obligatorio',
|
||||
]"
|
||||
v-model="surname"
|
||||
v-bind:="surnameAttrs"
|
||||
:error="!!errors.surname"
|
||||
:error-message="errors.surname"
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
|
@ -92,10 +103,10 @@
|
|||
placeholder="Dirección*"
|
||||
name="address"
|
||||
type="text"
|
||||
v-model="formPersonalData.data.address"
|
||||
:rules="[
|
||||
(val) => val.length > 0 || 'Campo obligatorio',
|
||||
]"
|
||||
v-model="address"
|
||||
v-bind:="addressAttrs"
|
||||
:error="!!errors.address"
|
||||
:error-message="errors.address"
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
|
@ -105,18 +116,22 @@
|
|||
placeholder="Código postal*"
|
||||
name="postalCode"
|
||||
type="text"
|
||||
v-model="formPersonalData.data.postalCode"
|
||||
:rules="[
|
||||
(val) => val.length > 0 || 'Campo obligatorio',
|
||||
]"
|
||||
mask="#####-###"
|
||||
v-model="postalCode"
|
||||
v-bind:="postalCodeAttrs"
|
||||
:error="!!errors.postalCode"
|
||||
:error-message="errors.postalCode"
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field-control field-select">
|
||||
<q-select
|
||||
outlined
|
||||
v-model="formPersonalData.data.city"
|
||||
name="city"
|
||||
v-model="city"
|
||||
v-bind:="cityAttrs"
|
||||
:error="!!errors.city"
|
||||
:error-message="errors.city"
|
||||
:options="optionsCity.data"
|
||||
:label="
|
||||
formPersonalData.data.city.length
|
||||
|
@ -124,13 +139,17 @@
|
|||
: 'Ciudad*'
|
||||
"
|
||||
stack-label
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field-control field-select">
|
||||
<q-select
|
||||
outlined
|
||||
v-model="formPersonalData.data.province"
|
||||
name="province"
|
||||
v-model="province"
|
||||
v-bind:="provinceAttrs"
|
||||
:error="!!errors.province"
|
||||
:error-message="errors.province"
|
||||
:options="optionsProvince.data"
|
||||
:label="
|
||||
formPersonalData.data.province
|
||||
|
@ -138,115 +157,126 @@
|
|||
: 'Provincia*'
|
||||
"
|
||||
stack-label
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field-control field-input">
|
||||
<div class="field-control field-input telephone">
|
||||
<q-input
|
||||
outlined
|
||||
placeholder="Teléfono*"
|
||||
name="phone"
|
||||
type="text"
|
||||
v-model="formPersonalData.data.phone"
|
||||
:rules="[
|
||||
(val) => val.length > 0 || 'Campo obligatorio',
|
||||
]"
|
||||
mask="(##) ##### ####"
|
||||
v-model="phone"
|
||||
v-bind:="phoneAttrs"
|
||||
:error="!!errors.phone"
|
||||
:error-message="errors.phone"
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-fields-container sender">
|
||||
<header class="checkout-header-form">
|
||||
<h3>Remitente</h3>
|
||||
</header>
|
||||
|
||||
<div class="checkout-fields">
|
||||
<div class="field-control field-input">
|
||||
<q-input
|
||||
placeholder="Nombre*"
|
||||
name="name"
|
||||
placeholder="Nombre y apellidos o nombre de empresa"
|
||||
name="senderName"
|
||||
type="text"
|
||||
v-model="formPersonalData.data.name"
|
||||
:rules="[
|
||||
(val) => val.length > 0 || 'Campo obligatorio',
|
||||
]"
|
||||
v-model="senderName"
|
||||
v-bind:="senderNameAttrs"
|
||||
:error="!!errors.senderName"
|
||||
:error-message="errors.senderName"
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field-control field-input">
|
||||
<q-input
|
||||
placeholder="Nombre*"
|
||||
name="name"
|
||||
placeholder="CIF / NIF"
|
||||
name="senderCifNif"
|
||||
type="text"
|
||||
v-model="formPersonalData.data.name"
|
||||
:rules="[
|
||||
(val) => val.length > 0 || 'Campo obligatorio',
|
||||
]"
|
||||
mask="#########"
|
||||
v-model="senderCifNif"
|
||||
v-bind:="senderCifNifAttrs"
|
||||
:error="!!errors.senderCifNif"
|
||||
:error-message="errors.senderCifNif"
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field-control field-input">
|
||||
<q-input
|
||||
placeholder="Nombre*"
|
||||
name="name"
|
||||
type="text"
|
||||
v-model="formPersonalData.data.name"
|
||||
:rules="[
|
||||
(val) => val.length > 0 || 'Campo obligatorio',
|
||||
]"
|
||||
placeholder="Email"
|
||||
name="senderEmail"
|
||||
type="email"
|
||||
v-model="senderEmail"
|
||||
v-bind:="senderEmailAttrs"
|
||||
:error="!!errors.senderEmail"
|
||||
:error-message="errors.senderEmail"
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field-control field-input">
|
||||
<q-input
|
||||
placeholder="Nombre*"
|
||||
name="name"
|
||||
placeholder="Teléfono"
|
||||
name="senderPhone"
|
||||
type="text"
|
||||
v-model="formPersonalData.data.name"
|
||||
:rules="[
|
||||
(val) => val.length > 0 || 'Campo obligatorio',
|
||||
]"
|
||||
mask="(##) ##### ####"
|
||||
v-model="senderPhone"
|
||||
v-bind:="senderPhoneAttrs"
|
||||
:error="!!errors.senderPhone"
|
||||
:error-message="errors.senderPhone"
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field-control field-input">
|
||||
<q-input
|
||||
v-model="formPersonalData.data.name"
|
||||
:rules="[
|
||||
(val) => val.length > 0 || 'Campo obligatorio',
|
||||
]"
|
||||
bg-color="white"
|
||||
label="Mensaje"
|
||||
class="message"
|
||||
placeholder="Notas sobre tu pedido (Opcional), por ejemplo, notas especiales para la entrega"
|
||||
name="senderNotes"
|
||||
type="textarea"
|
||||
v-model="senderNotes"
|
||||
v-bind:="senderNotesAttrs"
|
||||
:error="!!errors.senderNotes"
|
||||
:error-message="errors.senderNotes"
|
||||
class="message"
|
||||
autogrow
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-button-control">
|
||||
<q-btn color="primary" type="submit">Continuar</q-btn>
|
||||
</div>
|
||||
</q-form>
|
||||
</div>
|
||||
|
||||
<aside class="checkout-aside">
|
||||
<div class="checkout-delivery-date" v-if="true">
|
||||
<header class="checkout-aside-header">
|
||||
<strong> Fecha de entrega </strong>
|
||||
<header class="checkout-aside-header green-text">
|
||||
<strong class="checkout-aside-title">
|
||||
Fecha de entrega
|
||||
</strong>
|
||||
</header>
|
||||
|
||||
<div class="checkout-delivery-body">
|
||||
<p>13 de julio - De 11h - 12 h</p>
|
||||
<p class="green-text">13 de julio - De 11h - 12 h</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkout-summary">
|
||||
<header class="checkout-aside-header">
|
||||
<strong> Resumen del pedido </strong>
|
||||
<header class="checkout-aside-header gray-bg">
|
||||
<strong class="checkout-aside-title">
|
||||
Resumen del pedido
|
||||
</strong>
|
||||
</header>
|
||||
|
||||
<div class="checkout-summary-body">
|
||||
<div class="checkout-summary-body gray-bg">
|
||||
<ul class="checkout-summary-list">
|
||||
<li class="checkout-summary-item">
|
||||
<p>Ramo Lucena <span>30,00€</span></p>
|
||||
|
@ -261,35 +291,101 @@
|
|||
|
||||
<footer class="checkout-summary-footer">
|
||||
<p class="checkout-summary-paragraph">Total</p>
|
||||
<p class="checkout-summary-price">67€</p>
|
||||
<p class="checkout-summary-paragraph summary-price">67€</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="checkout-payment-methods">
|
||||
<div class="checkout-payment-methods gray-bg">
|
||||
<header class="checkout-aside-header">
|
||||
<strong>Método de pago</strong>
|
||||
<strong class="checkout-aside-title">Método de pago</strong>
|
||||
</header>
|
||||
|
||||
<div class="checkout-payment-body">
|
||||
<q-radio v-model="priceMethod" val="credit" color="primary">
|
||||
<p>a</p>
|
||||
<q-radio
|
||||
v-model="paymentMethod"
|
||||
v-bind="paymentMethodAttrs"
|
||||
val="credit"
|
||||
color="primary"
|
||||
>
|
||||
<p>
|
||||
Tarjeta
|
||||
<span class="card-flags">
|
||||
<IconMaster /><IconVisa /> <IconAny /> <IconExpress />
|
||||
</span>
|
||||
</p>
|
||||
</q-radio>
|
||||
<q-radio v-model="priceMethod" val="stripe" color="primary">
|
||||
<p>b</p>
|
||||
|
||||
<q-radio
|
||||
v-model="paymentMethod"
|
||||
v-bind="paymentMethodAttrs"
|
||||
val="stripe"
|
||||
color="primary"
|
||||
>
|
||||
<p>Stripe <a href="#">¿Qué es Stripe?</a></p>
|
||||
</q-radio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkout-terms">
|
||||
<q-checkbox v-model="terms" class="terms">
|
||||
<q-checkbox v-model="terms" v-bind="termsAttrs" class="terms">
|
||||
<p :style="!!errors.terms && 'color: red;'">
|
||||
He leído y estoy de acuerdo con los términosy condiciones de
|
||||
la tienda Floranet
|
||||
</p>
|
||||
</q-checkbox>
|
||||
|
||||
<q-btn flat class="btn" type="submit">CONTINUAR</q-btn>
|
||||
<q-btn flat class="btn" type="submit" form="checkout-form">
|
||||
PROCEDER AL PAGO
|
||||
</q-btn>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="!checkoutBlock" class="checkout-success" id="success-block">
|
||||
<h6 class="checkout-success-title green-text">
|
||||
Has efectuado la siguiente compra
|
||||
</h6>
|
||||
|
||||
<div class="checkout-success-body">
|
||||
<div class="checkout-success-content">
|
||||
<ul class="checkout-success-list">
|
||||
<li class="checkout-success-item">
|
||||
<div class="checkout-item-content">
|
||||
<div class="checkout-product-details">
|
||||
<img
|
||||
src="../assets/checkout-flower.png"
|
||||
alt="product"
|
||||
class="checkout-product-img"
|
||||
/>
|
||||
<p class="checkout-product-title">Ramo Lucena</p>
|
||||
</div>
|
||||
|
||||
<p class="checkout-product-price">30.00€</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="checkout-success-item">
|
||||
<div class="checkout-item-content">
|
||||
<div class="checkout-product-details">
|
||||
<img
|
||||
src="../assets/checkout-flower.png"
|
||||
alt="product"
|
||||
class="checkout-product-img"
|
||||
/>
|
||||
<p class="checkout-product-title">Ramo Lucena</p>
|
||||
</div>
|
||||
|
||||
<p class="checkout-product-price">30.00€</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<footer class="checkout-success-footer">
|
||||
<p class="checkout-success-paragraph">Total</p>
|
||||
<p class="checkout-success-paragraph">67.00€</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -298,8 +394,17 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useForm } from 'vee-validate';
|
||||
import { defineComponent, reactive, ref } from 'vue';
|
||||
|
||||
import { toTypedSchema } from '@vee-validate/zod';
|
||||
import IconAny from 'src/components/icons/credit-flags/IconAny.vue';
|
||||
import IconExpress from 'src/components/icons/credit-flags/IconExpress.vue';
|
||||
import IconMaster from 'src/components/icons/credit-flags/IconMaster.vue';
|
||||
import IconVisa from 'src/components/icons/credit-flags/IconVisa.vue';
|
||||
import Container from 'src/components/ui/Container.vue';
|
||||
import { defineComponent, reactive, ref, watch } from 'vue';
|
||||
import { useFormStore } from 'src/stores/forms';
|
||||
import { checkoutSchema } from 'src/utils/zod/schemas/checkoutSchema';
|
||||
|
||||
interface StepsProps {
|
||||
value: number;
|
||||
|
@ -320,19 +425,39 @@ interface FormPersonalData {
|
|||
|
||||
export default defineComponent({
|
||||
name: 'CheckoutPage',
|
||||
components: { Container },
|
||||
components: {
|
||||
Container,
|
||||
IconAny,
|
||||
IconVisa,
|
||||
IconExpress,
|
||||
IconMaster,
|
||||
// StepByStep,
|
||||
},
|
||||
setup() {
|
||||
const formPersonalData = reactive<{ data: FormPersonalData }>({
|
||||
data: {
|
||||
name: '',
|
||||
surname: '',
|
||||
address: '',
|
||||
postalCode: '',
|
||||
phone: '',
|
||||
city: '',
|
||||
province: '',
|
||||
const formStore = useFormStore();
|
||||
const { handleCheckoutData } = formStore;
|
||||
const { meta, errors, handleSubmit, defineField, resetForm } = useForm({
|
||||
validationSchema: toTypedSchema(checkoutSchema),
|
||||
initialValues: {
|
||||
paymentMethod: 'credit',
|
||||
terms: false,
|
||||
},
|
||||
});
|
||||
const [name, nameAttrs] = defineField('name');
|
||||
const [surname, surnameAttrs] = defineField('surname');
|
||||
const [address, addressAttrs] = defineField('address');
|
||||
const [postalCode, postalCodeAttrs] = defineField('postalCode');
|
||||
const [phone, phoneAttrs] = defineField('phone');
|
||||
const [city, cityAttrs] = defineField('city');
|
||||
const [province, provinceAttrs] = defineField('province');
|
||||
const [senderName, senderNameAttrs] = defineField('senderName');
|
||||
const [senderCifNif, senderCifNifAttrs] = defineField('senderCifNif');
|
||||
const [senderEmail, senderEmailAttrs] = defineField('senderEmail');
|
||||
const [senderPhone, senderPhoneAttrs] = defineField('senderPhone');
|
||||
const [senderNotes, senderNotesAttrs] = defineField('senderNotes');
|
||||
const [paymentMethod, paymentMethodAttrs] = defineField('paymentMethod');
|
||||
const [terms, termsAttrs] = defineField('terms');
|
||||
|
||||
const stepActive = reactive({ data: 1 });
|
||||
const stepList = reactive<{ data: StepsProps[] }>({
|
||||
data: [
|
||||
|
@ -340,7 +465,7 @@ export default defineComponent({
|
|||
value: 1,
|
||||
name: 'Paso 1',
|
||||
description: 'Datos de facturación',
|
||||
active: false,
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
|
@ -356,6 +481,16 @@ export default defineComponent({
|
|||
},
|
||||
],
|
||||
});
|
||||
const checkoutBlock = ref(true);
|
||||
// const successblock = document.querySelector('#success-block');
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
handleCheckoutData(values);
|
||||
stepList.data[2].active = true;
|
||||
checkoutBlock.value = false;
|
||||
// successblock?.scrollIntoView({ behavior: 'smooth' });
|
||||
resetForm();
|
||||
});
|
||||
|
||||
const handleClickStep = (value: number) => {
|
||||
stepActive['data'] = value;
|
||||
|
@ -378,7 +513,18 @@ export default defineComponent({
|
|||
data: ['Complete la dirección, código postal y seleccione la ciudad'],
|
||||
});
|
||||
|
||||
watch(
|
||||
const formPersonalData = reactive<{ data: FormPersonalData }>({
|
||||
data: {
|
||||
name: '',
|
||||
surname: '',
|
||||
address: '',
|
||||
postalCode: '',
|
||||
phone: '',
|
||||
city: '',
|
||||
province: '',
|
||||
},
|
||||
});
|
||||
/* watch(
|
||||
() => formPersonalData.data.postalCode,
|
||||
() => {
|
||||
if (
|
||||
|
@ -423,21 +569,49 @@ export default defineComponent({
|
|||
|
||||
function onSubmitPersonalData(_e: Event) {
|
||||
console.log(formPersonalData.data);
|
||||
}
|
||||
|
||||
const priceMethod = ref('credit');
|
||||
const terms = ref(false);
|
||||
} */
|
||||
|
||||
return {
|
||||
handleClickStep,
|
||||
stepsFormated,
|
||||
onSubmitPersonalData,
|
||||
formPersonalData,
|
||||
optionsProvince,
|
||||
optionsCity,
|
||||
stepList,
|
||||
priceMethod,
|
||||
|
||||
step: ref(1),
|
||||
checkoutBlock,
|
||||
meta,
|
||||
errors,
|
||||
onSubmit,
|
||||
name,
|
||||
nameAttrs,
|
||||
surname,
|
||||
surnameAttrs,
|
||||
address,
|
||||
addressAttrs,
|
||||
postalCode,
|
||||
postalCodeAttrs,
|
||||
phone,
|
||||
phoneAttrs,
|
||||
city,
|
||||
cityAttrs,
|
||||
province,
|
||||
provinceAttrs,
|
||||
senderName,
|
||||
senderNameAttrs,
|
||||
senderCifNif,
|
||||
senderCifNifAttrs,
|
||||
senderEmail,
|
||||
senderEmailAttrs,
|
||||
senderPhone,
|
||||
senderPhoneAttrs,
|
||||
senderNotes,
|
||||
senderNotesAttrs,
|
||||
terms,
|
||||
termsAttrs,
|
||||
paymentMethod,
|
||||
paymentMethodAttrs,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -450,20 +624,24 @@ export default defineComponent({
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.step-item-container {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.border-step {
|
||||
width: 90px;
|
||||
height: 1px;
|
||||
background-color: $primary-dark;
|
||||
}
|
||||
|
||||
.circle-step-container {
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
}
|
||||
|
||||
.circle-step {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
|
@ -484,6 +662,7 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.step-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -503,40 +682,275 @@ export default defineComponent({
|
|||
font-family: $font-lora;
|
||||
}
|
||||
}
|
||||
.checkout-content {
|
||||
margin: 50px 0 150px;
|
||||
|
||||
& .checkout-content {
|
||||
width: min(100%, 1144px);
|
||||
margin: 50px auto 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
|
||||
.checkout-header-form {
|
||||
background-color: $grey-700;
|
||||
padding: 12px 30px;
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 21px;
|
||||
border-radius: 5px;
|
||||
h3 {
|
||||
color: $text-default;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
& .checkout-form-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
& .checkout-form {
|
||||
flex: 1 0 min(100%, 795px);
|
||||
}
|
||||
& .checkout-aside {
|
||||
flex: 1 0 min(100%, 329px);
|
||||
& .checkout-delivery-date,
|
||||
& .checkout-summary,
|
||||
& .checkout-payment-methods {
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
& .gray-bg {
|
||||
background-color: $secondary-10;
|
||||
}
|
||||
|
||||
& .checkout-delivery-date {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
padding: 16px 23px 18px;
|
||||
background-color: $primary-light;
|
||||
margin-bottom: 21px;
|
||||
}
|
||||
|
||||
& .checkout-summary {
|
||||
margin-bottom: 33px;
|
||||
& .checkout-aside-header,
|
||||
& .checkout-summary-body,
|
||||
& .checkout-summary-footer {
|
||||
padding-inline: 22px 19px;
|
||||
}
|
||||
|
||||
& .checkout-aside-header {
|
||||
padding-block: 16px 17px;
|
||||
& .checkout-aside-title {
|
||||
font-family: $font-lora;
|
||||
font-weight: 600;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.32px;
|
||||
}
|
||||
}
|
||||
|
||||
& .checkout-summary-body {
|
||||
padding-bottom: 23px;
|
||||
& p {
|
||||
font-size: $font-12;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.24px;
|
||||
color: $text-default;
|
||||
}
|
||||
|
||||
& .checkout-summary-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-bottom: 4px;
|
||||
& .checkout-summary-item p {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
& .checkout-summary-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background-color: $secondary-orange-light;
|
||||
padding: 19px 19px 20px 23px;
|
||||
|
||||
& .checkout-summary-paragraph {
|
||||
font-family: $font-lora;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.32px;
|
||||
color: $text-default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .checkout-payment-methods {
|
||||
padding: 9px 16px 20px 21px;
|
||||
margin-bottom: 21px;
|
||||
& .checkout-aside-header {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
& .checkout-payment-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
& p {
|
||||
width: min(100%, 200px);
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
gap: 31px;
|
||||
font-size: $font-12;
|
||||
& .card-flags {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
& a {
|
||||
margin-left: 4px;
|
||||
font-family: $font-questrial;
|
||||
color: $blue;
|
||||
font-size: $font-12;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.24px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .checkout-terms {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 23px;
|
||||
& .terms p {
|
||||
margin-left: 10px;
|
||||
font-size: $font-12;
|
||||
line-height: 17px;
|
||||
letter-spacing: 0.24px;
|
||||
color: $text-muted-one;
|
||||
}
|
||||
|
||||
& .erro {
|
||||
font-family: $font-lora;
|
||||
font-size: $font-12;
|
||||
color: #ff0000;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
gap: 47px;
|
||||
}
|
||||
}
|
||||
|
||||
& .checkout-success {
|
||||
width: min(100%, 499px);
|
||||
margin: 122px auto 0;
|
||||
text-align: center;
|
||||
& .checkout-success-title {
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
& .checkout-success-body {
|
||||
& .checkout-success-content {
|
||||
background-color: $secondary-5;
|
||||
padding: 30px 46px 42px 38px;
|
||||
border-radius: 5px 5px 0px 0px;
|
||||
& .checkout-success-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 28px;
|
||||
& .checkout-success-item {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
& .checkout-item-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
min-height: 61px;
|
||||
& .checkout-product-details {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
& .checkout-product-img {
|
||||
object-fit: cover;
|
||||
width: 54px;
|
||||
height: 100%;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
& .checkout-product-title {
|
||||
font-size: $font-12;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.24px;
|
||||
font-family: $font-questrial;
|
||||
color: $text-default;
|
||||
}
|
||||
}
|
||||
|
||||
& .checkout-product-price {
|
||||
color: $text-muted-one;
|
||||
font-family: $font-roboto;
|
||||
font-size: $font-12;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
padding-right: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
& .checkout-success-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background-color: $secondary-40;
|
||||
border-radius: 0px 0px 5px 5px;
|
||||
padding: 14px 46px 7px 36px;
|
||||
|
||||
& .checkout-success-paragraph {
|
||||
font-family: $font-lora;
|
||||
letter-spacing: 0.32px;
|
||||
line-height: 21px;
|
||||
font-weight: 600;
|
||||
color: $text-muted-one;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-fields-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 15px;
|
||||
&.delivery {
|
||||
}
|
||||
&.sender {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
& .checkout-fields {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 9px;
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.field-control {
|
||||
flex-basis: calc(50% - 8px);
|
||||
flex: 1 0 min(100%, 390px);
|
||||
|
||||
&.telephone {
|
||||
flex: 0 0 calc(50% - 5px);
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
flex: 1 0 min(100%, 390px);
|
||||
}
|
||||
}
|
||||
|
||||
&.field-input {
|
||||
label {
|
||||
|
@ -592,6 +1006,11 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
&.sender {
|
||||
margin-top: 45px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,11 +4,6 @@
|
|||
<VerticalCarouselImgs :imgsArr="slidesContent" class="home-carousel" />
|
||||
</q-no-ssr>
|
||||
|
||||
<!-- <p v-if="isCarouselVisible">Está visível</p>
|
||||
<p v-if="!isCarouselVisible">Não está visível</p>
|
||||
<p v-if="isOpenNav">Hamburg ativo</p>
|
||||
<p v-if="!isOpenNav">Hamburg não ativo</p> -->
|
||||
|
||||
<section class="products-section">
|
||||
<header class="products-section-header section-header">
|
||||
<h3 class="products-header-title subtitle">
|
||||
|
@ -44,7 +39,7 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section class="products-selection">
|
||||
<section class="products-selection-section">
|
||||
<header class="products-selection-header section-header">
|
||||
<h3 class="products-selection-title subtitle">
|
||||
Nuestra selección de plantas para el verano
|
||||
|
@ -58,7 +53,42 @@
|
|||
</header>
|
||||
|
||||
<div class="products-selection-body">
|
||||
<!-- <Container> </Container> -->
|
||||
<!-- <HorizontalCarousel>
|
||||
<SwiperSlideOne
|
||||
v-for="{ id, discount, isNew, value, title, imgSrc } in cardMock"
|
||||
:key="id"
|
||||
>
|
||||
<Card
|
||||
:id="id"
|
||||
:key="id"
|
||||
:productValue="value"
|
||||
:productName="title"
|
||||
:discount="discount"
|
||||
:imgSrc="imgSrc"
|
||||
:isNew="isNew"
|
||||
/>
|
||||
</SwiperSlideOne>
|
||||
</HorizontalCarousel> -->
|
||||
|
||||
<q-no-ssr>
|
||||
<Swiper>
|
||||
<swiper-slide
|
||||
v-for="{ id, discount, isNew, value, title, imgSrc } in cardMock"
|
||||
:key="id"
|
||||
class="swiper-slide"
|
||||
>
|
||||
<Card
|
||||
:id="id"
|
||||
:key="id"
|
||||
:productValue="value"
|
||||
:productName="title"
|
||||
:discount="discount"
|
||||
:imgSrc="imgSrc"
|
||||
:isNew="isNew"
|
||||
/>
|
||||
</swiper-slide>
|
||||
</Swiper>
|
||||
</q-no-ssr>
|
||||
</div>
|
||||
|
||||
<footer class="products-selection-footer">
|
||||
|
@ -71,11 +101,9 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { defineAsyncComponent, defineComponent, ref } from 'vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import IconArrowCircleFilledRight from 'src/components/icons/IconArrowCircleFilledRight.vue';
|
||||
import VerticalCarouselImgs from 'src/components/quasar-components/carousel/VerticalCarouselImgs.vue';
|
||||
import Container from 'src/components/ui/Container.vue';
|
||||
import { cardMock } from 'src/mock/cards';
|
||||
import { useMobileStore } from 'src/stores/mobileNav';
|
||||
|
@ -83,21 +111,23 @@ import { useMobileStore } from 'src/stores/mobileNav';
|
|||
export default defineComponent({
|
||||
name: 'HomePage',
|
||||
components: {
|
||||
IconArrowCircleFilledRight,
|
||||
VerticalCarouselImgs,
|
||||
// HorizontalCarousel,
|
||||
// ButtonComponent,
|
||||
// SwiperSlide,
|
||||
VerticalCarouselImgs: defineAsyncComponent(
|
||||
() =>
|
||||
import(
|
||||
'src/components/quasar-components/carousel/VerticalCarouselImgs.vue'
|
||||
)
|
||||
),
|
||||
Swiper: defineAsyncComponent(
|
||||
() => import('src/components/swiper/Swiper.vue')
|
||||
),
|
||||
Card: defineAsyncComponent(() => import('src/components/ui/Card.vue')),
|
||||
Container,
|
||||
Card: defineAsyncComponent({
|
||||
loader: () => import('src/components/ui/Card.vue'),
|
||||
loadingComponent: { template: '<p>loading</p>' },
|
||||
}),
|
||||
},
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { isCarouselVisible, isOpenNav, screenWidth } =
|
||||
storeToRefs(mobileStore);
|
||||
|
||||
const slidesContent = [
|
||||
'assets/1.jpg',
|
||||
'assets/2.jpg',
|
||||
|
@ -108,11 +138,11 @@ export default defineComponent({
|
|||
const data = ref(null);
|
||||
|
||||
return {
|
||||
slidesContent,
|
||||
cardMock,
|
||||
isCarouselVisible,
|
||||
isOpenNav,
|
||||
slidesContent,
|
||||
screenWidth,
|
||||
isOpenNav,
|
||||
cardMock,
|
||||
data,
|
||||
};
|
||||
},
|
||||
|
@ -145,7 +175,7 @@ export default defineComponent({
|
|||
@media only screen and (max-width: $med-xmd) {
|
||||
padding-inline: 16px;
|
||||
gap: 22px;
|
||||
margin-bottom: 33px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,7 +205,7 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
.products-selection {
|
||||
.products-selection-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
@ -183,6 +213,23 @@ export default defineComponent({
|
|||
background-color: $secondary-10;
|
||||
padding-block: 104px 73px;
|
||||
|
||||
& .products-selection-body {
|
||||
width: min(100%, 1440px);
|
||||
margin: 0 auto 92px;
|
||||
padding-inline: 76px;
|
||||
height: 490px;
|
||||
position: relative;
|
||||
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
height: 300px;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $med-md) {
|
||||
padding-inline: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $med-xmd) {
|
||||
padding-block: 48px 41px;
|
||||
}
|
||||
|
|
|
@ -428,7 +428,7 @@ export default defineComponent({
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
gap: 84px;
|
||||
& .product-pag-item {
|
||||
margin-top: 76px;
|
||||
&::before {
|
||||
|
|
|
@ -1,12 +1,35 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { AvailabilityForm } from 'src/utils/zod/schemas/availabilitySchema';
|
||||
import type { CheckoutForm } from 'src/utils/zod/schemas/checkoutSchema';
|
||||
import type { QuestionForm } from 'src/utils/zod/schemas/questionSchema';
|
||||
|
||||
export type Order = 'lowest-price' | 'highest-price' | 'latest' | 'recommended';
|
||||
export type Category = 'ramos' | 'plantas';
|
||||
interface UseFormStoreState {
|
||||
sortProductFilters: {
|
||||
isOpenOrderFilter: boolean;
|
||||
order?: Order;
|
||||
price?: number;
|
||||
category: Category;
|
||||
};
|
||||
question: QuestionForm;
|
||||
availability: AvailabilityForm;
|
||||
checkout: CheckoutForm;
|
||||
}
|
||||
|
||||
export const useFormStore = defineStore('forms', {
|
||||
state: () => ({
|
||||
state: (): UseFormStoreState => ({
|
||||
sortProductFilters: {
|
||||
isOpenOrderFilter: false,
|
||||
order: undefined,
|
||||
price: undefined,
|
||||
category: 'ramos',
|
||||
},
|
||||
question: {
|
||||
fist_name: '',
|
||||
second_name: '',
|
||||
name: '',
|
||||
surname: '',
|
||||
email: '',
|
||||
telephone: '',
|
||||
phone: '',
|
||||
query: '',
|
||||
message: '',
|
||||
terms: false,
|
||||
|
@ -15,10 +38,26 @@ export const useFormStore = defineStore('forms', {
|
|||
date: '',
|
||||
postalCode: '',
|
||||
},
|
||||
checkout: {
|
||||
name: '',
|
||||
surname: '',
|
||||
address: '',
|
||||
postalCode: '',
|
||||
city: '',
|
||||
province: '',
|
||||
phone: '',
|
||||
senderName: '',
|
||||
senderCifNif: '',
|
||||
senderEmail: '',
|
||||
senderPhone: '',
|
||||
senderNotes: '',
|
||||
paymentMethod: 'credit',
|
||||
terms: false,
|
||||
},
|
||||
}),
|
||||
|
||||
actions: {
|
||||
handleQuestionData(values: typeof this.question) {
|
||||
handleQuestionData(values: QuestionForm) {
|
||||
console.log(values);
|
||||
this.question = values;
|
||||
},
|
||||
|
@ -27,5 +66,10 @@ export const useFormStore = defineStore('forms', {
|
|||
console.log(values);
|
||||
this.availability = values;
|
||||
},
|
||||
|
||||
handleCheckoutData(values: CheckoutForm) {
|
||||
// console.log(values);
|
||||
this.checkout = values;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export type Modify<T, R> = Omit<T, keyof R> & R;
|
|
@ -0,0 +1,7 @@
|
|||
export function handlePhoneVal(val: string) {
|
||||
const regex = /[\(\) ]/g;
|
||||
const valWithoutSpaceAndParenteses = val.replace(regex, '');
|
||||
const valLength = valWithoutSpaceAndParenteses.length;
|
||||
|
||||
return valLength > 0 && valLength === 11;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import * as M from './messages';
|
||||
|
||||
export const postalCode = z
|
||||
.string({ required_error: M.requiredMessage })
|
||||
.refine((val) => {
|
||||
const valWithoutHifen = val.replaceAll('-', '');
|
||||
const valLength = valWithoutHifen.length;
|
||||
|
||||
return valLength === 8 && valLength >= 1;
|
||||
}, 'El código postal debe tener 8 caracteres numéricos válidos');
|
|
@ -0,0 +1,7 @@
|
|||
export const nameMessage =
|
||||
'Sólo se aceptan una palabra y caracteres no numéricos';
|
||||
export const phoneMessage =
|
||||
'El número de teléfono debe contener 11 caracteres numéricos válidos';
|
||||
export const requiredMessage = 'Campo obligatorio';
|
||||
export const emailMessage =
|
||||
'Introduzca una dirección de correo electrónico válida.';
|
|
@ -0,0 +1 @@
|
|||
export const justOneWord = /^[A-Za-z]+$/;
|
|
@ -0,0 +1,18 @@
|
|||
import { z } from 'zod';
|
||||
import { postalCode } from './../globalProperties';
|
||||
|
||||
const availabilityObj = {
|
||||
date: z.string().refine((val) => {
|
||||
const [day, month, year] = val.split('/');
|
||||
const regex = /\//g;
|
||||
const valWithoutSlash = val.replace(regex, '');
|
||||
const data = new Date(`${year}-${month}-${day}`);
|
||||
const today = new Date();
|
||||
|
||||
return valWithoutSlash.length === 8 && data >= today;
|
||||
}, 'La fecha no puede ser inferior al día de hoy!'),
|
||||
postalCode,
|
||||
};
|
||||
|
||||
export const availabilitySchema = z.object(availabilityObj);
|
||||
export type AvailabilityForm = z.infer<typeof availabilitySchema>;
|
|
@ -0,0 +1,45 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { handlePhoneVal } from '../functions';
|
||||
import { postalCode } from '../globalProperties';
|
||||
import { justOneWord } from '../regex';
|
||||
|
||||
import * as M from '../messages';
|
||||
|
||||
const checkoutObjVal = {
|
||||
name: z
|
||||
.string({ required_error: M.requiredMessage })
|
||||
.regex(justOneWord, M.nameMessage),
|
||||
surname: z
|
||||
.string({ required_error: M.requiredMessage })
|
||||
.regex(justOneWord, M.nameMessage),
|
||||
address: z.string({ required_error: M.requiredMessage }),
|
||||
postalCode,
|
||||
city: z.string({ required_error: M.requiredMessage }).min(3),
|
||||
province: z.string({ required_error: M.requiredMessage }).min(3),
|
||||
phone: z
|
||||
.string({ required_error: M.requiredMessage })
|
||||
.refine(handlePhoneVal, M.phoneMessage),
|
||||
senderName: z.string().regex(justOneWord, M.nameMessage),
|
||||
senderCifNif: z
|
||||
.string()
|
||||
.length(9, 'El código postal debe tener 9 caracteres numéricos válidos'),
|
||||
senderEmail: z.string().email(M.emailMessage),
|
||||
senderPhone: z.string().refine(handlePhoneVal, M.phoneMessage),
|
||||
senderNotes: z.string(),
|
||||
paymentMethod: z.enum(['credit', 'stripe'], {
|
||||
required_error: 'Seleccione uno de los métodos de pago',
|
||||
}),
|
||||
terms: z.boolean().refine((val) => {
|
||||
return val === true;
|
||||
}, 'Acepte las condiciones antes de continuar con la compra'),
|
||||
};
|
||||
|
||||
export const checkoutSchema = z.object(checkoutObjVal).partial({
|
||||
senderName: true,
|
||||
senderCifNif: true,
|
||||
senderEmail: true,
|
||||
senderPhone: true,
|
||||
senderNotes: true,
|
||||
});
|
||||
export type CheckoutForm = z.infer<typeof checkoutSchema>;
|
|
@ -0,0 +1,29 @@
|
|||
import { toTypedSchema } from '@vee-validate/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { handlePhoneVal } from '../functions';
|
||||
import * as M from '../messages';
|
||||
import { justOneWord } from '../regex';
|
||||
|
||||
const questionObjVal = {
|
||||
name: z
|
||||
.string({ required_error: M.requiredMessage })
|
||||
.regex(justOneWord, M.nameMessage),
|
||||
surname: z
|
||||
.string({ required_error: M.requiredMessage })
|
||||
.regex(justOneWord, M.nameMessage),
|
||||
email: z.string({ required_error: M.requiredMessage }).email(M.emailMessage),
|
||||
phone: z
|
||||
.string({ required_error: M.requiredMessage })
|
||||
.refine(handlePhoneVal, M.phoneMessage),
|
||||
query: z.string({ required_error: M.requiredMessage }),
|
||||
message: z.string({ required_error: M.requiredMessage }),
|
||||
terms: z.boolean({ required_error: M.requiredMessage }).refine((val) => {
|
||||
return val === true;
|
||||
}),
|
||||
};
|
||||
|
||||
const questionType = z.object(questionObjVal);
|
||||
export type QuestionForm = z.infer<typeof questionType>;
|
||||
|
||||
export const questionSchema = toTypedSchema(z.object(questionObjVal));
|
|
@ -2,5 +2,7 @@
|
|||
"extends": "@quasar/app-vite/tsconfig-preset",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
Loading…
Reference in New Issue