Compare commits
39 Commits
e2c41561da
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b486c4f3fe | |||
|
|
bc02da9d48 | ||
| 7e474a85a2 | |||
| 8f8fcb83c9 | |||
| 936000b32a | |||
| 5315f1b188 | |||
| d6d54df384 | |||
| f1c880690f | |||
| 9fee97dea6 | |||
| 789dc380c1 | |||
| fe94d4c543 | |||
| f8f59a5134 | |||
| 3d1336c3b2 | |||
| 05eb117e4b | |||
| 84728c9532 | |||
| 5dc33897ce | |||
| 067a151be1 | |||
| 539e537572 | |||
| 60c295d731 | |||
| 0e99cb4cd7 | |||
| 212baf8efc | |||
| 148d664b44 | |||
| d1ad6e0f09 | |||
| 72f652eabe | |||
| b23809eb47 | |||
| cd3cb704be | |||
| b0f2bf142b | |||
| d9dbb43926 | |||
| 50ad21bb42 | |||
| 4a64e90a47 | |||
| 70d8d83691 | |||
| 66495f7b17 | |||
| 5be7d43446 | |||
|
|
9d21f06d51 | ||
|
|
e02e6bae43 | ||
|
|
ed565cc5c7 | ||
|
|
68f6a8088b | ||
|
|
485cf758ff | ||
|
|
a2b5044326 |
@@ -12,7 +12,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '24'
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- run: npm ci && npm run build
|
- run: npm ci && npm run build
|
||||||
|
|
||||||
@@ -21,4 +21,6 @@ jobs:
|
|||||||
docker build -t cachetdeco:latest .
|
docker build -t cachetdeco:latest .
|
||||||
docker stop cachetdeco 2>/dev/null || true
|
docker stop cachetdeco 2>/dev/null || true
|
||||||
docker rm cachetdeco 2>/dev/null || true
|
docker rm cachetdeco 2>/dev/null || true
|
||||||
docker run -d --name cachetdeco -p 30001:80 --restart unless-stopped cachetdeco:latest
|
docker run -d --name cachetdeco -p 30001:4321 --restart unless-stopped \
|
||||||
|
-e RESEND_API_KEY=${{ secrets.RESEND_API_KEY }} \
|
||||||
|
cachetdeco:latest
|
||||||
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
# Build output
|
# Build output
|
||||||
dist/
|
dist/
|
||||||
.astro/
|
.astro/
|
||||||
|
.wrangler/
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|||||||
16
Dockerfile
@@ -1,3 +1,13 @@
|
|||||||
FROM nginx:alpine
|
FROM node:24-alpine AS runtime
|
||||||
COPY dist/ /usr/share/nginx/html/
|
WORKDIR /app
|
||||||
EXPOSE 80
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV HOST=0.0.0.0
|
||||||
|
ENV PORT=4321
|
||||||
|
EXPOSE 4321
|
||||||
|
CMD ["node", "./dist/server/entry.mjs"]
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Cachet Peintres Décorateurs — Site Web
|
# Cachet Peintres Décorateurs — Site Web
|
||||||
|
|
||||||
Site vitrine statique pour **Cachet Peintres Décorateurs** (9500-5609 Québec Inc).
|
Site vitrine statique pour **Cachet Peintres Décorateurs** (CACHETDECO).
|
||||||
Construit avec **Astro v5 + Tailwind v4 + Decap CMS**, déployé sur **Cloudflare Pages**.
|
Construit avec **Astro v5 + Tailwind v4 + Decap CMS**, déployé sur **Cloudflare Pages**.
|
||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
@@ -180,7 +180,6 @@ Aucune refactorisation structurelle n'est nécessaire — l'infrastructure est d
|
|||||||
|
|
||||||
Conformément aux exigences légales québécoises, les informations suivantes doivent apparaître visiblement sur le site :
|
Conformément aux exigences légales québécoises, les informations suivantes doivent apparaître visiblement sur le site :
|
||||||
|
|
||||||
- **Raison sociale :** 9500-5609 Québec Inc
|
|
||||||
- **Numéro RBQ :** 5839 8736 01 (obligatoire pour tout service nécessitant une modification d'un bâtiment)
|
- **Numéro RBQ :** 5839 8736 01 (obligatoire pour tout service nécessitant une modification d'un bâtiment)
|
||||||
|
|
||||||
Ces informations sont affichées dans le footer de chaque page.
|
Ces informations sont affichées dans le footer de chaque page.
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { defineConfig } from 'astro/config';
|
|||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
import sitemap from '@astrojs/sitemap';
|
import sitemap from '@astrojs/sitemap';
|
||||||
import react from '@astrojs/react';
|
import react from '@astrojs/react';
|
||||||
|
import cloudflare from '@astrojs/cloudflare';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
output: 'static',
|
output: 'server',
|
||||||
|
adapter: cloudflare(),
|
||||||
site: 'https://cachetdeco.com',
|
site: 'https://cachetdeco.com',
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: 'fr',
|
defaultLocale: 'fr',
|
||||||
@@ -24,5 +26,8 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()],
|
plugins: [tailwindcss()],
|
||||||
|
ssr: {
|
||||||
|
noExternal: ['motion', 'motion/react'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
76
docs/cloudflare-workers-astro-v6.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Cloudflare Workers + Astro v6 Deployment Notes
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
- Astro v6 + `@astrojs/cloudflare` adapter (v13+)
|
||||||
|
- Deployed as **Cloudflare Worker** (not Pages)
|
||||||
|
- Build: `npm run build` → Deploy: `npx wrangler deploy`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Issue 1 — `wrangler.json` with `pages_build_output_dir` breaks deploy
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
The name 'ASSETS' is reserved in Pages projects. Please use a different name for your Assets binding.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Having `pages_build_output_dir` in `wrangler.json` makes Wrangler treat the project as a Pages project. The adapter generates `dist/server/wrangler.json` with an `ASSETS` binding for static assets, which is reserved in Pages context.
|
||||||
|
|
||||||
|
**Fix:** Remove `pages_build_output_dir` from `wrangler.json`, or delete the file entirely. The adapter generates a complete `dist/server/wrangler.json` — Wrangler picks it up automatically via config redirect.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Issue 2 — `locals.runtime.env` removed in Astro v6
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
Astro.locals.runtime.env has been removed in Astro v6.
|
||||||
|
Use 'import { env } from "cloudflare:workers"' instead.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Astro v6 dropped the `locals.runtime.env` pattern for accessing Cloudflare env vars/bindings.
|
||||||
|
|
||||||
|
**Fix:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ❌ Astro v5 (broken in v6)
|
||||||
|
const runtime = (locals as any).runtime;
|
||||||
|
const apiKey = runtime?.env?.RESEND_API_KEY;
|
||||||
|
|
||||||
|
// ✅ Astro v6
|
||||||
|
import { env } from 'cloudflare:workers';
|
||||||
|
const apiKey = (env as unknown as Record<string, string>).RESEND_API_KEY;
|
||||||
|
```
|
||||||
|
|
||||||
|
Install types and add to `tsconfig.json`:
|
||||||
|
```bash
|
||||||
|
npm install --save-dev @cloudflare/workers-types
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
// tsconfig.json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["@cloudflare/workers-types"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Env vars in Cloudflare Workers dashboard
|
||||||
|
|
||||||
|
- Set vars under **Worker → Settings → Variables and Secrets**
|
||||||
|
- They are accessible via `env` from `cloudflare:workers` at runtime
|
||||||
|
- Do **not** rely on `process.env` or `import.meta.env` for server-side secrets in Workers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wrangler config redirect (how it works)
|
||||||
|
|
||||||
|
When `astro build` runs, `@astrojs/cloudflare` generates `dist/server/wrangler.json`. Wrangler detects this and logs:
|
||||||
|
```
|
||||||
|
Using redirected Wrangler configuration.
|
||||||
|
- Configuration being used: "dist/server/wrangler.json"
|
||||||
|
- Original user's configuration: "wrangler.json"
|
||||||
|
```
|
||||||
|
The generated config includes the worker `main` entry, `assets` binding, `compatibility_date`, and any adapter-detected bindings (e.g. `IMAGES`, `SESSION`).
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
interface SoumissionBody {
|
|
||||||
nom: string;
|
|
||||||
courriel: string;
|
|
||||||
telephone: string;
|
|
||||||
type_service: string;
|
|
||||||
adresse?: string;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Env {
|
|
||||||
RESEND_API_KEY: string;
|
|
||||||
CONTACT_EMAIL: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serviceLabels: Record<string, string> = {
|
|
||||||
interieur: 'Peinture intérieure',
|
|
||||||
exterieur: 'Peinture extérieure',
|
|
||||||
commercial: 'Peinture commerciale',
|
|
||||||
decoration: 'Décoration intérieure',
|
|
||||||
autre: 'Autre',
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildEmailHtml(data: SoumissionBody): string {
|
|
||||||
const serviceLabel = serviceLabels[data.type_service] ?? data.type_service;
|
|
||||||
const adresseLine = data.adresse
|
|
||||||
? `<tr><td style="padding:8px 12px;color:#666;font-size:14px;border-bottom:1px solid #eee">Adresse des travaux</td><td style="padding:8px 12px;font-size:14px;border-bottom:1px solid #eee">${data.adresse}</td></tr>`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return `<!DOCTYPE html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
</head>
|
|
||||||
<body style="margin:0;padding:0;background:#f5f5f5;font-family:system-ui,sans-serif">
|
|
||||||
<div style="max-width:600px;margin:0 auto;padding:40px 20px">
|
|
||||||
<div style="background:#314732;padding:32px;border-radius:8px 8px 0 0;text-align:center">
|
|
||||||
<h1 style="margin:0;color:#ffffff;font-size:22px;font-weight:600">Nouvelle demande de soumission</h1>
|
|
||||||
<p style="margin:8px 0 0;color:rgba(255,255,255,0.7);font-size:14px">Cachet Peintres Décorateurs</p>
|
|
||||||
</div>
|
|
||||||
<div style="background:#ffffff;border-radius:0 0 8px 8px;overflow:hidden">
|
|
||||||
<div style="padding:24px;background:#f0f4ef;border-bottom:2px solid #d6e2d4">
|
|
||||||
<p style="margin:0;font-size:15px;color:#314732;font-weight:600">
|
|
||||||
Vous avez reçu une nouvelle demande de soumission de la part de <strong>${data.nom}</strong>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<table style="width:100%;border-collapse:collapse">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td style="padding:8px 12px;color:#666;font-size:14px;border-bottom:1px solid #eee;width:40%">Nom</td>
|
|
||||||
<td style="padding:8px 12px;font-size:14px;border-bottom:1px solid #eee;font-weight:600">${data.nom}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td style="padding:8px 12px;color:#666;font-size:14px;border-bottom:1px solid #eee">Courriel</td>
|
|
||||||
<td style="padding:8px 12px;font-size:14px;border-bottom:1px solid #eee"><a href="mailto:${data.courriel}" style="color:#314732;font-weight:600">${data.courriel}</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td style="padding:8px 12px;color:#666;font-size:14px;border-bottom:1px solid #eee">Téléphone</td>
|
|
||||||
<td style="padding:8px 12px;font-size:14px;border-bottom:1px solid #eee"><a href="tel:${data.telephone.replace(/\D/g, '')}" style="color:#314732;font-weight:600">${data.telephone}</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td style="padding:8px 12px;color:#666;font-size:14px;border-bottom:1px solid #eee">Type de service</td>
|
|
||||||
<td style="padding:8px 12px;font-size:14px;border-bottom:1px solid #eee">${serviceLabel}</td>
|
|
||||||
</tr>
|
|
||||||
${adresseLine}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div style="padding:20px">
|
|
||||||
<p style="margin:0 0 8px;font-size:13px;font-weight:700;color:#666;text-transform:uppercase;letter-spacing:0.05em">Message</p>
|
|
||||||
<div style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;padding:16px;font-size:14px;color:#374151;line-height:1.6;white-space:pre-wrap">${data.message}</div>
|
|
||||||
</div>
|
|
||||||
<div style="padding:16px 20px 24px">
|
|
||||||
<a href="mailto:${data.courriel}?subject=Re: Soumission — Cachet Peintres Décorateurs" style="display:inline-block;padding:10px 24px;background:#314732;color:#fff;text-decoration:none;border-radius:4px;font-size:14px;font-weight:700">Répondre à ${data.nom}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p style="margin:24px 0 0;text-align:center;font-size:11px;color:#999">
|
|
||||||
Cachet Peintres Décorateurs · 9500-5609 Québec Inc · RBQ 5839 8736 01
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateBody(body: unknown): body is SoumissionBody {
|
|
||||||
if (typeof body !== 'object' || body === null) return false;
|
|
||||||
const b = body as Record<string, unknown>;
|
|
||||||
return (
|
|
||||||
typeof b.nom === 'string' && b.nom.trim().length > 0 &&
|
|
||||||
typeof b.courriel === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(b.courriel) &&
|
|
||||||
typeof b.telephone === 'string' && b.telephone.trim().length > 0 &&
|
|
||||||
typeof b.type_service === 'string' && b.type_service.length > 0 &&
|
|
||||||
typeof b.message === 'string' && b.message.trim().length > 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const onRequestPost: PagesFunction<Env> = async (context) => {
|
|
||||||
const corsHeaders = {
|
|
||||||
'Access-Control-Allow-Origin': 'https://cachetdeco.com',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Rate limiting via CF headers (basic check)
|
|
||||||
const ip = context.request.headers.get('CF-Connecting-IP') ?? 'unknown';
|
|
||||||
|
|
||||||
let body: unknown;
|
|
||||||
try {
|
|
||||||
body = await context.request.json();
|
|
||||||
} catch {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ success: false, error: 'Corps de requête invalide.' }),
|
|
||||||
{ status: 400, headers: corsHeaders }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateBody(body)) {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ success: false, error: 'Données manquantes ou invalides.' }),
|
|
||||||
{ status: 422, headers: corsHeaders }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiKey = context.env.RESEND_API_KEY;
|
|
||||||
const contactEmail = context.env.CONTACT_EMAIL;
|
|
||||||
|
|
||||||
if (!apiKey || !contactEmail) {
|
|
||||||
console.error('Missing env vars: RESEND_API_KEY or CONTACT_EMAIL');
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ success: false, error: 'Configuration serveur manquante.' }),
|
|
||||||
{ status: 500, headers: corsHeaders }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const emailPayload = {
|
|
||||||
from: 'Cachet Peintres Décorateurs <soumission@cachetdeco.com>',
|
|
||||||
to: [contactEmail],
|
|
||||||
reply_to: body.courriel,
|
|
||||||
subject: `Soumission — ${serviceLabels[body.type_service] ?? body.type_service} — ${body.nom}`,
|
|
||||||
html: buildEmailHtml(body),
|
|
||||||
};
|
|
||||||
|
|
||||||
const resendRes = await fetch('https://api.resend.com/emails', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${apiKey}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(emailPayload),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!resendRes.ok) {
|
|
||||||
const resendError = await resendRes.text().catch(() => 'Unknown error');
|
|
||||||
console.error('Resend error:', resendError, 'IP:', ip);
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ success: false, error: 'Erreur lors de l\'envoi du courriel.' }),
|
|
||||||
{ status: 502, headers: corsHeaders }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ success: true }),
|
|
||||||
{ status: 200, headers: corsHeaders }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle preflight OPTIONS
|
|
||||||
export const onRequestOptions: PagesFunction = async () => {
|
|
||||||
return new Response(null, {
|
|
||||||
status: 204,
|
|
||||||
headers: {
|
|
||||||
'Access-Control-Allow-Origin': 'https://cachetdeco.com',
|
|
||||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
||||||
'Access-Control-Allow-Headers': 'Content-Type',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
2328
package-lock.json
generated
11
package.json
@@ -9,28 +9,33 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/react": "^5.0.0",
|
"@astrojs/cloudflare": "^13.1.7",
|
||||||
"@astrojs/sitemap": "^3.2.1",
|
"@astrojs/react": "^5.0.2",
|
||||||
|
"@astrojs/sitemap": "^3.7.2",
|
||||||
"@fontsource-variable/geist": "^5.2.8",
|
"@fontsource-variable/geist": "^5.2.8",
|
||||||
"@headlessui/react": "^2.2.9",
|
"@headlessui/react": "^2.2.9",
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"astro": "^5.4.0",
|
"astro": "^6.1.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"embla-carousel-autoplay": "^8.6.0",
|
"embla-carousel-autoplay": "^8.6.0",
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^0.577.0",
|
||||||
|
"motion": "^12.38.0",
|
||||||
"radix-ui": "^1.4.3",
|
"radix-ui": "^1.4.3",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
|
"react-hook-form": "^7.72.0",
|
||||||
|
"react-social-media-embed": "^2.5.18",
|
||||||
"shadcn": "^4.0.5",
|
"shadcn": "^4.0.5",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.1",
|
||||||
"tw-animate-css": "^1.4.0"
|
"tw-animate-css": "^1.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@cloudflare/workers-types": "^4.20260403.1",
|
||||||
"@types/react": "^19.2.14"
|
"@types/react": "^19.2.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
backend:
|
backend:
|
||||||
name: github
|
name: github
|
||||||
repo: mbcube/cachetdeco # REPLACE with your GitHub org/repo
|
repo: mbcube/cachetdeco
|
||||||
branch: main
|
branch: cloudflare
|
||||||
# For Cloudflare Pages (no Netlify Identity), use PKCE flow:
|
base_url: https://cachetdeco.br-mouad.workers.dev
|
||||||
auth_type: pkce
|
auth_endpoint: /api/auth
|
||||||
app_id: YOUR_GITHUB_OAUTH_APP_CLIENT_ID # REPLACE with your GitHub OAuth App client ID
|
|
||||||
|
|
||||||
locale: fr
|
locale: fr
|
||||||
media_folder: "public/images"
|
media_folder: "public/images"
|
||||||
@@ -78,7 +77,7 @@ collections:
|
|||||||
i18n: true
|
i18n: true
|
||||||
fields:
|
fields:
|
||||||
- { name: "services", label: "Page Services", widget: "string" }
|
- { name: "services", label: "Page Services", widget: "string" }
|
||||||
- { name: "soumission", label: "Page Soumission", widget: "string" }
|
- { name: "submission", label: "Page Soumission", widget: "string" }
|
||||||
|
|
||||||
- name: "hero"
|
- name: "hero"
|
||||||
label: "Hero (accueil)"
|
label: "Hero (accueil)"
|
||||||
@@ -172,10 +171,10 @@ collections:
|
|||||||
- { name: "button", label: "Bouton", widget: "string" }
|
- { name: "button", label: "Bouton", widget: "string" }
|
||||||
- { name: "breadcrumbHome", label: "Fil d'Ariane — Accueil", widget: "string", i18n: true }
|
- { name: "breadcrumbHome", label: "Fil d'Ariane — Accueil", widget: "string", i18n: true }
|
||||||
|
|
||||||
- name: "soumission"
|
- name: "submission"
|
||||||
label: "Page Soumission & formulaire"
|
label: "Page Soumission & formulaire"
|
||||||
label_singular: "Soumission"
|
label_singular: "Soumission"
|
||||||
folder: "src/content/soumission"
|
folder: "src/content/submission"
|
||||||
format: json
|
format: json
|
||||||
create: false
|
create: false
|
||||||
delete: false
|
delete: false
|
||||||
@@ -191,29 +190,33 @@ collections:
|
|||||||
i18n: true
|
i18n: true
|
||||||
fields:
|
fields:
|
||||||
- { name: "ariaLabel", label: "Aria-label formulaire", widget: "string" }
|
- { name: "ariaLabel", label: "Aria-label formulaire", widget: "string" }
|
||||||
- { name: "nom", label: "Libellé nom", widget: "string" }
|
- { name: "fullNameLabel", label: "Libellé nom complet", widget: "string" }
|
||||||
- { name: "nomPlaceholder", label: "Placeholder nom", widget: "string" }
|
- { name: "fullNamePlaceholder", label: "Placeholder nom", widget: "string" }
|
||||||
- { name: "courriel", label: "Libellé courriel", widget: "string" }
|
- { name: "emailLabel", label: "Libellé courriel", widget: "string" }
|
||||||
- { name: "courrielPlaceholder", label: "Placeholder courriel", widget: "string" }
|
- { name: "emailPlaceholder", label: "Placeholder courriel", widget: "string" }
|
||||||
- { name: "telephone", label: "Libellé téléphone", widget: "string" }
|
- { name: "phoneLabel", label: "Libellé téléphone", widget: "string" }
|
||||||
- { name: "telephonePlaceholder", label: "Placeholder téléphone", widget: "string" }
|
- { name: "phonePlaceholder", label: "Placeholder téléphone", widget: "string" }
|
||||||
- { name: "adresse", label: "Libellé adresse travaux", widget: "string" }
|
- { name: "workAddressLabel", label: "Libellé adresse travaux", widget: "string" }
|
||||||
- { name: "adressePlaceholder", label: "Placeholder adresse", widget: "string" }
|
- { name: "workAddressPlaceholder", label: "Placeholder adresse", widget: "string" }
|
||||||
- { name: "typeService", label: "Libellé type de service", widget: "string" }
|
- { name: "serviceTypeLabel", label: "Libellé type de service", widget: "string" }
|
||||||
- { name: "typeServicePlaceholder", label: "Placeholder sélection", widget: "string" }
|
- { name: "serviceTypePlaceholder", label: "Placeholder sélection", widget: "string" }
|
||||||
- name: "typeServiceOptions"
|
- name: "serviceTypeOptions"
|
||||||
label: "Options type de service"
|
label: "Options type de service"
|
||||||
widget: "object"
|
widget: "object"
|
||||||
fields:
|
fields:
|
||||||
- { name: "interieur", label: "Intérieur", widget: "string" }
|
- { name: "interior", label: "Intérieur", widget: "string" }
|
||||||
- { name: "exterieur", label: "Extérieur", widget: "string" }
|
- { name: "exterior", label: "Extérieur", widget: "string" }
|
||||||
- { name: "commercial", label: "Commercial", widget: "string" }
|
- { name: "commercial", label: "Commercial", widget: "string" }
|
||||||
- { name: "decoration", label: "Décoration", widget: "string" }
|
- { name: "decoration", label: "Décoration", widget: "string" }
|
||||||
- { name: "autre", label: "Autre", widget: "string" }
|
- { name: "other", label: "Autre", widget: "string" }
|
||||||
- { name: "message", label: "Libellé message", widget: "string" }
|
- { name: "messageLabel", label: "Libellé message", widget: "string" }
|
||||||
- { name: "messagePlaceholder", label: "Placeholder message", widget: "text" }
|
- { name: "messagePlaceholder", label: "Placeholder message", widget: "text" }
|
||||||
- { name: "envoyer", label: "Bouton envoyer", widget: "string" }
|
- { name: "attachmentsLabel", label: "Libellé pièces jointes", widget: "string" }
|
||||||
- { name: "sending", label: "État envoi", widget: "string" }
|
- { name: "attachmentsHint", label: "Aide zone dépôt", widget: "string" }
|
||||||
|
- { name: "attachmentsAccepted", label: "Formats acceptés", widget: "string" }
|
||||||
|
- { name: "attachmentsRemove", label: "Retirer fichier (aria)", widget: "string" }
|
||||||
|
- { name: "submitLabel", label: "Bouton envoyer", widget: "string" }
|
||||||
|
- { name: "submittingLabel", label: "État envoi", widget: "string" }
|
||||||
- { name: "successTitle", label: "Succès — titre", widget: "string" }
|
- { name: "successTitle", label: "Succès — titre", widget: "string" }
|
||||||
- { name: "successMessage", label: "Succès — message", widget: "text" }
|
- { name: "successMessage", label: "Succès — message", widget: "text" }
|
||||||
- { name: "errorTitle", label: "Erreur — titre", widget: "string" }
|
- { name: "errorTitle", label: "Erreur — titre", widget: "string" }
|
||||||
@@ -222,11 +225,13 @@ collections:
|
|||||||
label: "Messages validation"
|
label: "Messages validation"
|
||||||
widget: "object"
|
widget: "object"
|
||||||
fields:
|
fields:
|
||||||
- { name: "nom", label: "Nom", widget: "string" }
|
- { name: "fullName", label: "Nom", widget: "string" }
|
||||||
- { name: "courriel", label: "Courriel", widget: "string" }
|
- { name: "email", label: "Courriel", widget: "string" }
|
||||||
- { name: "telephone", label: "Téléphone", widget: "string" }
|
- { name: "phone", label: "Téléphone", widget: "string" }
|
||||||
- { name: "typeService", label: "Type service", widget: "string" }
|
- { name: "serviceType", label: "Type service", widget: "string" }
|
||||||
- { name: "message", label: "Message", widget: "string" }
|
- { name: "message", label: "Message", widget: "string" }
|
||||||
|
- { name: "attachmentsType", label: "Type fichier", widget: "string" }
|
||||||
|
- { name: "attachmentsSize", label: "Taille fichiers", widget: "string" }
|
||||||
- name: "sidebar"
|
- name: "sidebar"
|
||||||
label: "Barre latérale"
|
label: "Barre latérale"
|
||||||
widget: "object"
|
widget: "object"
|
||||||
@@ -258,6 +263,7 @@ collections:
|
|||||||
- { name: "navTitle", label: "Titre colonne navigation", widget: "string", i18n: true }
|
- { name: "navTitle", label: "Titre colonne navigation", widget: "string", i18n: true }
|
||||||
- { name: "servicesTitle", label: "Titre colonne services", widget: "string", i18n: true }
|
- { name: "servicesTitle", label: "Titre colonne services", widget: "string", i18n: true }
|
||||||
- { name: "contactTitle", label: "Titre colonne contact", widget: "string", i18n: true }
|
- { name: "contactTitle", label: "Titre colonne contact", widget: "string", i18n: true }
|
||||||
|
- { name: "serviceAreasTitle", label: "Titre colonne zones de service", widget: "string", i18n: true }
|
||||||
- { name: "legal", label: "Mention légale (utiliser {year})", widget: "string", i18n: true }
|
- { name: "legal", label: "Mention légale (utiliser {year})", widget: "string", i18n: true }
|
||||||
- { name: "rbq", label: "Ligne RBQ", widget: "string", i18n: true }
|
- { name: "rbq", label: "Ligne RBQ", widget: "string", i18n: true }
|
||||||
- { name: "neq", label: "Ligne NEQ", widget: "string", i18n: true }
|
- { name: "neq", label: "Ligne NEQ", widget: "string", i18n: true }
|
||||||
@@ -269,6 +275,11 @@ collections:
|
|||||||
fields:
|
fields:
|
||||||
- { name: "label", label: "Libellé", widget: "string" }
|
- { name: "label", label: "Libellé", widget: "string" }
|
||||||
- { name: "href", label: "URL", widget: "string" }
|
- { name: "href", label: "URL", widget: "string" }
|
||||||
|
- name: "serviceAreas"
|
||||||
|
label: "Zones de service"
|
||||||
|
widget: "list"
|
||||||
|
i18n: true
|
||||||
|
field: { name: "item", label: "Ville / Secteur", widget: "string" }
|
||||||
|
|
||||||
- name: "not_found"
|
- name: "not_found"
|
||||||
label: "Page 404"
|
label: "Page 404"
|
||||||
@@ -316,6 +327,26 @@ collections:
|
|||||||
- { name: "title", label: "Titre", widget: "string" }
|
- { name: "title", label: "Titre", widget: "string" }
|
||||||
- { name: "description", label: "Description", widget: "text" }
|
- { name: "description", label: "Description", widget: "text" }
|
||||||
|
|
||||||
|
- name: "testimonials"
|
||||||
|
label: "Témoignages"
|
||||||
|
label_singular: "Témoignage"
|
||||||
|
folder: "src/content/testimonials"
|
||||||
|
format: json
|
||||||
|
create: false
|
||||||
|
delete: false
|
||||||
|
i18n: true
|
||||||
|
slug: "{{slug}}"
|
||||||
|
fields:
|
||||||
|
- { name: "sectionLabel", label: "Libellé de section", widget: "string", i18n: true }
|
||||||
|
- { name: "title", label: "Titre de section", widget: "string", i18n: true }
|
||||||
|
- name: "items"
|
||||||
|
label: "Témoignages"
|
||||||
|
widget: "list"
|
||||||
|
i18n: true
|
||||||
|
fields:
|
||||||
|
- { name: "key", label: "Identifiant unique", widget: "string" }
|
||||||
|
- { name: "src", label: "URL du post Facebook (embed)", widget: "string" }
|
||||||
|
|
||||||
- name: "settings"
|
- name: "settings"
|
||||||
label: "Paramètres du site"
|
label: "Paramètres du site"
|
||||||
files:
|
files:
|
||||||
@@ -329,10 +360,15 @@ collections:
|
|||||||
- { name: "taglineHero", label: "Slogan hero", widget: "string" }
|
- { name: "taglineHero", label: "Slogan hero", widget: "string" }
|
||||||
- { name: "phone", label: "Téléphone", widget: "string" }
|
- { name: "phone", label: "Téléphone", widget: "string" }
|
||||||
- { name: "email", label: "Courriel", widget: "string" }
|
- { name: "email", label: "Courriel", widget: "string" }
|
||||||
|
- { name: "emailFromContact", label: "Expéditeur courriel (formulaire contact, ex. Nom <adresse@domaine>)", widget: "string" }
|
||||||
|
- { name: "emailToContact", label: "Destinataire courriel (formulaire contact)", widget: "string" }
|
||||||
|
- { name: "fromAddressSubmission", label: "Expéditeur courriel (formulaire soumission, ex. Nom <adresse@domaine>)", widget: "string" }
|
||||||
|
- { name: "toAddressSubmission", label: "Destinataire courriel (formulaire soumission)", widget: "string" }
|
||||||
- { name: "address", label: "Adresse", widget: "string" }
|
- { name: "address", label: "Adresse", widget: "string" }
|
||||||
- { name: "serviceArea", label: "Zone de service", widget: "string" }
|
- { name: "serviceArea", label: "Zone de service", widget: "string" }
|
||||||
- { name: "facebook", label: "Lien Facebook", widget: "string" }
|
- { name: "facebook", label: "Lien Facebook", widget: "string" }
|
||||||
- { name: "instagram", label: "Lien Instagram", widget: "string", required: false }
|
- { name: "instagram", label: "Lien Instagram", widget: "string", required: false }
|
||||||
|
- { name: "linkedin", label: "Lien LinkedIn (vide = masqué)", widget: "string", required: false }
|
||||||
- { name: "rbq", label: "Numéro RBQ", widget: "string" }
|
- { name: "rbq", label: "Numéro RBQ", widget: "string" }
|
||||||
- { name: "neq", label: "Numéro NEQ (Registraire)", widget: "string" }
|
- { name: "neq", label: "Numéro NEQ (Registraire)", widget: "string" }
|
||||||
|
|
||||||
|
|||||||
7
public/images/cachetvignette3.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated by Pixelmator Pro 3.6.18 -->
|
||||||
|
<svg width="600" height="700" viewBox="0 0 600 700" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path id="Path" fill="#74826e" stroke="none" d="M 212.013763 570.637939 L 209.280426 570.637939 C 201.072083 569.871216 194.230408 562.108765 194.230408 552.042114 L 194.230408 46.725403 C 194.230408 36.654541 201.759583 28.125427 210.647095 28.125427 L 212.705414 28.125427 L 300.24292 28.125427 C 432.24707 28.125427 539.630432 150.57959 539.630432 300.154602 C 539.630432 449.746246 432.24707 570.637939 300.24292 570.637939 Z"/>
|
||||||
|
<path id="path1" fill="#2e4030" stroke="none" d="M 299.743774 671.875 C 167.756256 671.875 60.368744 550.958374 60.368744 401.383331 C 60.368744 251.804138 167.756256 129.345825 299.743774 129.345825 L 387.289612 129.345825 L 389.34375 129.345825 C 398.231262 129.345825 405.760437 137.87085 405.760437 147.94165 L 405.760437 653.262512 C 405.760437 663.337524 398.922913 671.087524 390.7146 671.875 L 387.981262 671.875 Z"/>
|
||||||
|
<path id="path2" fill="none" stroke="#74826e" stroke-width="12.5" d="M 212.013763 570.637939 L 209.280426 570.637939 C 201.072083 569.871216 194.230408 562.108765 194.230408 552.042114 L 194.230408 46.729614 C 194.230408 36.654541 201.759583 28.129578 210.647095 28.129578 L 212.705414 28.129578 L 300.24707 28.129578 C 432.24707 28.129578 539.630432 150.57959 539.630432 300.154602 C 539.630432 449.746246 432.24707 570.637939 300.24707 570.637939 L 212.013763 570.637939 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 525 KiB After Width: | Height: | Size: 525 KiB |
|
Before Width: | Height: | Size: 287 KiB After Width: | Height: | Size: 287 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
BIN
public/images/logo-full.webp
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
7
public/images/logo-text.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated by Pixelmator Pro 3.6.18 -->
|
||||||
|
<svg width="1390" height="500" viewBox="0 0 1390 500" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<text id="CACHET" xml:space="preserve" x="3" y="312" font-family="Cocogoose Classic Trial" font-size="350" font-weight="600" fill="#2d4030">CACHET</text>
|
||||||
|
<path id="Rectangle" fill="#b0b0ac" fill-rule="evenodd" stroke="none" d="M 228 360 L 1161 360 L 1161 334 L 228 334 Z"/>
|
||||||
|
<text id="PEINTRES-DECORATEURS" xml:space="preserve" x="228" y="435" font-family="Geoform" font-size="72" font-weight="700" fill="#3d3e3b">PEINTRES DECORATEURS</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 633 B |
BIN
public/images/logo-text.webp
Normal file
|
After Width: | Height: | Size: 219 KiB |
7
public/images/logo-vignette.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated by Pixelmator Pro 3.6.18 -->
|
||||||
|
<svg width="600" height="700" viewBox="0 0 600 700" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path id="Path" fill="#74826e" stroke="none" d="M 212.013763 570.637939 L 209.280426 570.637939 C 201.072083 569.871216 194.230408 562.108765 194.230408 552.042114 L 194.230408 46.725403 C 194.230408 36.654541 201.759583 28.125427 210.647095 28.125427 L 212.705414 28.125427 L 300.24292 28.125427 C 432.24707 28.125427 539.630432 150.57959 539.630432 300.154602 C 539.630432 449.746246 432.24707 570.637939 300.24292 570.637939 Z"/>
|
||||||
|
<path id="path1" fill="#2e4030" stroke="none" d="M 299.743774 671.875 C 167.756256 671.875 60.368744 550.958374 60.368744 401.383331 C 60.368744 251.804138 167.756256 129.345825 299.743774 129.345825 L 387.289612 129.345825 L 389.34375 129.345825 C 398.231262 129.345825 405.760437 137.87085 405.760437 147.94165 L 405.760437 653.262512 C 405.760437 663.337524 398.922913 671.087524 390.7146 671.875 L 387.981262 671.875 Z"/>
|
||||||
|
<path id="path2" fill="none" stroke="#74826e" stroke-width="12.5" d="M 212.013763 570.637939 L 209.280426 570.637939 C 201.072083 569.871216 194.230408 562.108765 194.230408 552.042114 L 194.230408 46.729614 C 194.230408 36.654541 201.759583 28.129578 210.647095 28.129578 L 212.705414 28.129578 L 300.24707 28.129578 C 432.24707 28.129578 539.630432 150.57959 539.630432 300.154602 C 539.630432 449.746246 432.24707 570.637939 300.24707 570.637939 L 212.013763 570.637939 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 223 KiB After Width: | Height: | Size: 223 KiB |
BIN
public/images/services-4.webp
Normal file
|
After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 320 KiB After Width: | Height: | Size: 320 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 459 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 339 KiB |
|
Before Width: | Height: | Size: 3.0 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 572 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1020 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 3.4 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 331 KiB |
|
Before Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 432 KiB |
|
Before Width: | Height: | Size: 427 KiB |
|
Before Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 269 KiB |
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 282 KiB |
|
Before Width: | Height: | Size: 410 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 521 KiB |
|
Before Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 226 KiB |