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">
|
||||
<p class="filter-paragraph">
|
||||
Ordenar por:
|
||||
<span class="green-text">precio</span>
|
||||
</p>
|
||||
<div
|
||||
class="filter-item order-filter"
|
||||
:class="sortProductFilters.isOpenOrderFilter && 'active'"
|
||||
>
|
||||
<div class="order-filters">
|
||||
<p class="filter-paragraph">
|
||||
Ordenar por:
|
||||
<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) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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