217 lines
8.0 KiB
TypeScript
217 lines
8.0 KiB
TypeScript
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|||
|
|
|
|||
|
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
interface ConfigureRequestBody {
|
|||
|
|
services: string[];
|
|||
|
|
aiEnabled: boolean;
|
|||
|
|
aiType: string | null;
|
|||
|
|
industry: string | null;
|
|||
|
|
scope: string;
|
|||
|
|
timeline: string | null;
|
|||
|
|
name: string;
|
|||
|
|
company: string;
|
|||
|
|
email: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 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}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function formatTimeline(timeline: string | null): string {
|
|||
|
|
switch (timeline) {
|
|||
|
|
case 'asap':
|
|||
|
|
return 'as soon as possible';
|
|||
|
|
case '1-3months':
|
|||
|
|
return 'within the next 1–3 months';
|
|||
|
|
case '3-6months':
|
|||
|
|
return 'over a 3–6 month horizon';
|
|||
|
|
case 'exploring':
|
|||
|
|
return 'at a pace that suits your strategic planning';
|
|||
|
|
default:
|
|||
|
|
return 'within a timeline to be agreed upon';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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 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 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'),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
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.
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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.
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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.
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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.
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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.
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return `**Project Brief for ${displayCompany}**
|
|||
|
|
Prepared for: ${name}
|
|||
|
|
Date: ${new Date().toLocaleDateString('en-GB', { year: 'numeric', month: 'long', day: 'numeric' })}
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Overview**
|
|||
|
|
|
|||
|
|
Hi ${displayName}, based on your requirements for ${servicesList} within the ${industryLabel} sector, we have prepared this 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 propose a phased engagement beginning with a structured Discovery sprint (2–3 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.
|
|||
|
|
|
|||
|
|
**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.
|
|||
|
|
|
|||
|
|
**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
|
|||
|
|
|
|||
|
|
We look forward to building something exceptional together.
|
|||
|
|
|
|||
|
|
— The LetsBe. Team`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── Route Handler ────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
export async function POST(request: NextRequest) {
|
|||
|
|
try {
|
|||
|
|
const body = (await request.json()) as ConfigureRequestBody;
|
|||
|
|
|
|||
|
|
// Validate required fields
|
|||
|
|
if (!body.services || body.services.length === 0) {
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: 'At least one service must be selected.' },
|
|||
|
|
{ status: 400 },
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!body.name || body.name.trim().length < 2) {
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: 'A valid name is required.' },
|
|||
|
|
{ status: 400 },
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!body.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(body.email)) {
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: 'A valid email address is required.' },
|
|||
|
|
{ status: 400 },
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Generate the brief
|
|||
|
|
const brief = generateMockBrief(body);
|
|||
|
|
|
|||
|
|
return NextResponse.json({ success: true, brief });
|
|||
|
|
} catch {
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: 'An unexpected error occurred. Please try again.' },
|
|||
|
|
{ status: 500 },
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|