first commit
This commit is contained in:
commit
e1362c0d36
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Nuxt dev/build outputs
|
||||||
|
.output
|
||||||
|
.data
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Node dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.fleet
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
75
README.md
Normal file
75
README.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Nuxt 3 Minimal Starter
|
||||||
|
|
||||||
|
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Make sure to install the dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Server
|
||||||
|
|
||||||
|
Start the development server on `http://localhost:3000`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm run dev
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn dev
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
Build the application for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm run build
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn build
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Locally preview production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm run preview
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn preview
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
130
app.vue
Normal file
130
app.vue
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
<script setup>
|
||||||
|
// ITEM FETCHING //
|
||||||
|
// set initial sort order
|
||||||
|
const sortVariable = ref(["reserved", "date_created"]);
|
||||||
|
const reservedVariable = ref(true);
|
||||||
|
// fetch data
|
||||||
|
const { homepage } = await GqlGetHomepage();
|
||||||
|
const { data, refresh } = await useAsyncData("gifts", () =>
|
||||||
|
GqlGifts({ sort: sortVariable.value })
|
||||||
|
);
|
||||||
|
// update sort param
|
||||||
|
function sortList(param) {
|
||||||
|
sortVariable.value = param;
|
||||||
|
// refetch query (expose token or access pubic data)
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
// filter out already reserved items
|
||||||
|
const filteredList = computed(() => {
|
||||||
|
const list = data;
|
||||||
|
return list;
|
||||||
|
});
|
||||||
|
|
||||||
|
// CHANGE DISPLAY //
|
||||||
|
const listDisplay = ref(false);
|
||||||
|
function toggleDisplay() {
|
||||||
|
listDisplay.value = !listDisplay.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ITEM RESERVATION //
|
||||||
|
// no error to start
|
||||||
|
const errorStatus = ref(false);
|
||||||
|
const statusMessage = ref("");
|
||||||
|
// update the chosen item's reservation status
|
||||||
|
async function runMutation(payload) {
|
||||||
|
try {
|
||||||
|
const result = await GqlUpdateItem({
|
||||||
|
id: payload.id,
|
||||||
|
reserved: payload.reserve,
|
||||||
|
name: payload.name,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// if error, set the status to true
|
||||||
|
console.log("---ERREUR---", error);
|
||||||
|
errorStatus.value = true;
|
||||||
|
} finally {
|
||||||
|
if (errorStatus.value) {
|
||||||
|
// if error is true, show an error notice
|
||||||
|
statusMessage.value = "Une erreur est survenue… ¯\\_(ツ)_/¯";
|
||||||
|
// cleanup the error status
|
||||||
|
errorStatus.value = false;
|
||||||
|
} else {
|
||||||
|
// if all is well, show a success notice
|
||||||
|
statusMessage.value = `C'est noté merci beaucoup ${payload.name} 🥰`;
|
||||||
|
setTimeout(() => {
|
||||||
|
refresh();
|
||||||
|
}, 3500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main class="flow">
|
||||||
|
<h1>Liste de naissance</h1>
|
||||||
|
<section class="intro sidebar--reverse" data-direction="rtl">
|
||||||
|
<article v-html="homepage.content" class="editorial" />
|
||||||
|
<img
|
||||||
|
src="~/assets/images/jungle.jpg"
|
||||||
|
class="intro__image"
|
||||||
|
width="400"
|
||||||
|
height="600"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
<hr />
|
||||||
|
<SortFilter :onSort="sortList" :onDisplay="toggleDisplay" />
|
||||||
|
<hr />
|
||||||
|
<ul
|
||||||
|
class="gifts gifts--grid"
|
||||||
|
:class="{ 'gifts--list': listDisplay }"
|
||||||
|
role="list"
|
||||||
|
>
|
||||||
|
<li v-for="gift of data?.gifts" :key="gift.id" class="gift-item">
|
||||||
|
<GiftCard
|
||||||
|
:item="gift"
|
||||||
|
:onReserved="runMutation"
|
||||||
|
v-model:status-message="statusMessage"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.intro {
|
||||||
|
--sidebar-target-width: 400px;
|
||||||
|
--sidebar-target-grow: 0;
|
||||||
|
margin: var(--space-m-l) auto;
|
||||||
|
max-inline-size: 90ch;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.intro__image {
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
filter: brightness(110%);
|
||||||
|
height: 600px;
|
||||||
|
width: 400px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
margin-block: var(--space-xs-s);
|
||||||
|
border-bottom: 3px dotted var(--color-yellow-dark);
|
||||||
|
}
|
||||||
|
.gifts {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-s-m);
|
||||||
|
}
|
||||||
|
.gifts--grid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
|
||||||
|
}
|
||||||
|
.gifts--grid .gift-item > :deep(:first-child) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.gifts--list {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
</style>
|
55
assets/css/compositions/sidebar.css
Normal file
55
assets/css/compositions/sidebar.css
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
CUSTOM PROPERTIES AND CONFIGURATION
|
||||||
|
--gutter (var(--space-size-1)): This defines the space
|
||||||
|
between the sidebar and main content.
|
||||||
|
|
||||||
|
--sidebar-target-width (20rem): How large the sidebar should be
|
||||||
|
|
||||||
|
--sidebar-content-min-width(50%): The minimum size of the main content area
|
||||||
|
|
||||||
|
EXCEPTIONS
|
||||||
|
.sidebar[data-direction='rtl']: flips the sidebar to be on the right
|
||||||
|
*/
|
||||||
|
.sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--gutter, var(--space-xs-s));
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar > :first-child {
|
||||||
|
flex-basis: var(--sidebar-target-width, 20rem);
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar > :last-child {
|
||||||
|
flex-basis: 0;
|
||||||
|
flex-grow: 999;
|
||||||
|
min-width: var(--sidebar-content-min-width, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
A flipped version where the sidebar is on the right
|
||||||
|
*/
|
||||||
|
.sidebar[data-direction="rtl"],
|
||||||
|
.sidebar--reverse[data-direction="rtl"] {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* REVERSE sidebar on right*/
|
||||||
|
|
||||||
|
.sidebar--reverse {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--gutter, var(--space-xs-s));
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar--reverse > :last-child {
|
||||||
|
flex-basis: var(--sidebar-target-width, 20rem);
|
||||||
|
flex-grow: var(--sidebar-target-grow, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar--reverse > :first-child {
|
||||||
|
flex-basis: 0;
|
||||||
|
flex-grow: 999;
|
||||||
|
min-width: var(--sidebar-content-min-width, 50%);
|
||||||
|
}
|
122
assets/css/global/01-reset.css
Normal file
122
assets/css/global/01-reset.css
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/* RESET */
|
||||||
|
* {
|
||||||
|
/* Remove default margin on everything */
|
||||||
|
margin: 0;
|
||||||
|
/* Remove default padding on everything */
|
||||||
|
padding: 0;
|
||||||
|
/* Calc `em` based line height, bigger line height for smaller font size and smaller line height for bigger font size: https://kittygiraudel.com/2020/05/18/using-calc-to-figure-out-optimal-line-height/ */
|
||||||
|
line-height: calc(0.25rem + 1em + 0.25rem);
|
||||||
|
}
|
||||||
|
/* Use a more-intuitive box-sizing model on everything */
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
/* Remove border and set sensible defaults for backgrounds, on all elements except fieldset progress and meter */
|
||||||
|
*:where(:not(fieldset, progress, meter)) {
|
||||||
|
border-width: 0;
|
||||||
|
border-style: solid;
|
||||||
|
background-origin: border-box;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
/* Allow percentage-based heights in the application */
|
||||||
|
block-size: 100%;
|
||||||
|
/* Making sure text size is only controlled by font-size */
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
/* Improve text rendering */
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
/* Smooth scrolling for users that don't prefer reduced motion */
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
html:focus-within {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
/* Allow percentage-based heights in the application */
|
||||||
|
min-block-size: 100%;
|
||||||
|
/* https://www.sarasoueidan.com/blog/safari-fluid-typography-bug-fix/ */
|
||||||
|
-webkit-marquee-increment: 0vw;
|
||||||
|
}
|
||||||
|
/* Improve media defaults */
|
||||||
|
:where(img, svg, video, canvas, audio, iframe, embed, object) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
:where(img, svg, video) {
|
||||||
|
block-size: auto;
|
||||||
|
max-inline-size: 100%;
|
||||||
|
}
|
||||||
|
/* Smaller line height for titles */
|
||||||
|
:where(h1, h2, h3, h1 a, h2 a, h3 a) {
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
/* Avoid text overflows */
|
||||||
|
:where(p, h1, h2, h3, h4, h5, h6) {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
|
||||||
|
:where(ul, ol)[role="list"] {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
/* More readable underline style for anchor tags without a class. This could be set on anchor tags globally, but it can cause conflicts. */
|
||||||
|
a:not([class]) {
|
||||||
|
text-decoration-skip-ink: auto;
|
||||||
|
}
|
||||||
|
/* Make it clear that interactive elements are interactive */
|
||||||
|
:where(
|
||||||
|
a[href],
|
||||||
|
area,
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
label[for],
|
||||||
|
select,
|
||||||
|
summary,
|
||||||
|
textarea,
|
||||||
|
[tabindex]:not([tabindex*="-"])
|
||||||
|
) {
|
||||||
|
cursor: pointer;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
:where(input[type="file"]) {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
:where(input[type="file"])::-webkit-file-upload-button,
|
||||||
|
:where(input[type="file"])::file-selector-button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure users can't select button text */
|
||||||
|
:where(
|
||||||
|
button,
|
||||||
|
button[type],
|
||||||
|
input[type="button"],
|
||||||
|
input[type="submit"],
|
||||||
|
input[type="reset"]
|
||||||
|
),
|
||||||
|
:where(input[type="file"])::-webkit-file-upload-button,
|
||||||
|
:where(input[type="file"])::file-selector-button {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
user-select: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
/* Disabled cursor for disabled buttons */
|
||||||
|
:where(
|
||||||
|
button,
|
||||||
|
button[type],
|
||||||
|
input[type="button"],
|
||||||
|
input[type="submit"],
|
||||||
|
input[type="reset"]
|
||||||
|
)[disabled] {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
/* END RESET */
|
35
assets/css/global/02-fonts.css
Normal file
35
assets/css/global/02-fonts.css
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "wotfard";
|
||||||
|
src: url("~/assets/fonts/wotfard/wotfard-medium-fr.woff2") format("woff2");
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "wotfard";
|
||||||
|
src: url("~/assets/fonts/wotfard/wotfard-semibold-fr.woff2") format("woff2");
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "wotfard";
|
||||||
|
src: url("~/assets/fonts/wotfard/wotfard-regular-fr.woff2") format("woff2");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* reduces Cumulative Layout Shift
|
||||||
|
* https://www.24joursdeweb.fr/2021/performance-web-lintegrateur-ce-heros/
|
||||||
|
*/
|
||||||
|
@font-face {
|
||||||
|
font-family: "ArialReplace";
|
||||||
|
src: local("Arial");
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
size-adjust: 94%;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
48
assets/css/global/03-variables.css
Normal file
48
assets/css/global/03-variables.css
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
:root {
|
||||||
|
/* font sizes */
|
||||||
|
--size--2: clamp(0.79rem, calc(0.76rem + 0.16vw), 0.89rem);
|
||||||
|
--size--1: clamp(0.89rem, calc(0.85rem + 0.19vw), 1rem);
|
||||||
|
--size-0: clamp(1rem, calc(0.96rem + 0.21vw), 1.13rem);
|
||||||
|
--size-1: clamp(1.13rem, calc(1.08rem + 0.23vw), 1.27rem);
|
||||||
|
--size-2: clamp(1.27rem, calc(1.21rem + 0.26vw), 1.42rem);
|
||||||
|
--size-3: clamp(1.42rem, calc(1.36rem + 0.3vw), 1.6rem);
|
||||||
|
--size-4: clamp(1.6rem, calc(1.54rem + 0.33vw), 1.8rem);
|
||||||
|
--size-5: clamp(1.8rem, calc(1.73rem + 0.38vw), 2.03rem);
|
||||||
|
|
||||||
|
/* spaces */
|
||||||
|
--space-3xs: clamp(0.31rem, calc(0.31rem + 0vw), 0.31rem);
|
||||||
|
--space-2xs: clamp(0.56rem, calc(0.53rem + 0.14vw), 0.63rem);
|
||||||
|
--space-xs: clamp(0.88rem, calc(0.85rem + 0.14vw), 0.94rem);
|
||||||
|
--space-s: clamp(1.13rem, calc(1.07rem + 0.28vw), 1.25rem);
|
||||||
|
--space-m: clamp(1.69rem, calc(1.6rem + 0.42vw), 1.88rem);
|
||||||
|
--space-l: clamp(2.25rem, calc(2.14rem + 0.56vw), 2.5rem);
|
||||||
|
--space-xl: clamp(3.38rem, calc(3.21rem + 0.83vw), 3.75rem);
|
||||||
|
--space-2xl: clamp(4.5rem, calc(4.28rem + 1.11vw), 5rem);
|
||||||
|
--space-3xl: clamp(6.75rem, calc(6.42rem + 1.67vw), 7.5rem);
|
||||||
|
|
||||||
|
/* One-up pairs */
|
||||||
|
--space-3xs-2xs: clamp(0.31rem, calc(0.17rem + 0.69vw), 0.63rem);
|
||||||
|
--space-2xs-xs: clamp(0.56rem, calc(0.4rem + 0.83vw), 0.94rem);
|
||||||
|
--space-xs-s: clamp(0.88rem, calc(0.71rem + 0.83vw), 1.25rem);
|
||||||
|
--space-s-m: clamp(1.13rem, calc(0.79rem + 1.67vw), 1.88rem);
|
||||||
|
--space-m-l: clamp(1.69rem, calc(1.33rem + 1.81vw), 2.5rem);
|
||||||
|
--space-l-xl: clamp(2.25rem, calc(1.58rem + 3.33vw), 3.75rem);
|
||||||
|
--space-xl-2xl: clamp(3.38rem, calc(2.65rem + 3.61vw), 5rem);
|
||||||
|
--space-2xl-3xl: clamp(4.5rem, calc(3.17rem + 6.67vw), 7.5rem);
|
||||||
|
|
||||||
|
/* multi steps */
|
||||||
|
--space-3xs-s: clamp(0.31rem, calc(-0.1rem + 2.08vw), 1.25rem);
|
||||||
|
--space-2xs-s: clamp(0.56rem, calc(0.26rem + 1.53vw), 1.25rem);
|
||||||
|
--space-2xs-m: clamp(0.56rem, calc(-0.02rem + 2.92vw), 1.88rem);
|
||||||
|
--space-xs-m: clamp(0.88rem, calc(0.43rem + 2.22vw), 1.88rem);
|
||||||
|
--space-xs-l: clamp(0.88rem, calc(0.15rem + 3.61vw), 2.5rem);
|
||||||
|
--space-s-l: clamp(1.13rem, calc(0.51rem + 3.06vw), 2.5rem);
|
||||||
|
--space-s-xl: clamp(1.13rem, calc(-0.04rem + 5.83vw), 3.75rem);
|
||||||
|
--space-l-2xl: clamp(2.25rem, calc(1.03rem + 6.11vw), 5rem);
|
||||||
|
|
||||||
|
/* COLORS */
|
||||||
|
--color-yellow-light: hsl(20, 56%, 95%);
|
||||||
|
--color-yellow-dark: hsl(353, 13%, 27%);
|
||||||
|
--color-green-light: hsl(36, 95%, 66%);
|
||||||
|
--color-green-dark: hsl(145, 59%, 11%);
|
||||||
|
}
|
45
assets/css/global/04-base.css
Normal file
45
assets/css/global/04-base.css
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
body {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: var(--space-s-l) var(--space-xs-s);
|
||||||
|
max-inline-size: 80rem;
|
||||||
|
font-family: "wotfard", "ArialReplace", Arial, sans-serif;
|
||||||
|
font-size: var(--size-0);
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--color-green-dark);
|
||||||
|
background-color: var(--color-yellow-light);
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
h1,
|
||||||
|
.h1 {
|
||||||
|
font-size: var(--size-4);
|
||||||
|
}
|
||||||
|
h2,
|
||||||
|
.h2 {
|
||||||
|
font-size: var(--size-2);
|
||||||
|
}
|
||||||
|
h3,
|
||||||
|
.h3 {
|
||||||
|
font-size: var(--size-1);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow > * + * {
|
||||||
|
margin-block-start: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
padding-inline-start: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorial > * + * {
|
||||||
|
margin-block-start: var(--space-2xs);
|
||||||
|
}
|
||||||
|
.editorial h2 + * {
|
||||||
|
margin-block-start: var(--space-xs);
|
||||||
|
}
|
7
assets/css/style.css
Normal file
7
assets/css/style.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
@import-glob './global/*.css';
|
||||||
|
|
||||||
|
/* @import-glob './blocks/*.css'; */
|
||||||
|
|
||||||
|
@import-glob './compositions/*.css';
|
||||||
|
|
||||||
|
/* @import-glob './utilities/*.css'; */
|
BIN
assets/fonts/wotfard/wotfard-medium-fr.woff2
Normal file
BIN
assets/fonts/wotfard/wotfard-medium-fr.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/wotfard/wotfard-regular-fr.woff2
Normal file
BIN
assets/fonts/wotfard/wotfard-regular-fr.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/wotfard/wotfard-semibold-fr.woff2
Normal file
BIN
assets/fonts/wotfard/wotfard-semibold-fr.woff2
Normal file
Binary file not shown.
BIN
assets/images/jungle.jpg
Normal file
BIN
assets/images/jungle.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
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>
|
37
nuxt.config.ts
Normal file
37
nuxt.config.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
app: {
|
||||||
|
head: {
|
||||||
|
meta: [
|
||||||
|
{ charset: "utf-8" },
|
||||||
|
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
||||||
|
{
|
||||||
|
name: "robots",
|
||||||
|
content: "noindex,nofollow",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
css: ["@/assets/css/style.css"],
|
||||||
|
modules: ["nuxt-graphql-client", "@nuxt/image"],
|
||||||
|
|
||||||
|
runtimeConfig: { public: { GQL_HOST: "http://0.0.0.0:8055/graphql" } },
|
||||||
|
|
||||||
|
image: {
|
||||||
|
dir: "assets/images",
|
||||||
|
domains: ["http://0.0.0.0:8055"],
|
||||||
|
alias: { directus: "http://0.0.0.0:8055/assets/" },
|
||||||
|
presets: {
|
||||||
|
gift: { modifiers: { width: 200, height: 200, fit: "contain" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
postcss: {
|
||||||
|
plugins: {
|
||||||
|
"postcss-import-ext-glob": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
telemetry: false,
|
||||||
|
typescript: {
|
||||||
|
shim: false,
|
||||||
|
},
|
||||||
|
});
|
12129
package-lock.json
generated
Normal file
12129
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
package.json
Normal file
23
package.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "nuxt-app",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nuxt/devtools": "latest",
|
||||||
|
"@nuxt/image": "npm:@nuxt/image-edge@latest",
|
||||||
|
"nuxt": "^3.8.0",
|
||||||
|
"postcss-import-ext-glob": "^2.1.1",
|
||||||
|
"vue": "^3.3.7",
|
||||||
|
"vue-router": "^4.2.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"nuxt-graphql-client": "^0.2.31"
|
||||||
|
}
|
||||||
|
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
BIN
public/jungle.jpg
Normal file
BIN
public/jungle.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
22
queries/gifts.gql
Normal file
22
queries/gifts.gql
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
query gifts($sort: [String!]) {
|
||||||
|
gifts(filter: { status: { _eq: "published" } }, sort: $sort) {
|
||||||
|
status
|
||||||
|
id
|
||||||
|
name
|
||||||
|
url
|
||||||
|
prix
|
||||||
|
used
|
||||||
|
description
|
||||||
|
from
|
||||||
|
reserved
|
||||||
|
photo {
|
||||||
|
id
|
||||||
|
filename_disk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutation UpdateItem($id: ID!, $reserved: Boolean!, $name: String!) {
|
||||||
|
update_gifts_item(id: $id, data: { reserved: $reserved, from: $name }) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
6
queries/singles/homepage.gql
Normal file
6
queries/singles/homepage.gql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
query getHomepage {
|
||||||
|
homepage {
|
||||||
|
date_updated
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
3
server/tsconfig.json
Normal file
3
server/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../.nuxt/tsconfig.server.json"
|
||||||
|
}
|
4
tsconfig.json
Normal file
4
tsconfig.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user