first commit
This commit is contained in:
commit
e1362c0d36
25 changed files with 13100 additions and 0 deletions
288
components/GiftCard.vue
Normal file
288
components/GiftCard.vue
Normal file
|
@ -0,0 +1,288 @@
|
|||
<script setup>
|
||||
const props = defineProps({
|
||||
item: { type: Object, default: () => {} },
|
||||
statusMessage: { type: String, default: "" },
|
||||
});
|
||||
|
||||
let showDetails = ref(true);
|
||||
|
||||
const formError = ref("");
|
||||
const isFormVisible = ref(false);
|
||||
function showReservation() {
|
||||
isFormVisible.value = !isFormVisible.value;
|
||||
}
|
||||
|
||||
const reserved = ref(false);
|
||||
const fromName = ref("");
|
||||
const emit = defineEmits(["reserved"]);
|
||||
|
||||
const sendReservation = (id, reserve, name) => {
|
||||
console.log(fromName);
|
||||
if (reserved.value && fromName.value.length > 1) {
|
||||
emit("reserved", { id, reserve, name });
|
||||
formError.value = "";
|
||||
} else {
|
||||
formError.value = "Veuillez remplir le formulaire";
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="gift flow">
|
||||
<div class="gift__info">
|
||||
<h2 class="h3">
|
||||
<NuxtLink :to="item.url">{{ item.name }}</NuxtLink>
|
||||
</h2>
|
||||
<div class="gift__status">
|
||||
<p v-if="!item.reserved">🎉 Ce cadeau est disponible !</p>
|
||||
<p v-else>
|
||||
Ce cadeau est déjà réservé par
|
||||
<span class="from-name">{{ item.from || "¯\_(ツ)_/¯" }}</span> 🤗
|
||||
</p>
|
||||
</div>
|
||||
<p v-if="!item.reserved && item.prix" class="h2 gift__price">
|
||||
{{ item.prix }} €
|
||||
</p>
|
||||
<button v-if="!item.reserved" @click="showReservation" class="btn">
|
||||
Réserver
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="!item.reserved" class="gift__actions">
|
||||
<div v-if="isFormVisible" class="flow">
|
||||
<form>
|
||||
<div class="field form__confirmation">
|
||||
<label :for="`name-${item.id}`">Votre nom (requis)</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
:id="`name-${item.id}`"
|
||||
placeholder="Jeanne"
|
||||
v-model="fromName"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="field form__reservation">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="check"
|
||||
:id="`check-${item.id}`"
|
||||
v-model="reserved"
|
||||
required
|
||||
/>
|
||||
<label :for="`check-${item.id}`">Confirmer la réservation</label>
|
||||
</div>
|
||||
<button
|
||||
@click.prevent="sendReservation(item.id, reserved, fromName)"
|
||||
:disabled="reserved && fromName.length > 1 ? false : true"
|
||||
>
|
||||
Valider le cadeau
|
||||
</button>
|
||||
</form>
|
||||
<div
|
||||
v-if="statusMessage || formError"
|
||||
aria-live="polite"
|
||||
aria-atomic="false"
|
||||
class="form__errors"
|
||||
>
|
||||
<p v-if="statusMessage">
|
||||
{{ statusMessage }}
|
||||
</p>
|
||||
<p v-if="formError">
|
||||
{{ formError }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<details
|
||||
v-if="!item.reserved"
|
||||
class="flow"
|
||||
:open="showDetails"
|
||||
@click.prevent="showDetails = !showDetails"
|
||||
>
|
||||
<summary>
|
||||
<span v-if="showDetails">Masquer les détails</span>
|
||||
<span v-else>Voir les détails</span>
|
||||
</summary>
|
||||
<div class="gift__details">
|
||||
<NuxtPicture
|
||||
v-if="item.photo"
|
||||
format="avif,webp"
|
||||
class="gift__photo"
|
||||
:src="`http://localhost:8055/assets/${item.photo.id}?width=200&height=200&fit=inside`"
|
||||
loading="lazy"
|
||||
width="200"
|
||||
height="200"
|
||||
fit="inside"
|
||||
alt=""
|
||||
/>
|
||||
<div class="gift__buying">
|
||||
<span v-if="item.used" class="gift__used">Occasion ok !</span>
|
||||
<p
|
||||
v-if="!item.reserved && item.description"
|
||||
class="gift__description"
|
||||
>
|
||||
{{ item.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.gift {
|
||||
container-type: inline-size;
|
||||
container-name: card;
|
||||
border-radius: 8px;
|
||||
padding: var(--space-xs-s);
|
||||
box-shadow: 0 0 8px 0 hsla(24, 91%, 35%, 0.2);
|
||||
background-color: white;
|
||||
}
|
||||
.gift__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-s-m);
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
.gift__status {
|
||||
color: var(--color-yellow-dark);
|
||||
}
|
||||
details {
|
||||
margin-block-start: var(--space-xs);
|
||||
text-align: center;
|
||||
}
|
||||
details summary {
|
||||
font-size: var(--size--1);
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-yellow-dark);
|
||||
}
|
||||
.gift__details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-xs);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.gift__photo {
|
||||
align-self: center;
|
||||
}
|
||||
.gift__buying {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-2xs);
|
||||
}
|
||||
.gift__price {
|
||||
margin-block: var(--space-3xs);
|
||||
padding: var(--space-2xs);
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
align-self: center;
|
||||
border-block: 2px solid var(--color-green-dark);
|
||||
}
|
||||
.gift__used {
|
||||
padding: var(--space-3xs) var(--space-2xs);
|
||||
font-size: var(--size--1);
|
||||
background-color: var(--color-green-light);
|
||||
border-radius: 40px;
|
||||
}
|
||||
.gift__description {
|
||||
padding: var(--space-2xs) var(--space-xs);
|
||||
font-size: var(--size-0);
|
||||
border-radius: 8px;
|
||||
color: var(--color-green-light);
|
||||
background-color: var(--color-green-dark);
|
||||
}
|
||||
|
||||
.from-name {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-block-start: auto;
|
||||
padding: var(--space-2xs) var(--space-xs);
|
||||
position: relative;
|
||||
align-self: center;
|
||||
font-size: var(--size-0);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-green-light);
|
||||
background-color: white;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
.btn:focus-within,
|
||||
.btn:hover {
|
||||
background-color: var(--color-yellow-light);
|
||||
}
|
||||
|
||||
form,
|
||||
.form__confirmation {
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
.form__reservation label {
|
||||
margin-inline-start: var(--space-3xs);
|
||||
}
|
||||
.form__confirmation input {
|
||||
padding: var(--space-3xs) var(--space-2xs);
|
||||
font-size: var(--size--1);
|
||||
font-weight: bold;
|
||||
border: 1px solid var(--color-green-dark);
|
||||
}
|
||||
form button {
|
||||
padding: var(--space-2xs) var(--space-2xs);
|
||||
font-size: var(--size--1);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
transition-property: background-color, color;
|
||||
transition-duration: 0.3s;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
form button:not([disabled]) {
|
||||
color: var(--color-yellow-light);
|
||||
border-color: var(--color-green-dark);
|
||||
background-color: var(--color-green-dark);
|
||||
}
|
||||
form button:not([disabled]):is(:hover, :focus) {
|
||||
color: var(--color-green-dark);
|
||||
background-color: var(--color-yellow-light);
|
||||
}
|
||||
.form__errors {
|
||||
font-size: var(--size--1);
|
||||
}
|
||||
|
||||
@container card (min-inline-size: 60rem) {
|
||||
.gift__info {
|
||||
flex-flow: row wrap;
|
||||
justify-content: flex-start;
|
||||
row-gap: var(--space-2xs);
|
||||
}
|
||||
.gift__price {
|
||||
margin: 0;
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
.gift__actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: var(--space-s-m);
|
||||
align-content: end;
|
||||
}
|
||||
details {
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
grid-column: 2 / 3;
|
||||
}
|
||||
.gift__details {
|
||||
flex-flow: row nowrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.gift__description {
|
||||
text-align: left;
|
||||
}
|
||||
.gift__buying {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
47
components/SortFilter.vue
Normal file
47
components/SortFilter.vue
Normal file
|
@ -0,0 +1,47 @@
|
|||
<script setup>
|
||||
const selectedSort = ref("");
|
||||
|
||||
const emit = defineEmits(["sort", "display"]);
|
||||
const sortData = (sortParam) => {
|
||||
emit("sort", sortParam);
|
||||
};
|
||||
const changeDisplay = () => {
|
||||
emit("display");
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form>
|
||||
<div>
|
||||
<label for="sort">Trier par : </label>
|
||||
<select
|
||||
name="sort"
|
||||
id="sort"
|
||||
v-model="selectedSort"
|
||||
@change="sortData(selectedSort)"
|
||||
>
|
||||
<option disabled value="">Sélectionnez une option</option>
|
||||
<option value="prix">Prix croissant</option>
|
||||
<option value="-prix">Prix décroissant</option>
|
||||
<option :value="['reserved', 'prix']">Non réservés</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- <div>
|
||||
<input type="checkbox" name="list" id="list" @change="changeDisplay" />
|
||||
<label for="list"> Affichage en liste</label>
|
||||
</div> -->
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
form {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-2xs);
|
||||
}
|
||||
select {
|
||||
padding: var(--space-2xs);
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
Loading…
Add table
Add a link
Reference in a new issue