floranet/src/pages/CheckoutPage.vue

1003 lines
30 KiB
Vue

<template>
<q-page class="checkout-page">
<Container tag="section">
<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 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"
:key="i"
>
<div class="step-item">
<div class="circle-step-container">
<span class="border-step" :class="[i == 0 && 'transparent']" />
<div
class="circle-step"
:class="[
(active || (meta.valid && i == 1) || !checkoutBlock) &&
'active',
]"
>
<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>{{ name }}</h4>
</div>
<div class="description">
<p>{{ description }}</p>
</div>
</div>
</div>
</div>
</div>
<template v-if="checkoutBlock">
<div class="checkout-content">
<div class="checkout-form">
<q-form
action=""
method="post"
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="name"
v-bind:="nameAttrs"
:error="!!errors.name"
:error-message="errors.name"
outlined
/>
</div>
<div class="field-control field-input">
<q-input
placeholder="Apellidos*"
name="surname"
type="text"
v-model="surname"
v-bind:="surnameAttrs"
:error="!!errors.surname"
:error-message="errors.surname"
outlined
/>
</div>
<div class="field-control field-input">
<q-input
placeholder="Dirección*"
name="address"
type="text"
v-model="address"
v-bind:="addressAttrs"
:error="!!errors.address"
:error-message="errors.address"
outlined
/>
</div>
<div class="field-control field-input">
<q-input
placeholder="Código postal*"
name="postalCode"
type="text"
mask="#####-###"
v-model="postalCode"
v-bind:="postalCodeAttrs"
:error="!!errors.postalCode"
:error-message="errors.postalCode"
outlined
/>
</div>
<div class="field-control field-select">
<q-select
name="province"
v-model="province"
v-bind:="provinceAttrs"
:error="!!errors.province"
:error-message="errors.province"
:options="provinceOptions"
option-value="code"
option-label="name"
:label="
!province
? 'Complete la dirección y el código postal'
: 'Provincia*'
"
stack-label
map-options
emit-value
outlined
/>
</div>
<div class="field-control field-select">
<q-input
placeholder="Ciudade*"
name="city"
type="text"
v-model="city"
v-bind:="cityAttrs"
:error="!!errors.city"
:error-message="errors.city"
outlined
/>
</div>
<div class="field-control field-input telephone">
<q-input
placeholder="Teléfono*"
name="phone"
type="text"
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 y apellidos o nombre de empresa"
name="senderName"
type="text"
v-model="senderName"
v-bind:="senderNameAttrs"
:error="!!errors.senderName"
:error-message="errors.senderName"
outlined
/>
</div>
<div class="field-control field-input">
<q-input
placeholder="CIF / NIF"
name="senderCifNif"
type="text"
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="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="Teléfono"
name="senderPhone"
type="text"
mask="(##) ##### ####"
v-model="senderPhone"
v-bind:="senderPhoneAttrs"
:error="!!errors.senderPhone"
:error-message="errors.senderPhone"
outlined
/>
</div>
<div class="field-control field-input">
<q-input
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>
</q-form>
</div>
<aside class="checkout-aside">
<div
class="checkout-delivery-date"
:class="(meta.valid || !checkoutBlock) && 'active'"
>
<header class="checkout-aside-header green-text">
<strong class="checkout-aside-title">
Fecha de entrega
</strong>
</header>
<div class="checkout-delivery-body">
<p class="green-text">13 de julio - De 11h - 12 h</p>
</div>
</div>
<div class="checkout-summary">
<header class="checkout-aside-header gray-bg">
<strong class="checkout-aside-title">
Resumen del pedido
</strong>
</header>
<div class="checkout-summary-body gray-bg">
<ul class="checkout-summary-list">
<li
class="checkout-summary-item"
v-for="({ title, price }, index) in cart"
:key="index"
>
<p>
{{ title }}
<span>{{ price }}</span>
</p>
</li>
</ul>
<p class="green-text">Envio Gratuíto</p>
</div>
<footer class="checkout-summary-footer">
<p class="checkout-summary-paragraph">Total</p>
<p class="checkout-summary-paragraph summary-price">
{{ totalPrice }}€
</p>
</footer>
</div>
<div class="checkout-payment-methods gray-bg">
<header class="checkout-aside-header">
<strong class="checkout-aside-title">Método de pago</strong>
</header>
<div class="checkout-payment-body">
<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="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" 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" 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
v-for="({ title, price }, index) in cart"
:key="index"
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">{{ title }}</p>
</div>
<p class="checkout-product-price">
{{ price }}
</p>
</div>
</li>
</ul>
</div>
<footer class="checkout-success-footer">
<p class="checkout-success-paragraph">Total</p>
<p class="checkout-success-paragraph">{{ totalPrice }}.00</p>
</footer>
</div>
</div>
</div>
</Container>
</q-page>
</template>
<script>
import { toTypedSchema } from "@vee-validate/zod";
import { storeToRefs } from "pinia";
import { useForm } from "vee-validate";
import { defineComponent, reactive, ref } from "vue";
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 { useCartStore } from "src/stores/cart";
import { useFormStore } from "src/stores/forms";
import { checkoutSchema } from "src/utils/zod/schemas/checkoutSchema";
export default defineComponent({
name: "CheckoutPage",
components: {
Container,
IconAny,
IconVisa,
IconExpress,
IconMaster,
},
setup() {
const cartStore = useCartStore();
const { cart, totalPrice } = storeToRefs(cartStore);
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: [
{
value: 1,
name: "Paso 1",
description: "Datos de facturación",
active: true,
},
{
value: 2,
name: "Paso 2",
description: "Confirmación",
active: false,
},
{
value: 3,
name: "Paso 3",
description: "Pago",
active: false,
},
],
});
const checkoutBlock = ref(true);
const onSubmit = handleSubmit((values) => {
handleCheckoutData(values);
stepList.data[2].active = true;
checkoutBlock.value = false;
resetForm();
});
const handleClickStep = (value) => {
stepActive["data"] = value;
};
const stepsFormated = () => {
return stepList["data"].map((step) => {
if (step.value === stepActive["data"]) {
return { ...step, active: true };
}
return step;
});
};
const provinceOptions = ref([
{ code: "01", name: "Araba/Álava" },
{ code: "02", name: "Albacete" },
{ code: "03", name: "Alicante/Alacant" },
{ code: "04", name: "Almería" },
{ code: "05", name: "Ávila" },
{ code: "06", name: "Badajoz" },
{ code: "07", name: "Balears, Illes" },
{ code: "08", name: "Barcelona" },
{ code: "09", name: "Burgos" },
{ code: "10", name: "Cáceres" },
{ code: "11", name: "Cádiz" },
{ code: "12", name: "Castellón/Castelló" },
{ code: "13", name: "Ciudad Real" },
{ code: "14", name: "Córdoba" },
{ code: "15", name: "Coruña, A" },
{ code: "16", name: "Cuenca" },
{ code: "17", name: "Girona" },
{ code: "18", name: "Granada" },
{ code: "19", name: "Guadalajara" },
{ code: "20", name: "Gipuzkoa" },
{ code: "21", name: "Huelva" },
{ code: "22", name: "Huesca" },
{ code: "23", name: "Jaén" },
{ code: "24", name: "León" },
{ code: "25", name: "Lleida" },
{ code: "26", name: "Rioja, La" },
{ code: "27", name: "Lugo" },
{ code: "28", name: "Madrid" },
{ code: "29", name: "Málaga" },
{ code: "30", name: "Murcia" },
{ code: "31", name: "Navarra" },
{ code: "32", name: "Ourense" },
{ code: "33", name: "Asturias" },
{ code: "34", name: "Palencia" },
{ code: "35", name: "Palmas, Las" },
{ code: "36", name: "Pontevedra" },
{ code: "37", name: "Salamanca" },
{ code: "38", name: "Santa Cruz de Tenerife" },
{ code: "39", name: "Cantabria" },
{ code: "40", name: "Segovia" },
{ code: "41", name: "Sevilla" },
{ code: "42", name: "Soria" },
{ code: "43", name: "Tarragona" },
{ code: "44", name: "Teruel" },
{ code: "45", name: "Toledo" },
{ code: "46", name: "Valencia/València" },
{ code: "47", name: "Valladolid" },
{ code: "48", name: "Bizkaia" },
{ code: "49", name: "Zamora" },
{ code: "50", name: "Zaragoza" },
{ code: "51", name: "Ceuta" },
{ code: "52", name: "Melilla" },
]);
return {
handleClickStep,
stepsFormated,
onSubmit,
stepList,
provinceOptions,
totalPrice,
step: ref(1),
cart,
checkoutBlock,
meta,
errors,
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,
};
},
});
</script>
<style lang="scss" scoped>
.checkout-page {
.checkout-steps {
display: flex;
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;
border: 1px solid $primary-dark;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
user-select: none;
.step-value {
font-family: $font-questrial;
color: $primary-dark;
font-size: 1.25rem;
}
&.active {
background-color: $primary-dark;
.step-value {
color: $white;
}
}
}
.step-content {
display: flex;
flex-direction: column;
align-items: center;
font-family: $font-questrial;
h4 {
font-size: 1rem;
font-weight: 700;
color: $text-default;
margin-top: 5px;
margin-bottom: 4px;
line-height: 1.3;
}
p {
font-size: 0.875rem;
color: $text-default;
font-family: $font-lora;
}
}
& .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: 21px;
border-radius: 5px;
h3 {
color: $text-default;
font-size: 0.875rem;
line-height: 1.5;
}
@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;
user-select: none;
gap: 18px;
padding: 0 23px 0;
background-color: $primary-light;
transition: 200ms ease-in;
opacity: 0;
visibility: hidden;
height: 0;
overflow: hidden;
&.active {
height: min-content;
padding-block: 16px 18px;
opacity: 1;
visibility: visible;
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;
&.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: 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 {
padding-bottom: 10px;
}
.q-field__control {
background-color: #fff;
height: 40px;
border: 1px solid $primary-light;
input {
padding: 0px 30px;
font-family: $font-questrial;
color: $text-default !important;
}
&.text-negative {
border-color: $negative;
}
&::after,
&::before {
display: none;
}
}
.q-field__marginal {
height: 40px;
padding-right: 30px;
}
}
.q-field--error .q-field__bottom {
padding-top: 4px;
font-size: 0.675rem;
}
&.field-select {
.q-field__control {
min-height: initial;
background-color: #fff;
height: 40px;
padding: 0px 30px;
border: 1px solid $primary-light;
.q-field__native span {
display: none;
}
}
.q-field__append {
height: 40px;
}
.q-field__label {
font-family: $font-questrial;
color: $text-default !important;
}
.q-field__control-container {
padding-top: 0;
height: 36px;
}
}
}
@media only screen and (max-width: $med-lg) {
&.sender {
margin-top: 45px;
}
}
}
}
</style>