first commit

This commit is contained in:
Nico 2023-11-02 15:42:34 +01:00
commit e1362c0d36
25 changed files with 13100 additions and 0 deletions

24
.gitignore vendored Normal file
View 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
View 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
View 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>

View 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%);
}

View 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 */

View 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;
}

View 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%);
}

View 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
View File

@ -0,0 +1,7 @@
@import-glob './global/*.css';
/* @import-glob './blocks/*.css'; */
@import-glob './compositions/*.css';
/* @import-glob './utilities/*.css'; */

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/images/jungle.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
bun.lockb Executable file

Binary file not shown.

288
components/GiftCard.vue Normal file
View 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&nbsp;!</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 }}&nbsp;
</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&nbsp;!</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
View 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&nbsp;: </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
View 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

File diff suppressed because it is too large Load Diff

23
package.json Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/jungle.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

22
queries/gifts.gql Normal file
View 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
}
}

View File

@ -0,0 +1,6 @@
query getHomepage {
homepage {
date_updated
content
}
}

3
server/tsconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

4
tsconfig.json Normal file
View File

@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}