diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..98e530c --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +NEXT_PUBLIC_MANAGER_SERVICE_URL=https://api.myinfomate.be diff --git a/.gitignore b/.gitignore index 5ef6a52..c9f56c7 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +!.env.production # vercel .vercel diff --git a/src/app/[lang]/HomeClient.tsx b/src/app/[lang]/HomeClient.tsx index 2da646c..1bf81c8 100644 --- a/src/app/[lang]/HomeClient.tsx +++ b/src/app/[lang]/HomeClient.tsx @@ -479,9 +479,6 @@ export default function Home({ lang }: { lang: Language }) {
-
- {t.pricing.comingSoon} -

Essentiel

@@ -516,13 +513,14 @@ export default function Home({ lang }: { lang: Language }) { ))} - - {t.pricing.ctaStart} + + {t.pricing.ctaStartTrial}
-
+
+
{t.pricing.recommended}
@@ -571,6 +569,7 @@ export default function Home({ lang }: { lang: Language }) { {t.pricing.ctaStart}
+
@@ -611,7 +610,7 @@ export default function Home({ lang }: { lang: Language }) {
  • check_circle - {t.pricing.features.ai} — 2 000 {t.pricing.features.reqPerMonth} + {t.pricing.features.ai} — ≈ 2 000 {t.pricing.features.questionsPerMonth} {t.pricing.features.aiNeedMore} → Enterprise
  • diff --git a/src/app/[lang]/[segment]/SegmentPageClient.tsx b/src/app/[lang]/[segment]/SegmentPageClient.tsx index 34f6746..3cc6b0b 100644 --- a/src/app/[lang]/[segment]/SegmentPageClient.tsx +++ b/src/app/[lang]/[segment]/SegmentPageClient.tsx @@ -404,9 +404,6 @@ export default function SegmentPageClient({ data, lang }: { data: Segment; lang: {/* Essentiel */}
    -
    - {t.pricing.comingSoon} -

    Essentiel

    @@ -441,9 +438,9 @@ export default function SegmentPageClient({ data, lang }: { data: Segment; lang: ))} - + + {t.pricing.ctaStartTrial} +
    @@ -540,7 +537,7 @@ export default function SegmentPageClient({ data, lang }: { data: Segment; lang:
  • check_circle - {t.pricing.features.ai} — 2 000 {t.pricing.features.reqPerMonth} + {t.pricing.features.ai} — ≈ 2 000 {t.pricing.features.questionsPerMonth} {t.pricing.features.aiNeedMore} → Enterprise
  • diff --git a/src/app/[lang]/signup/SignupClient.tsx b/src/app/[lang]/signup/SignupClient.tsx new file mode 100644 index 0000000..83a1c6a --- /dev/null +++ b/src/app/[lang]/signup/SignupClient.tsx @@ -0,0 +1,463 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; +import translations, { Language } from '@/data/translations'; + +const API_BASE = process.env.NEXT_PUBLIC_MANAGER_SERVICE_URL ?? ''; + +const COUNTRIES = [ + { code: 'BE', label: { fr: 'Belgique', en: 'Belgium', nl: 'België', de: 'Belgien' } }, + { code: 'FR', label: { fr: 'France', en: 'France', nl: 'Frankrijk', de: 'Frankreich' } }, + { code: 'LU', label: { fr: 'Luxembourg', en: 'Luxembourg', nl: 'Luxemburg', de: 'Luxemburg' } }, + { code: 'NL', label: { fr: 'Pays-Bas', en: 'Netherlands', nl: 'Nederland', de: 'Niederlande' } }, + { code: 'DE', label: { fr: 'Allemagne', en: 'Germany', nl: 'Duitsland', de: 'Deutschland' } }, + { code: 'CH', label: { fr: 'Suisse (hors UE)', en: 'Switzerland (non-EU)', nl: 'Zwitserland (niet-EU)', de: 'Schweiz (nicht EU)' } }, +]; + +type SlugStatus = 'idle' | 'checking' | 'available' | 'taken' | 'short'; +type VatStatus = { kind: 'idle' | 'checking' | 'zero' | 'std' | 'err' | 'unavailable'; message: string }; +type FormState = 'idle' | 'loading' | 'success' | 'error'; + +function sanitizeSlug(value: string) { + return value + .toLowerCase() + .replace(/[^a-z0-9-]/g, '-') + .replace(/-+/g, '-') + .replace(/^-/, ''); +} + +export default function SignupClient({ lang }: { lang: Language }) { + const t = translations[lang].signup; + + const [orgName, setOrgName] = useState(''); + const [slug, setSlug] = useState(''); + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [address, setAddress] = useState(''); + const [zip, setZip] = useState(''); + const [city, setCity] = useState(''); + const [country, setCountry] = useState('BE'); + const [vat, setVat] = useState(''); + const [consent, setConsent] = useState(false); + const [showPassword, setShowPassword] = useState(false); + + const [slugStatus, setSlugStatus] = useState('idle'); + const [vatStatus, setVatStatus] = useState({ kind: 'idle', message: '' }); + const [formState, setFormState] = useState('idle'); + const [errorMessage, setErrorMessage] = useState(''); + const [result, setResult] = useState<{ webUrl: string; managerAppUrl: string; trialEndsAt: string } | null>(null); + + const slugTimer = useRef | null>(null); + const vatTimer = useRef | null>(null); + + useEffect(() => { + if (slugTimer.current) clearTimeout(slugTimer.current); + const clean = sanitizeSlug(slug); + if (!clean) { + setSlugStatus('idle'); + return; + } + if (clean.length < 3) { + setSlugStatus('short'); + return; + } + setSlugStatus('checking'); + slugTimer.current = setTimeout(async () => { + try { + const res = await fetch(`${API_BASE}/api/onboarding/check-slug/${clean}`); + const data = await res.json(); + setSlugStatus(data.available ? 'available' : 'taken'); + } catch { + setSlugStatus('idle'); + } + }, 600); + return () => { if (slugTimer.current) clearTimeout(slugTimer.current); }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [slug]); + + useEffect(() => { + if (vatTimer.current) clearTimeout(vatTimer.current); + + if (country === 'CH') { + setVatStatus({ kind: 'zero', message: t.vatZero }); + return; + } + if (!vat.trim()) { + setVatStatus({ kind: 'std', message: `${t.vatNoNumber} — ${t.vatNoNumberHint}` }); + return; + } + + setVatStatus({ kind: 'checking', message: t.vatChecking }); + vatTimer.current = setTimeout(async () => { + try { + const res = await fetch( + `${API_BASE}/api/onboarding/validate-vat?country=${encodeURIComponent(country)}&vatNumber=${encodeURIComponent(vat)}`, + { method: 'POST' } + ); + const data = await res.json(); + if (data.valid === null) { + setVatStatus({ kind: 'unavailable', message: t.vatUnavailable }); + } else if (data.valid === false) { + setVatStatus({ kind: 'err', message: t.vatInvalid }); + } else { + setVatStatus({ kind: data.vatRate === 0 ? 'zero' : 'std', message: data.vatRate === 0 ? t.vatZero : t.vatDomestic }); + } + } catch { + setVatStatus({ kind: 'unavailable', message: t.vatUnavailable }); + } + }, 500); + return () => { if (vatTimer.current) clearTimeout(vatTimer.current); }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [vat, country]); + + const trialEndDateLabel = () => { + const d = new Date(); + d.setDate(d.getDate() + 14); + return d.toLocaleDateString(lang === 'fr' ? 'fr-BE' : lang === 'nl' ? 'nl-BE' : lang === 'de' ? 'de-DE' : 'en-GB', { + day: 'numeric', month: 'long', + }); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const clean = sanitizeSlug(slug); + + if (!orgName.trim() || !firstName.trim() || !lastName.trim() || !email.trim() || password.length < 8 || !clean || !consent) { + return; + } + if (slugStatus === 'taken') return; + + setFormState('loading'); + try { + const res = await fetch(`${API_BASE}/api/onboarding/register`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + organizationName: orgName.trim(), + firstName: firstName.trim(), + lastName: lastName.trim(), + email: email.trim(), + password, + slug: clean, + billingAddress: [address, zip, city].filter(Boolean).join(', ') || null, + billingCountry: country, + vatNumber: vat.trim() || null, + }), + }); + + if (res.status === 409) { + setSlugStatus('taken'); + setFormState('idle'); + return; + } + if (!res.ok) throw new Error('registration failed'); + + const data = await res.json(); + setResult(data); + setFormState('success'); + } catch { + setErrorMessage(t.errorGeneric); + setFormState('error'); + } + }; + + if (formState === 'success' && result) { + return ( +
    +
    +
    + task_alt +
    +

    {t.successTitle}

    +

    {t.successDesc}

    +
    +
    +
    {t.recapUrlLabel}
    +
    {result.webUrl}
    +
    +
    +
    {t.recapTrialLabel}
    +
    {trialEndDateLabel()}
    +
    +
    +
    {t.recapEmailLabel}
    +
    {email}
    +
    +
    + + {t.goToApp} + +
    +
    + ); + } + + const missing = !orgName.trim() || !firstName.trim() || !lastName.trim() || !email.trim() || password.length < 8 || !sanitizeSlug(slug) || !consent; + + return ( +
    +
    + + {/* Brand panel — always dark, a deliberate brand block independent of page theme */} +
    +
    +
    + + + + + + MyInfoMate + + + + stars + {t.planBadge} + + +

    + {t.heroTitle} +

    + +
    + 39 € + {t.priceSuffix} +
    + + + check_circle + {t.trialBadge} + + +
      + {[t.feature1, t.feature2, t.feature3, t.feature4].map((f) => ( +
    • + + check + + {f} +
    • + ))} +
    + +
    +

    + info + {t.watermarkNote} +

    +
    + + shield + {t.trustHosting} + + + close + {t.trustCancel} + + + support_agent + {t.trustSupport} + +
    +
    +
    + + {/* Form panel — always light */} +
    +
    +

    {t.formTitle}

    +

    + {t.alreadyAccount}{' '} + {t.loginLink} +

    +
    + +
    +
    +
    + {t.sectionOrg} + +
    +
    + + setOrgName(e.target.value)} + placeholder={t.orgNamePlaceholder} + className="w-full px-5 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all" + /> +
    +
    + +
    + {t.slugPrefix} + setSlug(sanitizeSlug(e.target.value))} + placeholder={t.slugPlaceholder} + className="flex-1 min-w-0 px-1 py-3.5 bg-transparent outline-none font-bold text-slate-900" + /> + + {slugStatus === 'checking' && } + {slugStatus === 'available' && check_circle} + {(slugStatus === 'taken' || slugStatus === 'short') && cancel} + +
    +

    + {slugStatus === 'checking' && t.slugHintChecking} + {slugStatus === 'available' && `${t.slugHintAvailable} app.myinfomate.be/${sanitizeSlug(slug)}`} + {slugStatus === 'taken' && t.slugHintTaken} + {slugStatus === 'short' && t.slugHintTooShort} + {slugStatus === 'idle' && t.slugHintDefault} +

    +
    +
    + +
    +
    + {t.sectionContact} + +
    +
    +
    + + setFirstName(e.target.value)} placeholder={t.firstNamePlaceholder} + className="w-full px-5 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all" /> +
    +
    + + setLastName(e.target.value)} placeholder={t.lastNamePlaceholder} + className="w-full px-5 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all" /> +
    +
    +
    + + setEmail(e.target.value)} placeholder={t.emailPlaceholder} + className="w-full px-5 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all" /> +
    +
    + +
    + setPassword(e.target.value)} + placeholder={t.passwordPlaceholder} + className="w-full px-5 py-3.5 pr-12 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all" + /> + +
    +
    +
    + +
    +
    + {t.sectionBilling} + +
    +
    + + setAddress(e.target.value)} placeholder={t.addressPlaceholder} + className="w-full px-5 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all" /> +
    +
    +
    + + setZip(e.target.value)} placeholder={t.zipPlaceholder} + className="w-full px-5 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all" /> +
    +
    + + setCity(e.target.value)} placeholder={t.cityPlaceholder} + className="w-full px-5 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all" /> +
    +
    +
    +
    + + +
    +
    + + setVat(e.target.value)} placeholder={t.vatPlaceholder} + className="w-full px-5 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all" /> +
    +
    + {vatStatus.kind !== 'idle' && ( +
    + {vatStatus.kind === 'checking' + ? + : + {vatStatus.kind === 'zero' ? 'check_circle' : vatStatus.kind === 'err' ? 'error' : vatStatus.kind === 'unavailable' ? 'warning' : 'info'} + } + {vatStatus.message} +
    + )} +
    + + + + {formState === 'error' && ( +
    + {errorMessage} +
    + )} + + {slugStatus === 'taken' && ( +
    + {t.errorSlugTaken} +
    + )} + + + +

    + lock + {t.noCardNote} +

    +
    +
    +
    +
    + ); +} diff --git a/src/app/[lang]/signup/page.tsx b/src/app/[lang]/signup/page.tsx new file mode 100644 index 0000000..7209bf4 --- /dev/null +++ b/src/app/[lang]/signup/page.tsx @@ -0,0 +1,54 @@ +import type { Metadata } from 'next'; +import { notFound } from 'next/navigation'; +import { LOCALES, LOCALE_HTML_LANG, DEFAULT_LOCALE, isLocale, type Locale } from '@/i18n'; +import SignupClient from './SignupClient'; + +const META_BY_LOCALE: Record = { + fr: { + title: 'Créer mon compte — Plan Essentiel | MyInfoMate', + description: "Démarrez votre essai gratuit de 14 jours sur MyInfoMate, sans carte bancaire. Votre app visiteur web en quelques minutes.", + }, + en: { + title: 'Create my account — Essentiel plan | MyInfoMate', + description: 'Start your 14-day free trial on MyInfoMate, no card required. Your web visitor app in minutes.', + }, + nl: { + title: 'Mijn account aanmaken — Plan Essentieel | MyInfoMate', + description: 'Start uw gratis proefperiode van 14 dagen op MyInfoMate, zonder kredietkaart. Uw webbezoekersapp in enkele minuten.', + }, + de: { + title: 'Konto erstellen — Essentiel-Plan | MyInfoMate', + description: 'Starten Sie Ihre 14-tägige kostenlose Testphase bei MyInfoMate, ohne Kreditkarte. Ihre Web-Besucher-App in wenigen Minuten.', + }, +}; + +export function generateStaticParams() { + return LOCALES.map((lang) => ({ lang })); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ lang: string }>; +}): Promise { + const { lang } = await params; + if (!isLocale(lang)) return {}; + const m = META_BY_LOCALE[lang]; + const languages = Object.fromEntries(LOCALES.map((l) => [LOCALE_HTML_LANG[l], `/${l}/signup`])); + return { + title: m.title, + description: m.description, + robots: { index: false, follow: true }, + alternates: { + canonical: `/${lang}/signup`, + languages: { ...languages, 'x-default': `/${DEFAULT_LOCALE}/signup` }, + }, + }; +} + +export default async function SignupPage({ params }: { params: Promise<{ lang: string }> }) { + const { lang } = await params; + if (!isLocale(lang)) notFound(); + + return ; +} diff --git a/src/app/globals.css b/src/app/globals.css index cbd2795..330b9ee 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -69,6 +69,26 @@ animation: float 6s ease-in-out infinite; } +@property --border-angle { + syntax: ''; + inherits: false; + initial-value: 0deg; +} + +@keyframes spin-border { + to { --border-angle: 360deg; } +} + +@utility animated-card-border { + background: conic-gradient( + from var(--border-angle), + transparent 20%, + #0df2df 50%, + transparent 80% + ); + animation: spin-border 4s linear infinite; +} + @utility scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; diff --git a/src/data/translations.ts b/src/data/translations.ts index 3bac405..9393c65 100644 --- a/src/data/translations.ts +++ b/src/data/translations.ts @@ -194,6 +194,77 @@ const translations = { messagePlaceholder: 'Parlez-nous de votre projet...', submitButton: 'Envoyer ma demande', }, + signup: { + planBadge: 'Plan Essentiel', + heroTitle: 'Votre lieu culturel, en ligne en quelques minutes.', + priceSuffix: '/ mois HTVA', + trialBadge: "14 jours d'essai gratuit — sans carte bancaire", + feature1: 'App visiteur web, ouverte par QR code — aucune installation', + feature2: 'Contenus, agenda, plans et sections illimités', + feature3: 'Multilingue — vos visiteurs choisissent leur langue', + feature4: 'Vos couleurs et votre logo, votre adresse dédiée', + watermarkNote: "Pendant l'essai, vos pages visiteur affichent un léger filigrane « Aperçu ». Il disparaît dès l'activation de votre abonnement.", + trustHosting: 'Hébergé en Europe', + trustCancel: 'Résiliable à tout moment', + trustSupport: 'Support en français', + formTitle: 'Créer mon compte', + alreadyAccount: 'Vous avez déjà un espace ?', + loginLink: 'Se connecter', + sectionOrg: 'Votre organisation', + orgNameLabel: "Nom de l'organisation", + orgNamePlaceholder: 'Musée des Beaux-Arts', + slugLabel: 'Votre adresse dédiée', + slugPrefix: 'app.myinfomate.be/', + slugPlaceholder: 'votre-musee', + slugHintDefault: "Lettres minuscules, chiffres et tirets — c'est l'URL que vos visiteurs scanneront.", + slugHintChecking: 'Vérification de la disponibilité…', + slugHintTaken: 'Cette adresse est déjà utilisée — essayez un autre nom.', + slugHintAvailable: 'Disponible !', + slugHintTooShort: 'Au moins 3 caractères.', + sectionContact: 'Vos coordonnées', + firstNameLabel: 'Prénom', + firstNamePlaceholder: 'Camille', + lastNameLabel: 'Nom', + lastNamePlaceholder: 'Durand', + emailLabel: 'E-mail professionnel', + emailPlaceholder: 'camille@votre-musee.be', + passwordLabel: 'Mot de passe', + passwordPlaceholder: 'Au moins 8 caractères', + sectionBilling: 'Facturation', + addressLabel: 'Adresse', + addressPlaceholder: 'Rue de la Culture 12', + zipLabel: 'Code postal', + zipPlaceholder: '1000', + cityLabel: 'Ville', + cityPlaceholder: 'Bruxelles', + countryLabel: 'Pays', + vatLabel: 'N° de TVA', + vatOptional: 'optionnel', + vatPlaceholder: 'BE 0123.456.789', + vatChecking: 'Vérification du numéro de TVA…', + vatUnavailable: 'Service de validation TVA indisponible — TVA belge appliquée, à revalider ultérieurement.', + vatNoNumber: 'TVA belge 21% appliquée', + vatNoNumberHint: "Ajoutez un n° de TVA valide pour l'exonération intracommunautaire.", + vatDomestic: 'TVA belge 21% appliquée', + vatZero: 'TVA intracommunautaire — 0%', + vatInvalid: 'Numéro de TVA invalide', + consentPrefix: "J'accepte les", + consentTerms: 'conditions générales', + consentAnd: 'et la', + consentPrivacy: 'politique de confidentialité', + consentSuffix: 'de MyInfoMate.', + submitCta: 'Démarrer mon essai gratuit', + submitting: 'Création de votre espace…', + noCardNote: "Aucun prélèvement pendant l'essai. Nous ne demandons pas de carte.", + errorSlugTaken: 'Cette adresse est prise — choisissez-en une autre.', + errorGeneric: 'Une erreur est survenue, merci de réessayer.', + successTitle: 'Bienvenue à bord !', + successDesc: 'Votre espace est prêt. Un e-mail de confirmation vient de partir.', + recapUrlLabel: 'Votre adresse visiteur', + recapTrialLabel: 'Essai gratuit', + recapEmailLabel: 'E-mail de confirmation', + goToApp: 'Accéder à mon espace', + }, pricing: { sectionLabel: 'Tarifs', sectionTitle: 'Un plan pour chaque lieu', @@ -205,8 +276,8 @@ const translations = { startingFrom: 'À partir de', recommended: 'Recommandé', ctaStart: 'Demander une démo', + ctaStartTrial: 'Démarrer mon essai gratuit', ctaContact: 'Nous contacter', - comingSoon: 'Bientôt disponible', enterprisePrice: 'Sur devis', enterpriseDesc: 'Pour les organisations multi-sites ou aux besoins spécifiques (développements custom, accompagnement, SLA renforcé).', multiYearLabel: 'Budget pluriannuel ?', @@ -224,7 +295,7 @@ const translations = { pushNotif: 'Notifications push', stats: 'Stats de visite', ai: 'Assistant IA visiteur', - reqPerMonth: 'req/mois', + questionsPerMonth: 'questions de visiteurs / mois', statsBasic: '30 jours', statsAdvanced: 'Illimitées', webDisplayUrl: 'Affichage web sur URL dédiée', @@ -455,6 +526,77 @@ const translations = { messagePlaceholder: 'Tell us about your project...', submitButton: 'Send my request', }, + signup: { + planBadge: 'Essentiel plan', + heroTitle: 'Your cultural venue, online in minutes.', + priceSuffix: '/ month excl. VAT', + trialBadge: '14-day free trial — no card required', + feature1: 'Web visitor app, opened via QR code — no installation', + feature2: 'Unlimited content, agenda, maps and sections', + feature3: 'Multilingual — your visitors choose their language', + feature4: 'Your colors and logo, your dedicated address', + watermarkNote: 'During the trial, your visitor pages show a light "Preview" watermark. It disappears as soon as your subscription is activated.', + trustHosting: 'Hosted in Europe', + trustCancel: 'Cancel anytime', + trustSupport: 'Support in French', + formTitle: 'Create my account', + alreadyAccount: 'Already have a workspace?', + loginLink: 'Log in', + sectionOrg: 'Your organization', + orgNameLabel: 'Organization name', + orgNamePlaceholder: 'Museum of Fine Arts', + slugLabel: 'Your dedicated address', + slugPrefix: 'app.myinfomate.be/', + slugPlaceholder: 'your-museum', + slugHintDefault: "Lowercase letters, numbers and dashes — this is the URL your visitors will scan.", + slugHintChecking: 'Checking availability…', + slugHintTaken: 'This address is already taken — try another name.', + slugHintAvailable: 'Available!', + slugHintTooShort: 'At least 3 characters.', + sectionContact: 'Your contact details', + firstNameLabel: 'First name', + firstNamePlaceholder: 'Camille', + lastNameLabel: 'Last name', + lastNamePlaceholder: 'Durand', + emailLabel: 'Work email', + emailPlaceholder: 'camille@your-museum.com', + passwordLabel: 'Password', + passwordPlaceholder: 'At least 8 characters', + sectionBilling: 'Billing', + addressLabel: 'Address', + addressPlaceholder: '12 Culture Street', + zipLabel: 'Postal code', + zipPlaceholder: '1000', + cityLabel: 'City', + cityPlaceholder: 'Brussels', + countryLabel: 'Country', + vatLabel: 'VAT number', + vatOptional: 'optional', + vatPlaceholder: 'BE 0123.456.789', + vatChecking: 'Checking VAT number…', + vatUnavailable: 'VAT validation service unavailable — Belgian VAT applied, to be re-validated later.', + vatNoNumber: '21% Belgian VAT applied', + vatNoNumberHint: 'Add a valid VAT number for intra-community exemption.', + vatDomestic: '21% Belgian VAT applied', + vatZero: 'Intra-community VAT — 0%', + vatInvalid: 'Invalid VAT number', + consentPrefix: 'I accept the', + consentTerms: 'terms of service', + consentAnd: 'and the', + consentPrivacy: 'privacy policy', + consentSuffix: 'of MyInfoMate.', + submitCta: 'Start my free trial', + submitting: 'Creating your workspace…', + noCardNote: 'No charge during the trial. We never ask for a card.', + errorSlugTaken: 'This address is taken — choose another one.', + errorGeneric: 'Something went wrong, please try again.', + successTitle: 'Welcome aboard!', + successDesc: 'Your workspace is ready. A confirmation email has just been sent.', + recapUrlLabel: 'Your visitor address', + recapTrialLabel: 'Free trial', + recapEmailLabel: 'Confirmation email', + goToApp: 'Go to my workspace', + }, pricing: { sectionLabel: 'Pricing', sectionTitle: 'A plan for every venue', @@ -466,8 +608,8 @@ const translations = { startingFrom: 'Starting from', recommended: 'Recommended', ctaStart: 'Request a demo', + ctaStartTrial: 'Start my free trial', ctaContact: 'Contact us', - comingSoon: 'Coming soon', enterprisePrice: 'Custom quote', enterpriseDesc: 'For multi-site organizations or specific needs (custom development, onboarding, enhanced SLA).', multiYearLabel: 'Multi-year budget?', @@ -485,7 +627,7 @@ const translations = { pushNotif: 'Push notifications', stats: 'Visit statistics', ai: 'AI visitor assistant', - reqPerMonth: 'req/month', + questionsPerMonth: 'visitor questions / month', statsBasic: '30 days', statsAdvanced: 'Unlimited', webDisplayUrl: 'Web display on dedicated URL', @@ -716,6 +858,77 @@ const translations = { messagePlaceholder: 'Vertel ons over uw project...', submitButton: 'Mijn aanvraag verzenden', }, + signup: { + planBadge: 'Plan Essentieel', + heroTitle: 'Uw culturele locatie, online in enkele minuten.', + priceSuffix: '/ maand excl. BTW', + trialBadge: '14 dagen gratis proberen — geen kredietkaart nodig', + feature1: 'Web-bezoekersapp, geopend via QR-code — geen installatie', + feature2: 'Onbeperkte inhoud, agenda, plannen en secties', + feature3: 'Meertalig — uw bezoekers kiezen hun taal', + feature4: 'Uw kleuren en logo, uw eigen adres', + watermarkNote: 'Tijdens de proefperiode tonen uw bezoekerspagina\'s een licht "Preview"-watermerk. Dit verdwijnt zodra uw abonnement actief is.', + trustHosting: 'Gehost in Europa', + trustCancel: 'Op elk moment opzegbaar', + trustSupport: 'Ondersteuning in het Frans', + formTitle: 'Mijn account aanmaken', + alreadyAccount: 'Heeft u al een omgeving?', + loginLink: 'Inloggen', + sectionOrg: 'Uw organisatie', + orgNameLabel: 'Naam van de organisatie', + orgNamePlaceholder: 'Museum voor Schone Kunsten', + slugLabel: 'Uw eigen adres', + slugPrefix: 'app.myinfomate.be/', + slugPlaceholder: 'uw-museum', + slugHintDefault: 'Kleine letters, cijfers en streepjes — dit is de URL die uw bezoekers zullen scannen.', + slugHintChecking: 'Beschikbaarheid controleren…', + slugHintTaken: 'Dit adres is al in gebruik — probeer een andere naam.', + slugHintAvailable: 'Beschikbaar!', + slugHintTooShort: 'Minstens 3 tekens.', + sectionContact: 'Uw gegevens', + firstNameLabel: 'Voornaam', + firstNamePlaceholder: 'Camille', + lastNameLabel: 'Naam', + lastNamePlaceholder: 'Durand', + emailLabel: 'Professioneel e-mailadres', + emailPlaceholder: 'camille@uw-museum.be', + passwordLabel: 'Wachtwoord', + passwordPlaceholder: 'Minstens 8 tekens', + sectionBilling: 'Facturatie', + addressLabel: 'Adres', + addressPlaceholder: 'Cultuurstraat 12', + zipLabel: 'Postcode', + zipPlaceholder: '1000', + cityLabel: 'Stad', + cityPlaceholder: 'Brussel', + countryLabel: 'Land', + vatLabel: 'BTW-nummer', + vatOptional: 'optioneel', + vatPlaceholder: 'BE 0123.456.789', + vatChecking: 'BTW-nummer controleren…', + vatUnavailable: 'BTW-validatiedienst niet beschikbaar — Belgische BTW toegepast, later opnieuw te valideren.', + vatNoNumber: '21% Belgische BTW toegepast', + vatNoNumberHint: 'Voeg een geldig BTW-nummer toe voor intracommunautaire vrijstelling.', + vatDomestic: '21% Belgische BTW toegepast', + vatZero: 'Intracommunautaire BTW — 0%', + vatInvalid: 'Ongeldig BTW-nummer', + consentPrefix: 'Ik aanvaard de', + consentTerms: 'algemene voorwaarden', + consentAnd: 'en het', + consentPrivacy: 'privacybeleid', + consentSuffix: 'van MyInfoMate.', + submitCta: 'Start mijn gratis proefperiode', + submitting: 'Uw omgeving wordt aangemaakt…', + noCardNote: 'Geen afschrijving tijdens de proefperiode. Wij vragen nooit om een kaart.', + errorSlugTaken: 'Dit adres is al bezet — kies een andere.', + errorGeneric: 'Er is iets misgegaan, probeer het opnieuw.', + successTitle: 'Welkom aan boord!', + successDesc: 'Uw omgeving is klaar. Er is zojuist een bevestigingsmail verstuurd.', + recapUrlLabel: 'Uw bezoekersadres', + recapTrialLabel: 'Gratis proefperiode', + recapEmailLabel: 'Bevestigingsmail', + goToApp: 'Naar mijn omgeving', + }, pricing: { sectionLabel: 'Tarieven', sectionTitle: 'Een plan voor elke locatie', @@ -727,8 +940,8 @@ const translations = { startingFrom: 'Vanaf', recommended: 'Aanbevolen', ctaStart: 'Demo aanvragen', + ctaStartTrial: 'Start mijn gratis proefperiode', ctaContact: 'Neem contact op', - comingSoon: 'Binnenkort beschikbaar', enterprisePrice: 'Op maat', enterpriseDesc: 'Voor organisaties met meerdere locaties of specifieke behoeften (maatwerk ontwikkeling, begeleiding, verbeterde SLA).', multiYearLabel: 'Meerjarig budget?', @@ -746,7 +959,7 @@ const translations = { pushNotif: 'Pushmeldingen', stats: 'Bezoekstatistieken', ai: 'AI-bezoekersassistent', - reqPerMonth: 'req/maand', + questionsPerMonth: 'bezoekersvragen / maand', statsBasic: '30 dagen', statsAdvanced: 'Onbeperkt', webDisplayUrl: 'Webweergave op toegewijd URL', @@ -977,6 +1190,77 @@ const translations = { messagePlaceholder: 'Erzählen Sie uns von Ihrem Projekt...', submitButton: 'Meine Anfrage senden', }, + signup: { + planBadge: 'Essentiel-Plan', + heroTitle: 'Ihr Kulturort, online in wenigen Minuten.', + priceSuffix: '/ Monat zzgl. MwSt.', + trialBadge: '14 Tage kostenlos testen — keine Kreditkarte nötig', + feature1: 'Web-Besucher-App, per QR-Code geöffnet — keine Installation', + feature2: 'Unbegrenzte Inhalte, Agenda, Pläne und Sektionen', + feature3: 'Mehrsprachig — Ihre Besucher wählen ihre Sprache', + feature4: 'Ihre Farben und Ihr Logo, Ihre eigene Adresse', + watermarkNote: 'Während der Testphase zeigen Ihre Besucherseiten ein leichtes "Vorschau"-Wasserzeichen. Es verschwindet, sobald Ihr Abonnement aktiviert ist.', + trustHosting: 'Gehostet in Europa', + trustCancel: 'Jederzeit kündbar', + trustSupport: 'Support auf Französisch', + formTitle: 'Konto erstellen', + alreadyAccount: 'Haben Sie bereits einen Arbeitsbereich?', + loginLink: 'Anmelden', + sectionOrg: 'Ihre Organisation', + orgNameLabel: 'Name der Organisation', + orgNamePlaceholder: 'Museum der Schönen Künste', + slugLabel: 'Ihre eigene Adresse', + slugPrefix: 'app.myinfomate.be/', + slugPlaceholder: 'ihr-museum', + slugHintDefault: 'Kleinbuchstaben, Zahlen und Bindestriche — dies ist die URL, die Ihre Besucher scannen werden.', + slugHintChecking: 'Verfügbarkeit wird geprüft…', + slugHintTaken: 'Diese Adresse ist bereits vergeben — wählen Sie einen anderen Namen.', + slugHintAvailable: 'Verfügbar!', + slugHintTooShort: 'Mindestens 3 Zeichen.', + sectionContact: 'Ihre Kontaktdaten', + firstNameLabel: 'Vorname', + firstNamePlaceholder: 'Camille', + lastNameLabel: 'Nachname', + lastNamePlaceholder: 'Durand', + emailLabel: 'Geschäftliche E-Mail', + emailPlaceholder: 'camille@ihr-museum.de', + passwordLabel: 'Passwort', + passwordPlaceholder: 'Mindestens 8 Zeichen', + sectionBilling: 'Rechnungsstellung', + addressLabel: 'Adresse', + addressPlaceholder: 'Kulturstraße 12', + zipLabel: 'Postleitzahl', + zipPlaceholder: '1000', + cityLabel: 'Stadt', + cityPlaceholder: 'Brüssel', + countryLabel: 'Land', + vatLabel: 'USt-IdNr.', + vatOptional: 'optional', + vatPlaceholder: 'BE 0123.456.789', + vatChecking: 'USt-IdNr. wird geprüft…', + vatUnavailable: 'USt-Validierungsdienst nicht verfügbar — belgische MwSt. angewendet, später erneut zu validieren.', + vatNoNumber: '21% belgische MwSt. angewendet', + vatNoNumberHint: 'Fügen Sie eine gültige USt-IdNr. für die innergemeinschaftliche Befreiung hinzu.', + vatDomestic: '21% belgische MwSt. angewendet', + vatZero: 'Innergemeinschaftliche MwSt. — 0%', + vatInvalid: 'Ungültige USt-IdNr.', + consentPrefix: 'Ich akzeptiere die', + consentTerms: 'Allgemeinen Geschäftsbedingungen', + consentAnd: 'und die', + consentPrivacy: 'Datenschutzrichtlinie', + consentSuffix: 'von MyInfoMate.', + submitCta: 'Kostenlos testen', + submitting: 'Ihr Arbeitsbereich wird erstellt…', + noCardNote: 'Keine Abbuchung während der Testphase. Wir fragen nie nach einer Karte.', + errorSlugTaken: 'Diese Adresse ist vergeben — wählen Sie eine andere.', + errorGeneric: 'Etwas ist schiefgelaufen, bitte versuchen Sie es erneut.', + successTitle: 'Willkommen an Bord!', + successDesc: 'Ihr Arbeitsbereich ist bereit. Eine Bestätigungs-E-Mail wurde soeben versendet.', + recapUrlLabel: 'Ihre Besucheradresse', + recapTrialLabel: 'Kostenlose Testphase', + recapEmailLabel: 'Bestätigungs-E-Mail', + goToApp: 'Zu meinem Arbeitsbereich', + }, pricing: { sectionLabel: 'Preise', sectionTitle: 'Ein Plan für jeden Standort', @@ -988,8 +1272,8 @@ const translations = { startingFrom: 'Ab', recommended: 'Empfohlen', ctaStart: 'Demo anfordern', + ctaStartTrial: 'Kostenlos testen', ctaContact: 'Kontaktieren Sie uns', - comingSoon: 'Demnächst verfügbar', enterprisePrice: 'Auf Anfrage', enterpriseDesc: 'Für Organisationen mit mehreren Standorten oder spezifischen Anforderungen (individuelle Entwicklung, Begleitung, erweiterter SLA).', multiYearLabel: 'Mehrjähriges Budget?', @@ -1007,7 +1291,7 @@ const translations = { pushNotif: 'Push-Benachrichtigungen', stats: 'Besucherstatistiken', ai: 'KI-Besucherassistent', - reqPerMonth: 'Req/Monat', + questionsPerMonth: 'Besucherfragen / Monat', statsBasic: '30 Tage', statsAdvanced: 'Unbegrenzt', webDisplayUrl: 'Web-Anzeige auf dedizierter URL',