coyotte508 HF staff commited on
Commit
de10f77
1 Parent(s): 7069a57

✨ Products listing in admin

Browse files
src/hooks.server.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { users } from '$lib/server/db';
2
  import type { Handle } from '@sveltejs/kit';
3
 
4
  export const handle: Handle = async ({ event, resolve }) => {
5
  const token = event.cookies.get('bergereToken');
6
 
7
  if (token) {
8
- event.locals.user = await users.findOne({ token });
9
  }
10
 
11
  const response = await resolve(event);
 
1
+ import { collections } from '$lib/server/db';
2
  import type { Handle } from '@sveltejs/kit';
3
 
4
  export const handle: Handle = async ({ event, resolve }) => {
5
  const token = event.cookies.get('bergereToken');
6
 
7
  if (token) {
8
+ event.locals.user = await collections.users.findOne({ token });
9
  }
10
 
11
  const response = await resolve(event);
src/lib/server/db/index.ts CHANGED
@@ -18,5 +18,5 @@ const users = createUserCollection(db, client);
18
  const products = createProductCollection(db);
19
  const { pictures, picturesFs } = createPictureCollections(db);
20
 
21
- export { client, db, pages, users, pictures, picturesFs, products };
22
- export const collections = { products, pictures, pages, users };
 
18
  const products = createProductCollection(db);
19
  const { pictures, picturesFs } = createPictureCollections(db);
20
 
21
+ export { client, db };
22
+ export const collections = { products, pictures, pages, users, picturesFs };
src/lib/types/Product.ts CHANGED
@@ -6,7 +6,7 @@ export interface Product extends Timestamps {
6
  name: string;
7
  description: string;
8
  price: number;
9
- kind: 'armchair' | 'cushion';
10
  state: 'draft' | 'published' | 'retired';
11
 
12
  photos: Picture[];
 
6
  name: string;
7
  description: string;
8
  price: number;
9
+ kind: 'armchair' | 'cushion' | 'chair' | 'couch' | 'tufting';
10
  state: 'draft' | 'published' | 'retired';
11
 
12
  photos: Picture[];
src/routes/+layout.server.ts CHANGED
@@ -3,7 +3,7 @@ import '$lib/server/db';
3
  import { pages } from '$lib/server/db/page';
4
  import type { Picture } from '$lib/types/Picture';
5
  import { filterNullish } from '$lib/utils/filterNullish';
6
- import { pictures } from '$lib/server/db';
7
 
8
  export const load: LayoutServerLoad = async (input) => {
9
  const pageId = input.url.pathname;
@@ -12,7 +12,7 @@ export const load: LayoutServerLoad = async (input) => {
12
  const pageData = pages[pageId as keyof typeof pages];
13
 
14
  const pictureIds = filterNullish(Object.values(pageData.pictures));
15
- const pics = await pictures.find({ _id: { $in: pictureIds } }).toArray();
16
 
17
  return {
18
  pageData,
 
3
  import { pages } from '$lib/server/db/page';
4
  import type { Picture } from '$lib/types/Picture';
5
  import { filterNullish } from '$lib/utils/filterNullish';
6
+ import { collections } from '$lib/server/db';
7
 
8
  export const load: LayoutServerLoad = async (input) => {
9
  const pageId = input.url.pathname;
 
12
  const pageData = pages[pageId as keyof typeof pages];
13
 
14
  const pictureIds = filterNullish(Object.values(pageData.pictures));
15
+ const pics = await collections.pictures.find({ _id: { $in: pictureIds } }).toArray();
16
 
17
  return {
18
  pageData,
src/routes/+layout.svelte CHANGED
@@ -348,6 +348,14 @@
348
  }
349
  }
350
 
 
 
 
 
 
 
 
 
351
  /*
352
  @media (min-width: 1280px) { ... }
353
  @media (min-width: 1536px) { ... }
 
348
  }
349
  }
350
 
351
+ select.input {
352
+ cursor: pointer;
353
+ }
354
+
355
+ textarea.input {
356
+ max-width: calc(100% - 1rem);
357
+ }
358
+
359
  /*
360
  @media (min-width: 1280px) { ... }
361
  @media (min-width: 1536px) { ... }
src/routes/admin/pages/+page.server.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { pictures } from '$lib/server/db';
2
  import { pages } from '$lib/server/db/page';
3
  import type { PageServerLoad } from './$types';
4
 
5
  export const load: PageServerLoad = async () => {
6
  return {
7
  pages: Object.values(pages),
8
- photos: await pictures.find({ productId: { $exists: false } }).toArray()
9
  };
10
  };
 
1
+ import { collections } from '$lib/server/db';
2
  import { pages } from '$lib/server/db/page';
3
  import type { PageServerLoad } from './$types';
4
 
5
  export const load: PageServerLoad = async () => {
6
  return {
7
  pages: Object.values(pages),
8
+ photos: await collections.pictures.find({ productId: { $exists: false } }).toArray()
9
  };
10
  };
src/routes/admin/photos/[id]/+page.server.ts CHANGED
@@ -1,10 +1,10 @@
1
  import type { Picture } from '$lib/types/Picture';
2
- import { client, pictures, picturesFs } from '$lib/server/db';
3
  import { error, redirect } from '@sveltejs/kit';
4
  import type { PageServerLoad, Actions } from './$types';
5
 
6
  export const load: PageServerLoad = async ({ params }) => {
7
- const picture = await pictures.findOne({ _id: params.id });
8
 
9
  if (!picture) {
10
  throw error(404, 'Photo non trouvée');
@@ -18,7 +18,7 @@ export const load: PageServerLoad = async ({ params }) => {
18
  export const actions: Actions = {
19
  update: async function (input) {
20
  const name = String((await input.request.formData()).get('name'));
21
- await pictures.updateOne(
22
  { _id: input.params.id },
23
  {
24
  $set: {
@@ -34,8 +34,9 @@ export const actions: Actions = {
34
  let picture: Picture | null = null;
35
 
36
  await client.withSession(async (session) => {
37
- picture = (await pictures.findOneAndDelete({ _id: params.id }, { session })).value;
38
- await picturesFs.deleteMany({ picture: params.id }, { session });
 
39
  });
40
 
41
  if (!picture) {
 
1
  import type { Picture } from '$lib/types/Picture';
2
+ import { client, collections } from '$lib/server/db';
3
  import { error, redirect } from '@sveltejs/kit';
4
  import type { PageServerLoad, Actions } from './$types';
5
 
6
  export const load: PageServerLoad = async ({ params }) => {
7
+ const picture = await collections.pictures.findOne({ _id: params.id });
8
 
9
  if (!picture) {
10
  throw error(404, 'Photo non trouvée');
 
18
  export const actions: Actions = {
19
  update: async function (input) {
20
  const name = String((await input.request.formData()).get('name'));
21
+ await collections.pictures.updateOne(
22
  { _id: input.params.id },
23
  {
24
  $set: {
 
34
  let picture: Picture | null = null;
35
 
36
  await client.withSession(async (session) => {
37
+ picture = (await collections.pictures.findOneAndDelete({ _id: params.id }, { session }))
38
+ .value;
39
+ await collections.picturesFs.deleteMany({ picture: params.id }, { session });
40
  });
41
 
42
  if (!picture) {
src/routes/admin/produits/+page.server.ts ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { PageServerLoad } from './$types';
2
+ import { collections } from '$lib/server/db';
3
+
4
+ export const load: PageServerLoad = async () => {
5
+ const products = await collections.products.find({}).toArray();
6
+ const pictures = await collections.pictures
7
+ .find({ productId: { $in: products.map((p) => p._id) } })
8
+ .sort({ createdAt: 1 })
9
+ .toArray();
10
+
11
+ const byId = Object.fromEntries(products.map((p) => [p._id, p]));
12
+
13
+ for (const picture of pictures) {
14
+ byId[picture.productId!].photos = [...(byId[picture.productId!].photos || []), picture];
15
+ }
16
+
17
+ return {
18
+ products
19
+ };
20
+ };
src/routes/admin/produits/+page.svelte ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import Picture from '$lib/components/Picture.svelte';
3
+ import type { PageData } from './$types';
4
+
5
+ export let data: PageData;
6
+ </script>
7
+
8
+ <a href="/admin/produits/nouveau" class="link">Nouveau produit</a>
9
+
10
+ <h1 class="text-sunray mt-10">Liste des produits</h1>
11
+
12
+ <div class="flex flex-row flex-wrap gap-6 mt-6">
13
+ {#each data.products as product}
14
+ <div class="flex flex-col text-center">
15
+ <a href="/admin/produits/{product._id}" class="flex flex-col items-center">
16
+ <Picture picture={product.photos[0]} class="h-36 block" style="object-fit: scale-down;" />
17
+ <span class="mt-2">{product.name}</span>
18
+ </a>
19
+ </div>
20
+ {/each}
21
+ </div>
src/routes/admin/produits/[id]/+page.server.ts ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { collections } from '$lib/server/db';
2
+ import type { Product } from '$lib/types/Product';
3
+ import { error } from '@sveltejs/kit';
4
+ import type { Actions, PageServerLoad } from './$types';
5
+ import { omit } from 'lodash';
6
+
7
+ export const load: PageServerLoad = async ({ params }) => {
8
+ const product = await collections.products.findOne({ _id: params.id });
9
+
10
+ if (!product) {
11
+ throw error(404, 'Produit non trouvé');
12
+ }
13
+
14
+ const pictures = await collections.pictures
15
+ .find({ productId: params.id })
16
+ .sort({ createdAt: 1 })
17
+ .toArray();
18
+ product.photos = pictures;
19
+
20
+ return {
21
+ product
22
+ };
23
+ };
24
+
25
+ export const actions: Actions = {
26
+ default: async ({ request, params }) => {
27
+ const formData = await request.formData();
28
+
29
+ const update = {
30
+ ...(formData.get('name') && { name: formData.get('name') as string }),
31
+ ...(formData.get('state') && { state: formData.get('state') as Product['state'] }),
32
+ ...(formData.get('kind') && { kind: formData.get('kind') as Product['kind'] }),
33
+ ...(formData.get('description') && {
34
+ description: (formData.get('description') as string).replaceAll('\r', '')
35
+ }),
36
+ ...(formData.get('price') && { price: Number(formData.get('price')) }),
37
+ updatedAt: new Date()
38
+ };
39
+
40
+ const product = await collections.products.findOneAndUpdate(
41
+ { _id: params.id },
42
+ { $set: update },
43
+ { returnDocument: 'after' }
44
+ );
45
+
46
+ if (!product.value) {
47
+ throw error(404, 'Produit non trouvé');
48
+ }
49
+
50
+ return {};
51
+ }
52
+ };
src/routes/admin/produits/[id]/+page.svelte ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import PictureComponent from '$lib/components/Picture.svelte';
3
+ import type { PageData } from './$types';
4
+
5
+ export let data: PageData;
6
+ </script>
7
+
8
+ <h1 class="text-sunray">Modifier un produit</h1>
9
+
10
+ <div class="flex flex-col">
11
+ <form method="post">
12
+ <label class="block w-full mt-4 leading-8">
13
+ Nom
14
+ <input type="text" name="name" class="input block" value={data.product.name} />
15
+ </label>
16
+ <label class="block w-full mt-4 leading-8">
17
+ Prix
18
+ <input type="number" name="price" class="input block" value={data.product.price} />
19
+ </label>
20
+ <label class="block w-full mt-4 leading-8">
21
+ Etat
22
+ <select name="state" value={data.product.state} class="block input">
23
+ <option value="draft" selected={data.product.state === 'draft'}>Privé</option>
24
+ <option value="published" selected={data.product.state === 'published'}>Public</option>
25
+ <option value="retired" selected={data.product.state === 'retired'}>Vendu</option>
26
+ </select>
27
+ </label>
28
+ <label class="block w-full mt-4 leading-8">
29
+ Type
30
+
31
+ <select name="kind" value={data.product.kind} class="block input">
32
+ <option value="armchair" selected={data.product.kind === 'armchair'}>Fauteuil</option>
33
+ <option value="chair" selected={data.product.kind === 'chair'}>Chaise</option>
34
+ <option value="couch" selected={data.product.kind === 'couch'}>Canapé</option>
35
+ <option value="cushion" selected={data.product.kind === 'cushion'}>Coussin</option>
36
+ <option value="tufting" selected={data.product.kind === 'tufting'}>Tufting</option>
37
+ </select>
38
+ </label>
39
+
40
+ <label class="block my-4 w-full">
41
+ Description
42
+ <textarea name="description" cols="30" rows="10" class="block input"
43
+ >{data.product.description}</textarea
44
+ >
45
+ </label>
46
+
47
+ <button type="submit" class="mt-4 btn">Valider</button>
48
+ </form>
49
+ </div>
50
+
51
+ <h2 class="text-sunray my-4">Photos</h2>
52
+
53
+ <a href="/admin/photos/nouveau?productId={data.product._id}" class="link">Nouvelle photo</a>
54
+
55
+ <div class="flex flex-row flex-wrap gap-6 mt-6">
56
+ {#each data.product.photos as photo}
57
+ <div class="flex flex-col text-center">
58
+ <a href="/admin/photos/{photo._id}" class="flex flex-col items-center">
59
+ <PictureComponent picture={photo} class="h-36 block" style="object-fit: scale-down;" />
60
+ <span>{photo.name}</span>
61
+ </a>
62
+ </div>
63
+ {/each}
64
+ </div>
src/routes/admin/produits/nouveau/+page.svelte ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <h1 class="text-sunray">Ajouter un produit</h1>
2
+
3
+ <form method="post" enctype="multipart/form-data" action="/admin/produits">
4
+ <label class="block my-4 leading-8">
5
+ Nom du produit
6
+ <input class="input block" type="text" name="name" placeholder="Nom définitif" required />
7
+ </label>
8
+
9
+ <label class="block my-4 leading-8">
10
+ Prix
11
+ <input class="input block" type="number" name="price" placeholder="Prix (€)" required />
12
+ </label>
13
+
14
+ <label class="block w-full mt-4">
15
+ Description
16
+ <textarea name="description" cols="30" rows="10" class="block w-full" />
17
+ </label>
18
+
19
+ <label class="block my-4 leading-8">
20
+ Photo de garde
21
+ <input type="file" name="photo" accept="image/jpeg" class="block" required />
22
+ </label>
23
+
24
+ <label class="block my-4 leading-8">
25
+ Type
26
+ <select name="kind">
27
+ <option value="armchair">Fauteuil</option>
28
+ <option value="chair">Chaise</option>
29
+ <option value="couch">Canapé</option>
30
+ <option value="cushion">Coussin</option>
31
+ <option value="tufting">Tufting</option>
32
+ </select>
33
+ </label>
34
+
35
+ <input type="submit" class="btn" />
36
+ </form>
src/routes/compte/+page.server.ts CHANGED
@@ -1,7 +1,7 @@
1
  import { redirect } from '@sveltejs/kit';
2
- import type { ServerLoad } from './$types';
3
 
4
- export const load: ServerLoad = (event) => {
5
  if (!event.locals.user) {
6
  throw redirect(303, `/connexion?suivant=${encodeURIComponent(event.url.href)}`);
7
  }
 
1
  import { redirect } from '@sveltejs/kit';
2
+ import type { PageServerLoad } from './$types';
3
 
4
+ export const load: PageServerLoad = (event) => {
5
  if (!event.locals.user) {
6
  throw redirect(303, `/connexion?suivant=${encodeURIComponent(event.url.href)}`);
7
  }
src/routes/compte/+page.svelte CHANGED
@@ -15,11 +15,7 @@
15
  <p><a href="/admin" class="link">Admin</a></p>
16
  {/if}
17
 
18
- <form
19
- action="/deconnexion"
20
- method="post"
21
- class="text-sunray text-3xl inline-block cursor-pointer"
22
- >
23
- <input type="submit" value="Déconnexion" class="cursor-pointer btn" />
24
  </form>
25
  </Container>
 
15
  <p><a href="/admin" class="link">Admin</a></p>
16
  {/if}
17
 
18
+ <form action="/deconnexion" method="post" class="text-sunray text-3xl inline-block">
19
+ <input type="submit" value="Déconnexion" class="btn" />
 
 
 
 
20
  </form>
21
  </Container>
src/routes/connexion/+page.server.ts CHANGED
@@ -1,8 +1,8 @@
1
  import { error, redirect } from '@sveltejs/kit';
2
- import { users } from '$lib/server/db';
3
  import bcrypt from 'bcryptjs';
4
  import type { Actions } from './$types';
5
  import { addYears } from 'date-fns';
 
6
 
7
  export const actions: Actions = {
8
  default: async (event) => {
@@ -14,7 +14,10 @@ export const actions: Actions = {
14
 
15
  const email = data.get('email')!.toString().trim();
16
 
17
- const user = await users.findOne({ email }, { collation: { locale: 'en', strength: 1 } });
 
 
 
18
 
19
  if (!user) {
20
  throw error(404, "Utilisateur non trouvé pour l'email: " + email);
@@ -30,7 +33,7 @@ export const actions: Actions = {
30
 
31
  if (!token) {
32
  token = crypto.randomUUID();
33
- await users.updateOne({ _id: user._id }, { $set: { token } });
34
  }
35
 
36
  event.cookies.set('bergereToken', token, {
 
1
  import { error, redirect } from '@sveltejs/kit';
 
2
  import bcrypt from 'bcryptjs';
3
  import type { Actions } from './$types';
4
  import { addYears } from 'date-fns';
5
+ import { collections } from '$lib/server/db';
6
 
7
  export const actions: Actions = {
8
  default: async (event) => {
 
14
 
15
  const email = data.get('email')!.toString().trim();
16
 
17
+ const user = await collections.users.findOne(
18
+ { email },
19
+ { collation: { locale: 'en', strength: 1 } }
20
+ );
21
 
22
  if (!user) {
23
  throw error(404, "Utilisateur non trouvé pour l'email: " + email);
 
33
 
34
  if (!token) {
35
  token = crypto.randomUUID();
36
+ await collections.users.updateOne({ _id: user._id }, { $set: { token } });
37
  }
38
 
39
  event.cookies.set('bergereToken', token, {
src/routes/contact/+page.server.ts CHANGED
@@ -18,10 +18,10 @@ export const actions: Actions = {
18
  };
19
  }
20
 
21
- const firstName = body.get('firstName').toString().trim();
22
- const lastName = body.get('lastName').toString().trim();
23
- const message = body.get('message').toString().trim();
24
- const email = body.get('email').toString().trim();
25
 
26
  await mg.messages.create('mails.bergereenchantee.fr', {
27
  from: 'Formulaire de contact <contact@mails.bergereenchantee.fr>',
 
18
  };
19
  }
20
 
21
+ const firstName = body.get('firstName')!.toString().trim();
22
+ const lastName = body.get('lastName')!.toString().trim();
23
+ const message = body.get('message')!.toString().trim();
24
+ const email = body.get('email')!.toString().trim();
25
 
26
  await mg.messages.create('mails.bergereenchantee.fr', {
27
  from: 'Formulaire de contact <contact@mails.bergereenchantee.fr>',
src/routes/contact/+page.svelte CHANGED
@@ -113,7 +113,7 @@
113
  </div>
114
 
115
  <div class="w-full flex mt-4">
116
- <button type="submit" class="btn ml-auto cursor-pointer"> Envoyer </button>
117
  </div>
118
  </form>
119
  {/if}
 
113
  </div>
114
 
115
  <div class="w-full flex mt-4">
116
+ <button type="submit" class="btn ml-auto"> Envoyer </button>
117
  </div>
118
  </form>
119
  {/if}
src/routes/photos/raw/[id]/+server.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { picturesFs } from '$lib/server/db';
2
  import { error } from '@sveltejs/kit';
3
  import type { RequestHandler } from './$types';
4
 
5
  export const GET: RequestHandler = async ({ params }) => {
6
- const fs = await picturesFs.findOne({ _id: params.id });
7
 
8
  if (!fs) {
9
  throw error(404, 'Image non trouvée');
 
1
+ import { collections } from '$lib/server/db';
2
  import { error } from '@sveltejs/kit';
3
  import type { RequestHandler } from './$types';
4
 
5
  export const GET: RequestHandler = async ({ params }) => {
6
+ const fs = await collections.picturesFs.findOne({ _id: params.id });
7
 
8
  if (!fs) {
9
  throw error(404, 'Image non trouvée');
vite.config.ts CHANGED
@@ -27,8 +27,8 @@ const config: UserConfig = {
27
  shortcuts: {
28
  input: 'w-full max-w-80 text-lg pl-2 border border-solid border-2 rounded-xl',
29
  link: 'underline text-brunswick',
30
- btn: 'text-white bg-oxford px-4 py-2 rounded-3xl font-bold border-0',
31
- 'btn-sunray': 'text-white bg-sunray px-4 py-2 rounded-3xl font-bold border-0'
32
  },
33
  theme: {
34
  colors: {
 
27
  shortcuts: {
28
  input: 'w-full max-w-80 text-lg pl-2 border border-solid border-2 rounded-xl',
29
  link: 'underline text-brunswick',
30
+ btn: 'text-white bg-oxford px-4 py-2 rounded-3xl font-bold border-0 cursor-pointer',
31
+ 'btn-sunray': 'text-white bg-sunray px-4 py-2 rounded-3xl font-bold border-0 cursor-pointer'
32
  },
33
  theme: {
34
  colors: {