coyotte508 HF staff commited on
Commit
3d330e9
1 Parent(s): 36f8d04

✨ Admin page for pages

Browse files
src/lib/server/db/index.ts CHANGED
@@ -19,4 +19,4 @@ 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 };
 
19
  const { pictures, picturesFs } = createPictureCollections(db);
20
 
21
  export { client, db, pages, users, pictures, picturesFs, products };
22
+ export const collections = { products, pictures, pages, users };
src/lib/server/db/page.ts CHANGED
@@ -28,7 +28,7 @@ Des tissus rigoureusement sélectionnés vous seront proposés pour habiller vos
28
 
29
  Daphné ne travaille que sur rendez vous, alors n'hésitez pas à la contacter, par téléphone ou par mail pour toute demande.`,
30
  'eshop-description': "description de l'eshop",
31
- description: `C'est dans son univers enchanteur que Daphné le Couls, tapissière d'ameublement qualifiée depuis 2019, vous propose la réfection de vos assises dans son atelier situé en Finistère, à logonna Daoulas (entre l'axe Brest Quimper).
32
 
33
  Daphné se déplace à votre domicile afin de déterminer avec vous vos besoins, qu'il s'agisse d'une réfection de siège complète, ou bien de la création de coussins décoratifs.`
34
  },
@@ -46,17 +46,23 @@ Daphné se déplace à votre domicile afin de déterminer avec vous vos besoins,
46
  'realisation-8': null,
47
  'realisation-9': null,
48
  'realisation-10': null
49
- }
 
 
50
  } as HomePage,
51
  '/contact': {
52
  _id: '/contact',
53
  name: 'Contact',
54
  text: {
 
 
55
  description: 'Je me déplace à votre domicile dans le Finistère sur rendez-vous.'
56
  },
57
  pictures: {
58
  'photo-garde': null
59
- }
 
 
60
  } as ContactPage,
61
  '/atelier': {
62
  _id: '/atelier',
@@ -70,7 +76,7 @@ Daphné se déplace à votre domicile afin de déterminer avec vous vos besoins,
70
  'texte-2': `Nous vous proposons un service en ligne afin de concevoir à distance des coussins, et de vous les livrer n'importe où en France.
71
 
72
  Nous proposons à la vente également des assises déjà refectionnées dans la partie E-shop.`,
73
- description:
74
  "À l'atelier, nous vous proposons la réfection traditionnelle ou moderne de vos assises (crin ou mousse) selon vos besoins."
75
  },
76
  pictures: {
@@ -83,7 +89,7 @@ Nous proposons à la vente également des assises déjà refectionnées dans la
83
  _id: '/realisations',
84
  name: 'Réalisations',
85
  text: {
86
- description:
87
  "Découvrez les sièges, fauteuils et coussins réalisés par Daphné, tapissière d'ameublement de la Bergère Enchantée",
88
  'realisation-1': '',
89
  'realisation-2': '',
@@ -94,7 +100,17 @@ Nous proposons à la vente également des assises déjà refectionnées dans la
94
  'realisation-7': '',
95
  'realisation-8': '',
96
  'realisation-9': '',
97
- 'realisation-10': ''
 
 
 
 
 
 
 
 
 
 
98
  },
99
  pictures: {
100
  'realisation-1': null,
@@ -106,8 +122,20 @@ Nous proposons à la vente également des assises déjà refectionnées dans la
106
  'realisation-7': null,
107
  'realisation-8': null,
108
  'realisation-9': null,
109
- 'realisation-10': null
110
- }
 
 
 
 
 
 
 
 
 
 
 
 
111
  } as CreationsPage,
112
  '/tissus-et-finitions': {
113
  _id: '/tissus-et-finitions',
@@ -130,7 +158,7 @@ Nous proposons à la vente également des assises déjà refectionnées dans la
130
  'photo-15': null
131
  },
132
  text: {
133
- description:
134
  "Découvrez les finitions et tissus utilisés par Daphné, tapissière d'ameublement de la Bergère Enchantée"
135
  }
136
  } as FabricsPage,
@@ -138,7 +166,7 @@ Nous proposons à la vente également des assises déjà refectionnées dans la
138
  _id: '/vente',
139
  name: 'E-shop',
140
  text: {
141
- description:
142
  'Liste des fauteuils, chaises, coussins... réalisés par Daphné et disponibles à la vente'
143
  },
