feat: configurator overhaul — full i18n, AI brief generation, redesigned UI

Configurator:
- Full i18n: industries, timelines, AI types, field labels, error messages,
  complete screen — all translated to French
- Real AI brief generation via OpenRouter (DeepSeek V3.2) with fallback template
- Fixed email notification bug (was sending to client instead of admin)
- Added wizard reset capability ("Start Over" button on success screen)
- Redesigned section shell with refined card, accent line, step indicators
- All step components use translation keys instead of hardcoded strings

Hero:
- Tighter spacing to keep CTAs above the fold
- Reduced bottom gradient height

Footer:
- Removed GitHub link
- Legal name in copyright
- "American-founded" location text

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 17:52:09 +01:00
parent 4aa357a999
commit acefb70b68
12 changed files with 532 additions and 459 deletions

View File

@@ -15,134 +15,176 @@ interface ConfigureRequestBody {
email: string;
}
// ─── Helpers ─────────────────────────────────────────────────────────────────
// ─── Formatting helpers ──────────────────────────────────────────────────────
function formatServicesList(services: string[]): string {
if (services.length === 0) return 'digital services';
if (services.length === 1) return services[0];
if (services.length === 2) return `${services[0]} and ${services[1]}`;
const last = services[services.length - 1];
const rest = services.slice(0, -1);
return `${rest.join(', ')}, and ${last}`;
const SERVICE_NAMES: Record<string, string> = {
web: 'Web Design & Development',
systems: 'Custom Software',
infrastructure: 'Private Infrastructure',
};
const INDUSTRY_NAMES: Record<string, string> = {
maritime: 'Maritime & Yachting',
hospitality: 'Hospitality',
technology: 'Technology',
realestate: 'Real Estate',
finance: 'Finance',
ngo: 'NGO & Nonprofit',
other: 'Other',
};
const TIMELINE_NAMES: Record<string, string> = {
asap: 'As soon as possible',
'1-3months': '13 months',
'3-6months': '36 months',
exploring: 'Just exploring',
};
const AI_TYPE_NAMES: Record<string, string> = {
teammate: 'Internal AI Teammate',
'customer-facing': 'Customer-Facing AI',
'data-intelligence': 'Data Intelligence',
notsure: 'AI Integration (approach TBD)',
};
function buildContext(body: ConfigureRequestBody): string {
const services = body.services.map((s) => SERVICE_NAMES[s] ?? s).join(', ');
const industry = body.industry ? INDUSTRY_NAMES[body.industry] ?? body.industry : 'Not specified';
const timeline = body.timeline ? TIMELINE_NAMES[body.timeline] ?? body.timeline : 'Not specified';
const company = body.company.trim() || 'Not specified';
const aiType = body.aiEnabled && body.aiType ? AI_TYPE_NAMES[body.aiType] ?? body.aiType : null;
let context = `Client Name: ${body.name}
Company: ${company}
Services Requested: ${services}
Industry: ${industry}
Timeline: ${timeline}`;
if (body.aiEnabled) {
context += `\nAI Integration: Yes — ${aiType ?? 'type to be determined'}`;
}
if (body.scope.trim()) {
context += `\nClient's Goals: "${body.scope.trim()}"`;
}
return context;
}
function formatTimeline(timeline: string | null): string {
switch (timeline) {
case 'asap':
return 'as soon as possible';
case '1-3months':
return 'within the next 13 months';
case '3-6months':
return 'over a 36 month horizon';
case 'exploring':
return 'at a pace that suits your strategic planning';
default:
return 'within a timeline to be agreed upon';
// ─── AI Brief Generation ─────────────────────────────────────────────────────
async function generateBriefWithAI(body: ConfigureRequestBody): Promise<string> {
const apiKey = process.env.OPENROUTER_API_KEY;
if (!apiKey) {
return generateFallbackBrief(body);
}
const context = buildContext(body);
const displayName = body.name.split(' ')[0] || body.name;
const systemPrompt = `You are writing a project brief on behalf of LetsBe Solutions, a digital studio that builds custom websites, custom software, and private digital infrastructure. The company is American-founded and serves businesses on the Côte d'Azur and internationally.
Key facts about LetsBe:
- Every project is designed and coded from scratch — no templates, no page builders
- They build custom software (CRMs, management platforms, association systems, etc.)
- They deploy private infrastructure on dedicated servers that the client fully owns and controls
- They can layer AI integration into any system they build
- Small, experienced team with decades of combined experience in design and engineering
- They emphasize data ownership, privacy, and digital sovereignty
Write in a professional but warm tone. Be specific and practical — no empty buzzwords. The brief should feel like it was written by someone who understood the client's needs, not a generic template.`;
const userPrompt = `Generate a personalized project brief for the following prospect. The brief should:
1. Address the client by first name (${displayName})
2. Acknowledge their specific industry and goals
3. For each service they selected, describe concretely what LetsBe would build and why it matters for their business
4. If AI integration is requested, explain practically what that would look like
5. Propose a clear engagement approach (discovery → strategy → build → launch)
6. Include a timeline note based on their preference
7. End with clear next steps
Format the brief using **bold** for section headings and --- for separators. Keep it concise but substantive — around 400-600 words.
Client details:
${context}`;
try {
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
'HTTP-Referer': process.env.NEXT_PUBLIC_SITE_URL || 'https://letsbe.biz',
'X-Title': 'LetsBe Project Configurator',
},
body: JSON.stringify({
model: 'deepseek/deepseek-v3.2',
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt },
],
max_tokens: 1500,
temperature: 0.7,
}),
});
if (!response.ok) {
console.error('OpenRouter API error:', response.status, response.statusText);
return generateFallbackBrief(body);
}
const data = await response.json();
const content = data.choices?.[0]?.message?.content;
if (!content) {
return generateFallbackBrief(body);
}
return content;
} catch (error) {
console.error('AI brief generation failed:', error);
return generateFallbackBrief(body);
}
}
function formatIndustry(industry: string | null): string {
switch (industry) {
case 'maritime':
return 'Maritime & Yachting';
case 'hospitality':
return 'Hospitality';
case 'technology':
return 'Technology';
case 'realestate':
return 'Real Estate';
case 'finance':
return 'Finance';
case 'ngo':
return 'NGO & Nonprofit';
case 'other':
return 'your sector';
default:
return 'your industry';
}
}
// ─── Fallback Brief (no API key or API failure) ──────────────────────────────
function formatAIType(aiType: string | null): string {
switch (aiType) {
case 'teammate':
return 'an internal AI teammate that augments your team\'s workflow';
case 'customer-facing':
return 'a customer-facing AI layer to enhance client interactions';
case 'data-intelligence':
return 'a data intelligence system that surfaces actionable insights from your data';
case 'notsure':
return 'an AI integration strategy tailored to your specific use case (to be defined during discovery)';
default:
return 'intelligent automation';
}
}
function generateFallbackBrief(body: ConfigureRequestBody): string {
const { services, aiEnabled, aiType, industry, scope, timeline, name, company } = body;
function generateMockBrief(body: ConfigureRequestBody): string {
const {
services,
aiEnabled,
aiType,
industry,
scope,
timeline,
name,
company,
} = body;
const servicesList = formatServicesList(services);
const industryLabel = formatIndustry(industry);
const timelineStr = formatTimeline(timeline);
const displayCompany = company.trim() || 'your organisation';
const serviceNames = services.map((s) => SERVICE_NAMES[s] ?? s);
const servicesList = serviceNames.length <= 2
? serviceNames.join(' and ')
: `${serviceNames.slice(0, -1).join(', ')}, and ${serviceNames[serviceNames.length - 1]}`;
const industryLabel = industry ? INDUSTRY_NAMES[industry] ?? industry : 'your industry';
const displayCompany = company.trim() || 'your organization';
const displayName = name.split(' ')[0] || 'there';
const hasWeb = services.some((s) =>
s.toLowerCase().includes('web') || s.toLowerCase().includes('design'),
);
const hasSystems = services.some((s) =>
s.toLowerCase().includes('system') || s.toLowerCase().includes('cog') || s.toLowerCase().includes('custom'),
);
const hasInfra = services.some((s) =>
s.toLowerCase().includes('infra') || s.toLowerCase().includes('server'),
);
const timelineStr = timeline
? TIMELINE_NAMES[timeline]?.toLowerCase() ?? 'a timeline to be agreed upon'
: 'a timeline to be agreed upon';
const hasWeb = services.includes('web');
const hasSystems = services.includes('systems');
const hasInfra = services.includes('infrastructure');
let sections = '';
let webSection = '';
if (hasWeb) {
webSection = `
**Design & Development**
We will design and develop a bespoke digital presence for ${displayCompany} — starting from a clean slate, not a template. Expect a modern, responsive interface built on a headless architecture with exceptional performance scores (Lighthouse 95+). The design will reflect your brand positioning within the ${industryLabel} sector, with full accessibility compliance and SEO-optimised markup from day one.
`;
sections += `\n**Web Design & Development**\nWe'll design and build a custom website for ${displayCompany} from scratch — no templates, no page builders. Modern, responsive, fast, and optimized for search engines from day one.\n`;
}
let systemsSection = '';
if (hasSystems) {
systemsSection = `
**Custom Systems**
We will architect and build a purpose-made internal system tailored to ${displayCompany}'s operational workflows. This includes a custom data model, role-based access control, and integrations with your existing toolchain. No generic SaaS — every logic rule and every interface is written to match how your team actually works.
`;
sections += `\n**Custom Software**\nWe'll build a purpose-made system tailored to how ${displayCompany} actually operates — custom data model, role-based access, and integrations with your existing tools.\n`;
}
let infraSection = '';
if (hasInfra) {
infraSection = `
**Digital Infrastructure**
We will provision a dedicated, private cloud environment for ${displayCompany} — giving your team full data sovereignty. This includes containerised deployments, automated backups, uptime monitoring, and a managed CI/CD pipeline. Your data remains yours, stored in compliant European infrastructure.
`;
sections += `\n**Private Infrastructure**\nWe'll set up a dedicated server environment for ${displayCompany} with email, cloud storage, and business tools that you fully own and control.\n`;
}
let aiSection = '';
if (aiEnabled && aiType) {
aiSection = `
**AI Integration**
Beyond the core build, we will layer in ${formatAIType(aiType)}. This is not a bolted-on chatbot — it is a deeply integrated capability that evolves alongside your digital ecosystem. The AI layer will be scoped precisely during our discovery sessions to ensure maximum return on investment.
`;
const aiLabel = AI_TYPE_NAMES[aiType] ?? 'AI integration';
sections += `\n**AI Integration**\nWe'll layer ${aiLabel.toLowerCase()} into your systems — deeply integrated, not bolted on. The exact approach will be scoped during discovery.\n`;
}
let scopeSection = '';
if (scope && scope.trim().length > 0) {
scopeSection = `
**Your Goals**
You've shared the following context: "${scope.trim()}" — we've taken note of this and will frame our initial discovery session around these priorities.
`;
if (scope?.trim()) {
sections += `\n**Your Goals**\nYou shared: "${scope.trim()}" — we'll frame our discovery sessions around these priorities.\n`;
}
return `**Project Brief for ${displayCompany}**
@@ -153,27 +195,27 @@ Date: ${new Date().toLocaleDateString('en-GB', { year: 'numeric', month: 'long',
**Overview**
Hi ${displayName}, based on your requirements for ${servicesList} within the ${industryLabel} sector, we have prepared this preliminary brief to guide our first conversation.
Hi ${displayName}, based on your interest in ${servicesList} for the ${industryLabel} sector, here's a preliminary brief to guide our first conversation.
LetsBe. will approach ${displayCompany}'s project as a complete digital ecosystem — not a collection of disconnected deliverables. Every component we build is designed to work in concert, giving you a unified platform that you own and control entirely.
${webSection}${systemsSection}${infraSection}${aiSection}${scopeSection}
**Recommended Approach**
We'll approach this as a unified project — every component working together, fully owned and controlled by you.
${sections}
**Our Approach**
We propose a phased engagement beginning with a structured Discovery sprint (23 sessions) to map your requirements, data flows, and technical constraints before any code is written. This protects your investment and ensures we build exactly what you need — nothing more, nothing less.
We start with a Discovery phase (23 sessions) to understand your requirements before writing any code. This ensures we build exactly what you need.
**Timeline**
Based on your preference, we will plan to deliver this project ${timelineStr}. A detailed project roadmap with milestones will be shared following the Discovery phase.
Target delivery: ${timelineStr}. A detailed roadmap will follow the Discovery phase.
**Next Steps**
1. Book a 30-minute introductory call with our team
2. We'll share a detailed scope document within 48 hours of that call
3. Discovery sprint begins — at no obligation
1. Book a 30-minute introductory call
2. We'll share a detailed scope document within 48 hours
3. Discovery begins — no obligation
We look forward to building something exceptional together.
Looking forward to building something great together.
— The LetsBe. Team`;
— The LetsBe Team`;
}
// ─── Route Handler ────────────────────────────────────────────────────────────
@@ -204,8 +246,8 @@ export async function POST(request: NextRequest) {
);
}
// Generate the brief
const brief = generateMockBrief(body);
// Generate the brief (AI if available, fallback otherwise)
const brief = await generateBriefWithAI(body);
// Send emails (non-blocking — don't fail the response if email fails)
if (process.env.SMTP_HOST && process.env.SMTP_PASS) {
@@ -217,7 +259,7 @@ export async function POST(request: NextRequest) {
brief,
}),
sendLeadNotification({
to: body.email,
to: process.env.ADMIN_EMAIL || 'hello@letsbe.biz',
name: body.name,
company: body.company,
brief,
@@ -225,7 +267,6 @@ export async function POST(request: NextRequest) {
email: body.email,
}),
]).catch(() => {
// Silently log — don't break the user flow
console.error('Email sending failed');
});
}