From 10e51b5da4793610b58f5956dd6bec02b9fb64d0 Mon Sep 17 00:00:00 2001 From: Lucien Cartier-Tilet Date: Wed, 19 Nov 2025 22:03:35 +0100 Subject: [PATCH] feat(pages): add contact page --- app/composables/useApi.test.ts | 12 +++- app/composables/useApi.ts | 2 + app/composables/useBackend.ts | 3 +- app/pages/contact.vue | 123 +++++++++++++++++++++++++++++++++ i18n/locales/en.json | 42 +++++++++++ i18n/locales/fr.json | 48 ++++++++++++- nuxt.config.ts | 7 +- 7 files changed, 230 insertions(+), 7 deletions(-) create mode 100644 app/pages/contact.vue diff --git a/app/composables/useApi.test.ts b/app/composables/useApi.test.ts index 6a91be3..d58a51c 100644 --- a/app/composables/useApi.test.ts +++ b/app/composables/useApi.test.ts @@ -14,7 +14,17 @@ vi.mock('#app', () => ({ })); // Mock $fetch globally -global.$fetch = vi.fn(); +declare global { + var $fetch: ReturnType; +} +// global.$fetch = vi.fn(); +vi.mock('#app', () => ({ + useRuntimeConfig: vi.fn(() => ({ + public: { + apiBase: 'http://localhost:3100/api' + } + })) +})) describe('useApi', () => { beforeEach(() => { diff --git a/app/composables/useApi.ts b/app/composables/useApi.ts index 66cc90f..09228c1 100644 --- a/app/composables/useApi.ts +++ b/app/composables/useApi.ts @@ -3,6 +3,8 @@ import type { ApiError } from '~/types/api/error'; import type { HttpMethod } from '~/types/http-method'; import { QueryResult } from '~/types/query-result'; +export type UseApiResponse = QueryResult; + export interface UseApi { get: (path: string, opts?: FetchOptions, immediate?: boolean) => UseApiResponse; del: (path: string, opts?: FetchOptions, immediate?: boolean) => UseApiResponse; diff --git a/app/composables/useBackend.ts b/app/composables/useBackend.ts index a8934ce..6f1dcab 100644 --- a/app/composables/useBackend.ts +++ b/app/composables/useBackend.ts @@ -1,12 +1,13 @@ import type { ContactRequest, ContactResponse } from '~/types/api/contact'; import type { MetaResponse } from '~/types/api/meta'; +import type { UseApiResponse } from './useApi'; export const useBackend = () => { const api = useApi(); const getMeta = (): UseApiResponse => api.get('/meta'); const postContact = (): UseApiResponse => - api.post('/contact', false); + api.post('/contact', undefined, true); return { getMeta, postContact }; }; diff --git a/app/pages/contact.vue b/app/pages/contact.vue new file mode 100644 index 0000000..211c5df --- /dev/null +++ b/app/pages/contact.vue @@ -0,0 +1,123 @@ + + + diff --git a/i18n/locales/en.json b/i18n/locales/en.json index b1e5b54..ae73e86 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -38,6 +38,28 @@ "name": "Languages & Worldbuilding" }, "contact": { + "form": { + "sendButton": "Send Message", + "validation": { + "shortName": "Must contain at least one character", + "longName": "Cannot exceed 100 characters", + "shortMessage": "Must contain at least 10 characters", + "longMessage": "Cannot exceed 5000 characters", + "invalidEmail": "Invalid email address format" + }, + "labels": { + "name": "Name", + "email": "Email Address", + "message": "Message", + "website": "Website" + }, + "placeholders": { + "name": "Alex Taylor", + "email": "alex.taylor[at]example.com", + "message": "Hello, ...", + "website": "https://example.com" + } + }, "name": "Contact" } }, @@ -51,5 +73,25 @@ "frontend": "Frontend Version", "backend": "Backend Version" } + }, + "backend": { + "failed": "Error", + "errors": { + "title": "There was an error", + "unknown": "The website encountered an unknown error. Please try again later." + }, + "contact": { + "success": "Email sent! We’ve also sent you a confirmation email!", + "honeypot": "Mmmmmh, I love me some honey from the honeypot!", + "errors": { + "internal": "The website encountered an internal error. Please try again later.", + "validation": { + "name": "Incorrect name format. Must contain from 1 to 50 characters.", + "email": "Incorrect email format.", + "message": "Incorrect message format. Must contain from 10 to 5000 characters.", + "other": "Malformed request." + } + } + } } } diff --git a/i18n/locales/fr.json b/i18n/locales/fr.json index 76c81cd..eb754b2 100644 --- a/i18n/locales/fr.json +++ b/i18n/locales/fr.json @@ -38,7 +38,29 @@ "name": "Langues et Univers Fictifs" }, "contact": { - "name": "Contact" + "name": "Contact", + "form": { + "sendButton": "Envoyer le message", + "validation": { + "shortName": "Longueur minimale du nom : 1 caractère", + "longName": "Longeur maximale du nom : 100 caractères", + "shortMessage": "Longueur minimale du message : 10 caractères", + "longMessage": "Longueur maximale du message : 5000 caractères", + "invalidEmail": "Format adresse courriel invalide" + }, + "labels": { + "name": "Nom", + "email": "Addresse courriel", + "message": "Message", + "website": "Site web" + }, + "placeholders": { + "name": "Dominique Dubois", + "email": "dominique.dubois[at]example.com", + "message": "Bonjour, ...", + "website": "https://example.com" + } + } } }, "footer": { @@ -48,8 +70,28 @@ "rust": "Backend fait avec Rust" }, "versions": { - "frontend": "Frontend Version", - "backend": "Backend Version" + "frontend": "Version frontend", + "backend": "Version backend" + } + }, + "backend": { + "failed": "Erreur", + "errors": { + "title": "Une erreur est survenue", + "unknown": "Une erreur inconnue est survenue. Veuillez réessayer plus tard." + }, + "contact": { + "success": "Message envoyé ! Un email de confirmation vous a été également envoyé.", + "honeypot": "Miam, du bon miel pour le robot !", + "errors": { + "internal": "Une erreur interne est survenue, veuillez réessayer plus tard.", + "validation": { + "name": "Format du nom incorrect. Doit faire de 1 à 50 caractères.", + "email": "Format de l’adresse courriel invalide.", + "message": "Format du message invalide. Doit faire entre 10 et 5000 caractères.", + "other": "Données de la requête malformées." + } + } } } } diff --git a/nuxt.config.ts b/nuxt.config.ts index 51c19ca..502595a 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -5,6 +5,10 @@ export default defineNuxtConfig({ enabled: true, vueDevTools: true, telemetry: false, + + timeline: { + enabled: true + } }, modules: [ @@ -66,7 +70,6 @@ export default defineNuxtConfig({ } }, turnstile: { - siteKey: '', // Overridden by NUXT_PUBLIC_TURNSTILE_SITE_KEY addValidateEndpoint: true }, runtimeConfig: { @@ -76,5 +79,5 @@ export default defineNuxtConfig({ public: { apiBase: process.env.NUXT_PUBLIC_API_BASE || 'http://localhost:3100/api', } - }, + } });