144
  pictures: {
 
28
 
29
  Daphné ne travaille que sur rendez vous, alors n'hésitez pas à la contacter, par téléphone ou par mail pour toute demande.`,
30
  'eshop-description': "description de l'eshop",
31
+ 'search-engine-description': `C'est dans son univers enchanteur que Daphné le Couls, tapissière d'ameublement qualifiée depuis 2019, vous propose la réfection de vos assises dans son atelier situé en Finistère, à logonna Daoulas (entre l'axe Brest Quimper).
32
 
33
  Daphné se déplace à votre domicile afin de déterminer avec vous vos besoins, qu'il s'agisse d'une réfection de siège complète, ou bien de la création de coussins décoratifs.`
34
  },
 
46
  'realisation-8': null,
47
  'realisation-9': null,
48
  'realisation-10': null
49
+ },
50
+ createdAt: new Date(),
51
+ updatedAt: new Date()
52
  } as HomePage,
53
  '/contact': {
54
  _id: '/contact',
55
  name: 'Contact',
56
  text: {
57
+ 'search-engine-description':
58
+ 'Je me déplace à votre domicile dans le Finistère sur rendez-vous.',
59
  description: 'Je me déplace à votre domicile dans le Finistère sur rendez-vous.'
60
  },
61
  pictures: {
62
  'photo-garde': null
63
+ },
64
+ createdAt: new Date(),
65
+ updatedAt: new Date()
66
  } as ContactPage,
67
  '/atelier': {
68
  _id: '/atelier',
 
76
  'texte-2': `Nous vous proposons un service en ligne afin de concevoir à distance des coussins, et de vous les livrer n'importe où en France.
77
 
78
  Nous proposons à la vente également des assises déjà refectionnées dans la partie E-shop.`,
79
+ 'search-engine-description':
80
  "À l'atelier, nous vous proposons la réfection traditionnelle ou moderne de vos assises (crin ou mousse) selon vos besoins."
81
  },
82
  pictures: {
 
89
  _id: '/realisations',
90
  name: 'Réalisations',
91
  text: {
92
+ 'search-engine-description':
93
  "Découvrez les sièges, fauteuils et coussins réalisés par Daphné, tapissière d'ameublement de la Bergère Enchantée",
94
  'realisation-1': '',
95
  'realisation-2': '',
 
100
  'realisation-7': '',
101
  'realisation-8': '',
102
  'realisation-9': '',
103
+ 'realisation-10': '',
104
+ 'realisation-11': '',
105
+ 'realisation-12': '',
106
+ 'realisation-13': '',
107
+ 'realisation-14': '',
108
+ 'realisation-15': '',
109
+ 'realisation-16': '',
110
+ 'realisation-17': '',
111
+ 'realisation-18': '',
112
+ 'realisation-19': '',
113
+ 'realisation-20': ''
114
  },
115
  pictures: {
116
  'realisation-1': null,
 
122
  'realisation-7': null,
123
  'realisation-8': null,
124
  'realisation-9': null,
125
+ 'realisation-10': null,
126
+ 'realisation-11': null,
127
+ 'realisation-12': null,
128
+ 'realisation-13': null,
129
+ 'realisation-14': null,
130
+ 'realisation-15': null,
131
+ 'realisation-16': null,
132
+ 'realisation-17': null,
133
+ 'realisation-18': null,
134
+ 'realisation-19': null,
135
+ 'realisation-20': null
136
+ },
137
+ createdAt: new Date(),
138
+ updatedAt: new Date()
139
  } as CreationsPage,
140
  '/tissus-et-finitions': {
141
  _id: '/tissus-et-finitions',
 
158
  'photo-15': null
159
  },
160
  text: {
161
+ 'search-engine-description':
162
  "Découvrez les finitions et tissus utilisés par Daphné, tapissière d'ameublement de la Bergère Enchantée"
163
  }
164
  } as FabricsPage,
 
166
  _id: '/vente',
167
  name: 'E-shop',
168
  text: {
169
+ 'search-engine-description':
170
  'Liste des fauteuils, chaises, coussins... réalisés par Daphné et disponibles à la vente'
171
  },
172
  pictures: {
src/lib/types/Page.ts CHANGED
@@ -3,8 +3,8 @@ import type { Timestamps } from './Timestamps';
3
  export interface Page extends Timestamps {
4
  _id: string;
5
  name: string;
6
- text: Record<string, string>;
7
- pictures: Record<string, string | null>;
8
  }
9
 
10
  export interface HomePage extends Page {
@@ -13,60 +13,29 @@ export interface HomePage extends Page {
13
  text: {
14
  presentation: string;
15
  'eshop-description': string;
16
- description: string;
17
  };
18
  pictures: {
19
  discover: string | null;
20
  move: string | null;
21
  'e-shop': string | null;
22
- 'realisation-1': string | null;
23
- 'realisation-2': string | null;
24
- 'realisation-3': string | null;
25
- 'realisation-4': string | null;
26
- 'realisation-5': string | null;
27
- 'realisation-6': string | null;
28
- 'realisation-7': string | null;
29
- 'realisation-8': string | null;
30
- 'realisation-9': string | null;
31
- 'realisation-10': string | null;
32
- };
33
  }
34
 
35
  export interface CreationsPage extends Page {
36
  _id: '/realisations';
37
  name: 'Réalisations';
38
  text: {
39
- description: string;
40
- 'realisation-1': string;
41
- 'realisation-2': string;
42
- 'realisation-3': string;
43
- 'realisation-4': string;
44
- 'realisation-5': string;
45
- 'realisation-6': string;
46
- 'realisation-7': string;
47
- 'realisation-8': string;
48
- 'realisation-9': string;
49
- 'realisation-10': string;
50
- };
51
- pictures: {
52
- 'realisation-1': string | null;
53
- 'realisation-2': string | null;
54
- 'realisation-3': string | null;
55
- 'realisation-4': string | null;
56
- 'realisation-5': string | null;
57
- 'realisation-6': string | null;
58
- 'realisation-7': string | null;
59
- 'realisation-8': string | null;
60
- 'realisation-9': string | null;
61
- 'realisation-10': string | null;
62
- };
63
  }
64
 
65
  export interface FabricsPage {
66
  _id: '/tissus-et-finitions';
67
  name: 'Tissus et finitions';
68
  text: {
69
- description: string;
70
  };
71
  pictures: {
72
  'photo-1': string | null;
@@ -92,6 +61,7 @@ export interface ContactPage extends Page {
92
  name: 'Contact';
93
  text: {
94
  description: string;
 
95
  };
96
  pictures: {
97
  'photo-garde': string | null;
@@ -102,7 +72,7 @@ export interface WorkshopPage extends Page {
102
  _id: '/atelier';
103
  name: "L'Atelier";
104
  text: {
105
- description: string;
106
  'texte-1': string;
107
  'texte-2': string;
108
  };
@@ -117,7 +87,7 @@ export interface EshopPage extends Page {
117
  _id: '/vente';
118
  name: 'E-shop';
119
  text: {
120
- description: string;
121
  };
122
  pictures: {
123
  background: string | null;
 
3
  export interface Page extends Timestamps {
4
  _id: string;
5
  name: string;
6
+ text: Record<string, string | undefined>;
7
+ pictures: Record<string, string | null | undefined>;
8
  }
9
 
10
  export interface HomePage extends Page {
 
13
  text: {
14
  presentation: string;
15
  'eshop-description': string;
16
+ 'search-engine-description': string;
17
  };
18
  pictures: {
19
  discover: string | null;
20
  move: string | null;
21
  'e-shop': string | null;
22
+ } & Partial<Record<`realisation-${number}`, string | null>>;
 
 
 
 
 
 
 
 
 
 
23
  }
24
 
25
  export interface CreationsPage extends Page {
26
  _id: '/realisations';
27
  name: 'Réalisations';
28
  text: {
29
+ 'search-engine-description': string;
30
+ } & Partial<Record<`realisation-${number}`, string>>;
31
+ pictures: Partial<Record<`realisation-${number}`, string | null>>;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
  export interface FabricsPage {
35
  _id: '/tissus-et-finitions';
36
  name: 'Tissus et finitions';
37
  text: {
38
+ 'search-engine-description': string;
39
  };
40
  pictures: {
41
  'photo-1': string | null;
 
61
  name: 'Contact';
62
  text: {
63
  description: string;
64
+ 'search-engine-description': string;
65
  };
66
  pictures: {
67
  'photo-garde': string | null;
 
72
  _id: '/atelier';
73
  name: "L'Atelier";
74
  text: {
75
+ 'search-engine-description': string;
76
  'texte-1': string;
77
  'texte-2': string;
78
  };
 
87
  _id: '/vente';
88
  name: 'E-shop';
89
  text: {
90
+ 'search-engine-description': string;
91
  };
92
  pictures: {
93
  background: string | null;
src/lib/utils/typedKeys.ts ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ export function typedKeys<K extends string>(x: Record<K, any>): K[] {
2
+ return Object.keys(x) as K[];
3
+ }
src/routes/+layout.svelte CHANGED
@@ -24,9 +24,9 @@
24
  <meta property="twitter:title" content={$page.data.title} />
25
  {/if}
26
  {#if $page.data.description}
27
- <meta name="description" content={$page.data.description} />
28
- <meta property="og:description" content={$page.data.description} />
29
- <meta property="twitter:description" content={$page.data.description} />
30
  {/if}
31
  <meta property="og:type" content={$page.data.type || 'website'} />
32
  {#if shownPicture}
 
24
  <meta property="twitter:title" content={$page.data.title} />
25
  {/if}
26
  {#if $page.data.description}
27
+ <meta name="description" content={$page.data['search-engine-description']} />
28
+ <meta property="og:description" content={$page.data['search-engine-description']} />
29
+ <meta property="twitter:description" content={$page.data['search-engine-description']} />
30
  {/if}
31
  <meta property="og:type" content={$page.data.type || 'website'} />
32
  {#if shownPicture}
src/routes/+page.svelte CHANGED
@@ -3,6 +3,7 @@
3
  import Container from '$lib/components/Container.svelte';
4
  import Picture from '$lib/components/Picture.svelte';
5
  import type { HomePage } from '$lib/types/Page';
 
6
  import { marked } from 'marked';
7
  import type { PageData } from './$types';
8
 
@@ -13,7 +14,7 @@
13
 
14
  type PictureKey = keyof typeof pageData.pictures;
15
 
16
- const showcasePics = Object.keys(pageData.pictures)
17
  .filter((key) => key.startsWith('realisation-') && pageData.pictures[key as PictureKey])
18
  .map((key) => pictures.find((pic) => pic._id === pageData.pictures[key as PictureKey]))
19
  .filter(Boolean);
 
3
  import Container from '$lib/components/Container.svelte';
4
  import Picture from '$lib/components/Picture.svelte';
5
  import type { HomePage } from '$lib/types/Page';
6
+ import { typedKeys } from '$lib/utils/typedKeys';
7
  import { marked } from 'marked';
8
  import type { PageData } from './$types';
9
 
 
14
 
15
  type PictureKey = keyof typeof pageData.pictures;
16
 
17
+ const showcasePics = typedKeys(pageData.pictures)
18
  .filter((key) => key.startsWith('realisation-') && pageData.pictures[key as PictureKey])
19
  .map((key) => pictures.find((pic) => pic._id === pageData.pictures[key as PictureKey]))
20
  .filter(Boolean);
src/routes/admin/+layout.server.ts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { LayoutServerLoad } from './$types';
2
+ import { error, redirect } from '@sveltejs/kit';
3
+
4
+ export const load: LayoutServerLoad = (event) => {
5
+ if (!event.locals.user) {
6
+ throw redirect(303, `/connexion?suivant=${encodeURIComponent(event.url.href)}`);
7
+ }
8
+
9
+ if (event.locals.user.authority !== 'admin') {
10
+ throw error(403);
11
+ }
12
+ };
src/routes/admin/+layout.svelte ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { page } from '$app/stores';
3
+ import Container from '$lib/components/Container.svelte';
4
+ </script>
5
+
6
+ <div class="flex justify-evenly mb-10">
7
+ <a
8
+ href="/admin/pages"
9
+ class="pa-2 link"
10
+ class:text-sunray={$page.url.pathname.startsWith('/admin/pages')}>Pages</a
11
+ >
12
+ <a
13
+ href="/admin/photos"
14
+ class="pa-2 link"
15
+ class:text-sunray={$page.url.pathname.startsWith('/admin/photos')}>Photos</a
16
+ >
17
+ <a
18
+ href="/admin/produits"
19
+ class="pa-2 link"
20
+ class:text-sunray={$page.url.pathname.startsWith('/admin/produits')}>Produits</a
21
+ >
22
+ </div>
23
+
24
+ <Container>
25
+ <slot />
26
+ </Container>
src/routes/admin/+page.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import type { PageLoad } from './$types';
2
+ import { redirect } from '@sveltejs/kit';
3
+
4
+ export const load: PageLoad = () => {
5
+ throw redirect(302, '/admin/pages');
6
+ };
src/routes/admin/pages/+page.server.ts ADDED
@@ -0,0 +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
+ };
src/routes/admin/pages/+page.svelte ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import type { Page } from '$lib/types/Page';
3
+ import type { PageData } from './$types';
4
+
5
+ export let data: PageData;
6
+
7
+ function updatePicture(page: Page, key: string, value: string) {
8
+ fetch('/admin/pages/' + encodeURIComponent(page._id), {
9
+ method: 'POST',
10
+ headers: { 'content-type': 'application/json' },
11
+ body: JSON.stringify({ type: 'picture', key, value })
12
+ });
13
+ }
14
+
15
+ function updateText(page: Page, key: string, value: string) {
16
+ fetch('/admin/pages/' + encodeURIComponent(page._id), {
17
+ method: 'POST',
18
+ headers: { 'content-type': 'application/json' },
19
+ body: JSON.stringify({ type: 'text', key, value })
20
+ });
21
+ }
22
+ </script>
23
+
24
+ {#each data.pages as page}
25
+ <section class="mb-6">
26
+ <h1>{page.name} ({page._id})</h1>
27
+
28
+ <h2 class="mt-4">Textes</h2>
29
+
30
+ {#each Object.keys(page.text) as key}
31
+ <label class="block w-full mt-4">
32
+ <h3>{key}</h3>
33
+ <textarea
34
+ name="{page._id}_text_{key}"
35
+ cols="30"
36
+ rows="10"
37
+ class="block w-full"
38
+ value={page.text[key]}
39
+ on:blur={(event) => updateText(page, key, event.currentTarget.value)}
40
+ />
41
+ </label>
42
+ {/each}
43
+
44
+ <h2 class="mt-4">Images</h2>
45
+
46
+ {#each Object.keys(page.pictures) as key}
47
+ <label class="block w-full mt-4">
48
+ <h3>{key}</h3>
49
+ <select
50
+ name="{page._id}_picture_{key}"
51
+ value={page.pictures[key]}
52
+ on:change={(event) => updatePicture(page, key, event.currentTarget.value)}
53
+ >
54
+ {#each data.photos as photo}
55
+ <option value={photo._id}>{photo.name}</option>
56
+ {/each}
57
+ </select>
58
+ </label>
59
+ {/each}
60
+ </section>
61
+ {/each}
src/routes/admin/pages/[id]/+server.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { collections } from '$lib/server/db';
2
+ import { pages } from '$lib/server/db/page';
3
+ import { error } from '@sveltejs/kit';
4
+ import type { RequestHandler } from './$types';
5
+
6
+ export const POST: RequestHandler = async (input) => {
7
+ const id = input.params.id;
8
+
9
+ const body: { type: 'picture' | 'text'; key: string; value: string } = await input.request.json();
10
+
11
+ if (!(id in pages)) {
12
+ throw error(400, 'Mauvaise id de page: ' + id);
13
+ }
14
+
15
+ const type = body.type === 'picture' ? 'pictures' : body.type;
16
+
17
+ const page = pages[id as keyof typeof pages];
18
+
19
+ if (!(type in page) || !(body.key in page[type])) {
20
+ throw error(400, 'Mauvaise clé');
21
+ }
22
+
23
+ await collections.pages.updateOne(
24
+ { _id: id },
25
+ { $set: { [`${type}.${body.key}`]: String(body.value).replaceAll('\r', '') } },
26
+ { upsert: true }
27
+ );
28
+
29
+ return new Response();
30
+ };
src/routes/realisations/+page.svelte CHANGED
@@ -2,6 +2,7 @@
2
  import Container from '$lib/components/Container.svelte';
3
  import PictureComponent from '$lib/components/Picture.svelte';
4
  import type { CreationsPage } from '$lib/types/Page';
 
5
  import { marked } from 'marked';
6
  import type { PageData } from './$types';
7
 
@@ -12,7 +13,7 @@
12
 
13
  type PictureKey = keyof typeof pageData.pictures;
14
 
15
- const picKeys = Object.keys(pageData.pictures).filter(
16
  (key) => key.startsWith('realisation-') && pageData.pictures[key as PictureKey]
17
  );
18
  </script>
 
2
  import Container from '$lib/components/Container.svelte';
3
  import PictureComponent from '$lib/components/Picture.svelte';
4
  import type { CreationsPage } from '$lib/types/Page';
5
+ import { typedKeys } from '$lib/utils/typedKeys';
6
  import { marked } from 'marked';
7
  import type { PageData } from './$types';
8
 
 
13
 
14
  type PictureKey = keyof typeof pageData.pictures;
15
 
16
+ const picKeys = typedKeys(pageData.pictures).filter(
17
  (key) => key.startsWith('realisation-') && pageData.pictures[key as PictureKey]
18
  );
19
  </script>