diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index 873d54d..4efec87 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -1,166 +1,274 @@
-
-import React, { useEffect, useState } from 'react';
-import type { ReactElement } from 'react';
+import {
+ mdiAccountTieOutline,
+ mdiArrowRight,
+ mdiBullhornOutline,
+ mdiCashMultiple,
+ mdiChartTimelineVariant,
+ mdiFileDocumentOutline,
+ mdiHomeCityOutline,
+ mdiLightbulbOnOutline,
+ mdiMapMarkerOutline,
+ mdiOpenInNew,
+ mdiRobotOutline,
+ mdiScaleBalance,
+} from '@mdi/js';
import Head from 'next/head';
import Link from 'next/link';
+import React, { ReactElement } from 'react';
import BaseButton from '../components/BaseButton';
+import BaseIcon from '../components/BaseIcon';
import CardBox from '../components/CardBox';
-import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest';
-import BaseDivider from '../components/BaseDivider';
-import BaseButtons from '../components/BaseButtons';
import { getPageTitle } from '../config';
-import { useAppSelector } from '../stores/hooks';
-import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
-import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
+const featureCards = [
+ {
+ icon: mdiRobotOutline,
+ title: 'AI launch architect',
+ text: 'Turn founder notes into a saved launch blueprint with project phases, tasks, and budget-aligned next steps.',
+ },
+ {
+ icon: mdiScaleBalance,
+ title: 'Compliance research hub',
+ text: 'Track city, county, state, and federal research items as real checklist records instead of scattered notes.',
+ },
+ {
+ icon: mdiHomeCityOutline,
+ title: 'Land, build, and layout planning',
+ text: 'Seed site strategy, property records, and design-asset briefs for walkthroughs, systems, and 3D-ready layouts.',
+ },
+ {
+ icon: mdiAccountTieOutline,
+ title: 'Staffing to opening day',
+ text: 'Generate launch roles, training tracks, and go-to-market campaigns so the business is prepared to operate — not just exist on paper.',
+ },
+];
-export default function Starter() {
- const [illustrationImage, setIllustrationImage] = useState({
- src: undefined,
- photographer: undefined,
- photographer_url: undefined,
- })
- const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
- const [contentType, setContentType] = useState('image');
- const [contentPosition, setContentPosition] = useState('left');
- const textColor = useAppSelector((state) => state.style.linkColor);
+const workflowSteps = [
+ {
+ eyebrow: 'Step 01',
+ title: 'Capture the founder vision',
+ text: 'Describe the business, target location, budget, and legacy goal. Add a reference image or scope file when you have one.',
+ icon: mdiLightbulbOnOutline,
+ },
+ {
+ eyebrow: 'Step 02',
+ title: 'Generate the first launch package',
+ text: 'The app saves a project workspace with timelines, documents, funding motions, staffing plans, and design backlog items.',
+ icon: mdiChartTimelineVariant,
+ },
+ {
+ eyebrow: 'Step 03',
+ title: 'Run the business buildout',
+ text: 'Use the existing admin interface to refine records, assign work, track approvals, and move toward opening-day readiness.',
+ icon: mdiOpenInNew,
+ },
+];
- const title = 'Legacy Business Builder'
-
- // Fetch Pexels image/video
- useEffect(() => {
- async function fetchData() {
- const image = await getPexelsImage();
- const video = await getPexelsVideo();
- setIllustrationImage(image);
- setIllustrationVideo(video);
- }
- fetchData();
- }, []);
-
- const imageBlock = (image) => (
-
- );
-
- const videoBlock = (video) => {
- if (video?.video_files?.length > 0) {
- return (
-
-
-
- Your browser does not support the video tag.
-
-
-
)
- }
- };
+const valueBlocks = [
+ {
+ icon: mdiCashMultiple,
+ label: 'Funding path',
+ text: 'Budget strategy, capital rounds, and lender-ready planning.',
+ },
+ {
+ icon: mdiMapMarkerOutline,
+ label: 'Site path',
+ text: 'Location, property, and zoning-aware research checkpoints.',
+ },
+ {
+ icon: mdiFileDocumentOutline,
+ label: 'Docs + checklists',
+ text: 'Operating, permit, training, and launch deliverables in one hub.',
+ },
+ {
+ icon: mdiBullhornOutline,
+ label: 'Opening demand',
+ text: 'Marketing and launch motions seeded from the first intake.',
+ },
+];
+export default function HomePage() {
return (
-
+ <>
-
{getPageTitle('Starter Page')}
+
{getPageTitle('Legacy Business Builder')}
-
-
- {contentType === 'image' && contentPosition !== 'background'
- ? imageBlock(illustrationImage)
- : null}
- {contentType === 'video' && contentPosition !== 'background'
- ? videoBlock(illustrationVideo)
- : null}
-
-
-
-
-
-
This is a React.js/Node.js app generated by the Flatlogic Web App Generator
-
For guides and documentation please check
- your local README.md and the Flatlogic documentation
+
+
+
+
+
+
+
+
+
Legacy Business Builder
+
AI-powered launch operating system
+
+
+
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
-
© 2026 {title} . All rights reserved
-
- Privacy Policy
-
-
+
+
+
+
+
+
+
+
+
+ Idea to opening-day execution
+
+
+ Build the business legacy your family can inherit, operate, and grow.
+
+
+ This app is designed for founder ideas that need more than a pitch deck. It creates a real launch workflow across funding,
+ property, compliance research, design assets, staffing, training, marketing, and opening-day readiness.
+
-
+
+
+
+
+
+
+ {valueBlocks.map((block) => (
+
+
+
+
+
{block.label}
+
{block.text}
+
+ ))}
+
+
+
+
+
+
+
+
Now live
+
Legacy Launchpad
+
+
+ First MVP slice
+
+
+
+ The first delivery is a real founder intake that creates saved project records, timelines, tasks, compliance research, funding
+ rounds, property notes, staffing plans, training, launch campaigns, and 3D/layout asset briefs.
+
+
+
Best for the first conversation
+
+ “I have the vision in my head. Help me turn it into the first executable launch plan for my target city and state.”
+
+
+
+
+
Important
+
+ The product can organize and draft launch research, but city/county/state legal and compliance items still need confirmation with
+ agencies and licensed professionals before real-world decisions are made.
+
+
+
+
+
+
+
+
+
What the platform handles
+
A premium operations shell for turning a founder dream into a buildable business.
+
+ Instead of leaving the concept trapped in notes, the app converts it into connected records and workflows your family can keep using as the business grows.
+
+
+
+
+ {featureCards.map((card) => (
+
+
+
+
+ {card.title}
+ {card.text}
+
+ ))}
+
+
+
+
+
+
+
How it works
+
Three deliberate moves to get from idea to a saved launch system.
+
+
+
+ {workflowSteps.map((step) => (
+
+
+
{step.title}
+
{step.text}
+
+ ))}
+
+
+
+
+
+
+
+
+
Start inside the real app
+
Open the admin interface, then use Legacy Launchpad to create the first working plan.
+
+ The landing page stays public. The planning workflow and entity records stay protected behind login, so your business operating system stays private while you build it.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
);
}
-Starter.getLayout = function getLayout(page: ReactElement) {
+HomePage.getLayout = function getLayout(page: ReactElement) {
return {page} ;
};
-
diff --git a/frontend/src/pages/legacy-launchpad.tsx b/frontend/src/pages/legacy-launchpad.tsx
new file mode 100644
index 0000000..d983c9b
--- /dev/null
+++ b/frontend/src/pages/legacy-launchpad.tsx
@@ -0,0 +1,1407 @@
+/* eslint-disable @next/next/no-img-element */
+import {
+ mdiAccountTieOutline,
+ mdiAlertCircle,
+ mdiArrowRight,
+ mdiBullhornOutline,
+ mdiCashMultiple,
+ mdiChartTimelineVariant,
+ mdiCheckCircle,
+ mdiFileDocumentOutline,
+ mdiHomeCityOutline,
+ mdiLightbulbOnOutline,
+ mdiMapMarkerOutline,
+ mdiOpenInNew,
+ mdiRobotOutline,
+ mdiScaleBalance,
+ mdiUpload,
+} from '@mdi/js';
+import axios from 'axios';
+import { Field, Form, Formik } from 'formik';
+import Head from 'next/head';
+import Link from 'next/link';
+import React, { ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
+import BaseButton from '../components/BaseButton';
+import BaseIcon from '../components/BaseIcon';
+import CardBox from '../components/CardBox';
+import FormField from '../components/FormField';
+import FormFilePicker from '../components/FormFilePicker';
+import FormImagePicker from '../components/FormImagePicker';
+import NotificationBar from '../components/NotificationBar';
+import SectionMain from '../components/SectionMain';
+import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
+import { getPageTitle } from '../config';
+import { humanize } from '../helpers/humanize';
+import { hasPermission } from '../helpers/userPermissions';
+import LayoutAuthenticated from '../layouts/Authenticated';
+import { useAppSelector } from '../stores/hooks';
+
+type LaunchpadState = Record | null;
+
+type LivePropertySearchForm = {
+ city: string;
+ state: string;
+ country: string;
+ propertyType: string;
+ maxPrice: string;
+ minLotAcres: string;
+ limit: string;
+};
+
+type MetricCardProps = {
+ icon: string;
+ label: string;
+ value: string | number;
+ helper: string;
+};
+
+type SectionCardProps = {
+ eyebrow: string;
+ title: string;
+ children: React.ReactNode;
+};
+
+const blueprintSingleLineLimits: Record = {
+ ideaTitle: 180,
+ businessType: 180,
+ city: 120,
+ county: 120,
+ state: 60,
+ targetBudget: 40,
+ targetOpenDate: 40,
+};
+
+const blueprintLongTextLimits: Record = {
+ sitePreferences: 1500,
+ vision: 5000,
+ familyLegacyGoal: 2500,
+};
+
+const initialValues = {
+ ideaTitle: '',
+ businessType: '',
+ locationFlexibility: 'usa_wide',
+ sitePreferences: '',
+ city: '',
+ county: '',
+ state: '',
+ targetBudget: '',
+ targetOpenDate: '',
+ vision: '',
+ familyLegacyGoal: '',
+ reference_images: [],
+ reference_files: [],
+};
+
+const numberFormatter = new Intl.NumberFormat('en-US');
+const currencyFormatter = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ maximumFractionDigits: 0,
+});
+const dateFormatter = new Intl.DateTimeFormat('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+});
+const locationSearchOptions = [
+ { value: 'specific_area', label: 'I already have a place in mind' },
+ { value: 'usa_wide', label: 'Search anywhere in the USA' },
+ { value: 'international', label: 'Search USA or other countries' },
+];
+
+const livePropertyTypeOptions = [
+ { value: 'any', label: 'Any supported listing type' },
+ { value: 'land', label: 'Land' },
+ { value: 'single_family', label: 'Single family' },
+ { value: 'condo', label: 'Condo' },
+ { value: 'townhouse', label: 'Townhouse' },
+ { value: 'multi_family', label: 'Multi-family' },
+ { value: 'apartment', label: 'Apartment' },
+ { value: 'manufactured', label: 'Manufactured' },
+];
+
+const initialLivePropertySearch: LivePropertySearchForm = {
+ city: '',
+ state: '',
+ country: 'USA',
+ propertyType: 'any',
+ maxPrice: '',
+ minLotAcres: '',
+ limit: '6',
+};
+
+function formatCurrency(value: number | string | null | undefined) {
+ if (value === null || value === undefined || value === '') {
+ return '—';
+ }
+
+ const numeric = Number(value);
+ if (!Number.isFinite(numeric)) {
+ return '—';
+ }
+
+ return currencyFormatter.format(numeric);
+}
+
+function formatNumber(value: number | string | null | undefined) {
+ if (value === null || value === undefined || value === '') {
+ return '—';
+ }
+
+ const numeric = Number(value);
+ if (!Number.isFinite(numeric)) {
+ return '—';
+ }
+
+ return numberFormatter.format(numeric);
+}
+
+function formatDate(value: string | number | Date | null | undefined) {
+ if (!value) {
+ return 'TBD';
+ }
+
+ const parsed = new Date(value);
+ if (Number.isNaN(parsed.getTime())) {
+ return 'TBD';
+ }
+
+ return dateFormatter.format(parsed);
+}
+
+function formatLocationSummary(location: Record | null | undefined) {
+ if (!location) {
+ return 'Flexible / to be chosen';
+ }
+
+ const specificLocation = [location.city, location.state].filter(Boolean).join(', ');
+ if (specificLocation) {
+ return specificLocation;
+ }
+
+ if (location.county) {
+ return location.county;
+ }
+
+ if (location.label) {
+ return location.label;
+ }
+
+ if (location.country) {
+ return location.country;
+ }
+
+ return 'Flexible / to be chosen';
+}
+
+function statusTone(status?: string) {
+ switch (status) {
+ case 'approved':
+ case 'completed':
+ case 'done':
+ case 'succeeded':
+ case 'funded':
+ return 'bg-emerald-100 text-emerald-700';
+ case 'in_progress':
+ case 'running':
+ case 'submitted':
+ return 'bg-blue-100 text-blue-700';
+ case 'failed':
+ case 'blocked':
+ case 'rejected':
+ return 'bg-rose-100 text-rose-700';
+ default:
+ return 'bg-slate-100 text-slate-700';
+ }
+}
+
+function deriveLivePropertyType(values: Record, responseData: Record) {
+ const savedPropertyType = responseData?.project?.properties_project?.[0]?.property_type;
+ if (savedPropertyType === 'raw_land') {
+ return 'land';
+ }
+
+ if (savedPropertyType === 'residential_conversion') {
+ return 'single_family';
+ }
+
+ const searchText = `${values?.sitePreferences || ''} ${values?.businessType || ''}`.toLowerCase();
+ if (/(land|acre|rural|retreat|farm)/.test(searchText)) {
+ return 'land';
+ }
+
+ return 'any';
+}
+
+function buildInitialLivePropertySearch(values: Record, responseData: Record): LivePropertySearchForm {
+ return {
+ city: values?.city || responseData?.location?.city || '',
+ state: values?.state || responseData?.location?.state || '',
+ country: 'USA',
+ propertyType: deriveLivePropertyType(values, responseData),
+ maxPrice: values?.targetBudget || '',
+ minLotAcres: '',
+ limit: '6',
+ };
+}
+
+function cleanBlueprintSingleLine(value: any, maxLength: number) {
+ if (typeof value !== 'string') {
+ return '';
+ }
+
+ const normalized = value.replace(/\s+/g, ' ').trim();
+ return normalized.length > maxLength ? normalized.slice(0, maxLength).trim() : normalized;
+}
+
+function cleanBlueprintLongText(value: any, maxLength: number) {
+ if (typeof value !== 'string') {
+ return '';
+ }
+
+ const compactedLines = value
+ .replace(/\r\n?/g, '\n')
+ .split('\n')
+ .map((line) => line.replace(/[ \t]+/g, ' ').trim())
+ .reduce((accumulator: string[], line) => {
+ const previousLine = accumulator[accumulator.length - 1];
+
+ if (!line && !previousLine) {
+ return accumulator;
+ }
+
+ if (line && previousLine === line) {
+ return accumulator;
+ }
+
+ accumulator.push(line);
+ return accumulator;
+ }, []);
+
+ const normalized = compactedLines.join('\n').trim();
+ return normalized.length > maxLength ? normalized.slice(0, maxLength).trim() : normalized;
+}
+
+function compactUploadedReference(item: any) {
+ if (!item || typeof item !== 'object') {
+ return null;
+ }
+
+ const normalizedItem = {
+ id: cleanBlueprintSingleLine(item.id, 120),
+ name: cleanBlueprintSingleLine(item.name, 240),
+ sizeInBytes: Number.isFinite(Number(item.sizeInBytes)) ? Number(item.sizeInBytes) : undefined,
+ privateUrl: cleanBlueprintSingleLine(item.privateUrl, 500),
+ publicUrl: cleanBlueprintSingleLine(item.publicUrl, 500),
+ new: Boolean(item.new),
+ };
+
+ return Object.fromEntries(Object.entries(normalizedItem).filter(([, currentValue]) => currentValue !== undefined && currentValue !== ''));
+}
+
+function buildBlueprintPayload(values: Record) {
+ return {
+ ideaTitle: cleanBlueprintSingleLine(values.ideaTitle, blueprintSingleLineLimits.ideaTitle),
+ businessType: cleanBlueprintSingleLine(values.businessType, blueprintSingleLineLimits.businessType),
+ locationFlexibility: cleanBlueprintSingleLine(values.locationFlexibility, 40) || 'usa_wide',
+ sitePreferences: cleanBlueprintLongText(values.sitePreferences, blueprintLongTextLimits.sitePreferences),
+ city: cleanBlueprintSingleLine(values.city, blueprintSingleLineLimits.city),
+ county: cleanBlueprintSingleLine(values.county, blueprintSingleLineLimits.county),
+ state: cleanBlueprintSingleLine(values.state, blueprintSingleLineLimits.state),
+ targetBudget: cleanBlueprintSingleLine(values.targetBudget, blueprintSingleLineLimits.targetBudget),
+ targetOpenDate: cleanBlueprintSingleLine(values.targetOpenDate, blueprintSingleLineLimits.targetOpenDate),
+ vision: cleanBlueprintLongText(values.vision, blueprintLongTextLimits.vision),
+ familyLegacyGoal: cleanBlueprintLongText(values.familyLegacyGoal, blueprintLongTextLimits.familyLegacyGoal),
+ reference_images: Array.isArray(values.reference_images) ? values.reference_images.map(compactUploadedReference).filter(Boolean).slice(0, 1) : [],
+ reference_files: Array.isArray(values.reference_files) ? values.reference_files.map(compactUploadedReference).filter(Boolean).slice(0, 1) : [],
+ };
+}
+
+function buildRecoveredLaunchpadResult(project: Record) {
+ const aiRunStatus = Array.isArray(project?.ai_runs_project) ? project.ai_runs_project[0]?.status : null;
+ const recoveredMode = aiRunStatus === 'succeeded' ? 'ai' : 'template';
+ const recoveredSummary = String(project?.vision || '')
+ .split(/\n{2,}/)
+ .map((section) => section.trim())
+ .filter(Boolean)[0] || 'Your launch blueprint was saved and is ready for review.';
+
+ return {
+ mode: recoveredMode,
+ warnings: [
+ `Your blueprint was saved as “${project?.project_name || 'Saved launch blueprint'}”, but the final response back to Launchpad was interrupted. Use the saved workspace below instead of submitting again.`,
+ ],
+ generatedAt: project?.createdAt || project?.created_on || new Date().toISOString(),
+ summary: {
+ executive_summary: recoveredSummary,
+ budget_strategy: 'The workspace is already saved. Review the funding, property, staffing, and task records inside the project to keep refining the plan.',
+ compliance_notice: 'Please verify permits, zoning, taxes, and licensing with local professionals before acting on the saved checklist.',
+ },
+ businessIdea: project?.business_idea || null,
+ location: project?.primary_location || null,
+ project,
+ counts: {
+ phases: Array.isArray(project?.project_phases_project) ? project.project_phases_project.length : 0,
+ tasks: Array.isArray(project?.tasks_project) ? project.tasks_project.length : 0,
+ legal_requirements: Array.isArray(project?.legal_requirements_project) ? project.legal_requirements_project.length : 0,
+ documents: Array.isArray(project?.documents_project) ? project.documents_project.length : 0,
+ funding_rounds: Array.isArray(project?.funding_rounds_project) ? project.funding_rounds_project.length : 0,
+ positions: Array.isArray(project?.positions_project) ? project.positions_project.length : 0,
+ training_programs: Array.isArray(project?.training_programs_project) ? project.training_programs_project.length : 0,
+ marketing_campaigns: Array.isArray(project?.marketing_campaigns_project) ? project.marketing_campaigns_project.length : 0,
+ design_assets: Array.isArray(project?.design_assets_project) ? project.design_assets_project.length : 0,
+ ai_runs: Array.isArray(project?.ai_runs_project) ? project.ai_runs_project.length : 0,
+ },
+ };
+}
+
+function formatApiErrorMessage(error: any, fallbackMessage: string) {
+ const statusCode = error?.response?.status;
+ const responseData = error?.response?.data;
+ const responseMessage = typeof responseData === 'string'
+ ? responseData.trim()
+ : typeof responseData?.message === 'string'
+ ? responseData.message.trim()
+ : typeof responseData?.error === 'string'
+ ? responseData.error.trim()
+ : '';
+
+ if (statusCode === 413) {
+ return 'This blueprint request was larger than the planner expected, so the page could not finish the response cleanly. Keep the intake high-level, and if a saved blueprint appears below, use it instead of submitting again.';
+ }
+
+ if (responseMessage) {
+ return responseMessage;
+ }
+
+ if (typeof error?.message === 'string' && error.message.trim()) {
+ return error.message.trim();
+ }
+
+ return fallbackMessage;
+}
+
+function MetricCard({ icon, label, value, helper }: MetricCardProps) {
+ return (
+
+
+
+
+
{label}
+
{value}
+
{helper}
+
+ );
+}
+
+function SectionCard({ eyebrow, title, children }: SectionCardProps) {
+ return (
+
+
+ {children}
+
+ );
+}
+
+const LegacyLaunchpad = () => {
+ const { currentUser } = useAppSelector((state) => state.auth);
+ const [result, setResult] = useState(null);
+ const [submissionError, setSubmissionError] = useState('');
+ const [recentPlans, setRecentPlans] = useState[]>([]);
+ const [recentPlansLoading, setRecentPlansLoading] = useState(false);
+ const [recentPlansError, setRecentPlansError] = useState('');
+ const [livePropertySearch, setLivePropertySearch] = useState(initialLivePropertySearch);
+ const [livePropertyResults, setLivePropertyResults] = useState[]>([]);
+ const [livePropertySearchLoading, setLivePropertySearchLoading] = useState(false);
+ const [livePropertySearchError, setLivePropertySearchError] = useState('');
+ const [livePropertySearchMeta, setLivePropertySearchMeta] = useState | null>(null);
+ const [hasAttemptedLivePropertySearch, setHasAttemptedLivePropertySearch] = useState(false);
+
+ const canReadProjects = currentUser ? hasPermission(currentUser, 'READ_PROJECTS') : false;
+ const canReadBusinessIdeas = currentUser ? hasPermission(currentUser, 'READ_BUSINESS_IDEAS') : false;
+
+ const loadRecentPlans = useCallback(async () => {
+ if (!canReadProjects) {
+ return;
+ }
+
+ setRecentPlansLoading(true);
+ setRecentPlansError('');
+
+ try {
+ const response = await axios.get('/projects', {
+ params: {
+ limit: 4,
+ page: 0,
+ },
+ });
+ setRecentPlans(Array.isArray(response.data?.rows) ? response.data.rows : []);
+ } catch (error) {
+ console.error('Failed to load recent launch plans', error);
+ setRecentPlansError('Could not load recent launch plans right now.');
+ } finally {
+ setRecentPlansLoading(false);
+ }
+ }, [canReadProjects]);
+
+ const recoverSavedBlueprint = useCallback(async (submittedValues: Record, submissionStartedAt: number) => {
+ if (!canReadProjects || !submittedValues?.ideaTitle) {
+ return null;
+ }
+
+ try {
+ const response = await axios.get('/projects', {
+ params: {
+ project_name: submittedValues.ideaTitle,
+ limit: 5,
+ page: 0,
+ },
+ });
+
+ const matchingPlan = (Array.isArray(response.data?.rows) ? response.data.rows : []).find((plan: Record) => {
+ const planName = String(plan?.project_name || '').toLowerCase();
+ const expectedName = String(submittedValues.ideaTitle || '').toLowerCase();
+ const createdAtMs = Date.parse(plan?.createdAt || plan?.created_on || '');
+ const createdRecently = Number.isNaN(createdAtMs) || createdAtMs >= submissionStartedAt - (2 * 60 * 1000);
+
+ return planName.includes(expectedName) && createdRecently;
+ });
+
+ if (!matchingPlan?.id) {
+ return null;
+ }
+
+ try {
+ const detailResponse = await axios.get(`/projects/${matchingPlan.id}`);
+ return buildRecoveredLaunchpadResult(detailResponse.data || matchingPlan);
+ } catch (detailError) {
+ console.error('Failed to load recovered launch blueprint details', detailError);
+ return buildRecoveredLaunchpadResult(matchingPlan);
+ }
+ } catch (recoveryError) {
+ console.error('Failed to check for a saved launch blueprint after an interrupted response', recoveryError);
+ return null;
+ }
+ }, [canReadProjects]);
+
+ useEffect(() => {
+ loadRecentPlans();
+ }, [loadRecentPlans]);
+
+ const handleLivePropertySearch = useCallback(async () => {
+ setLivePropertySearchError('');
+ setLivePropertySearchLoading(true);
+ setHasAttemptedLivePropertySearch(true);
+
+ try {
+ const response = await axios.post('/projects/property-search', {
+ data: livePropertySearch,
+ });
+ setLivePropertyResults(Array.isArray(response.data?.results) ? response.data.results : []);
+ setLivePropertySearchMeta(response.data || null);
+ } catch (error: any) {
+ console.error('Live property search failed', error);
+ setLivePropertyResults([]);
+ setLivePropertySearchMeta(null);
+ setLivePropertySearchError(formatApiErrorMessage(error, 'We could not load live property listings right now.'));
+ } finally {
+ setLivePropertySearchLoading(false);
+ }
+ }, [livePropertySearch]);
+
+ const generatedProject = result?.project;
+ const generatedIdea = result?.businessIdea;
+ const phaseItems = useMemo(
+ () => [...(generatedProject?.project_phases_project || [])].sort((left, right) => (left?.sort_order || 0) - (right?.sort_order || 0)),
+ [generatedProject],
+ );
+ const taskItems = useMemo(
+ () => [...(generatedProject?.tasks_project || [])].sort((left, right) => new Date(left?.due_at || 0).getTime() - new Date(right?.due_at || 0).getTime()),
+ [generatedProject],
+ );
+ const legalItems = generatedProject?.legal_requirements_project || [];
+ const documentItems = generatedProject?.documents_project || [];
+ const fundingItems = generatedProject?.funding_rounds_project || [];
+ const propertyItems = generatedProject?.properties_project || [];
+ const staffingItems = generatedProject?.positions_project || [];
+ const trainingItems = generatedProject?.training_programs_project || [];
+ const marketingItems = generatedProject?.marketing_campaigns_project || [];
+ const designItems = generatedProject?.design_assets_project || [];
+
+ return (
+ <>
+
+ {getPageTitle('Legacy Launchpad')}
+
+
+
+ {''}
+
+
+
+ Build a real launch blueprint from one founder intake. The AI-generated compliance checklist is a research starter for a known city,
+ county, and state — or for broader market-specific follow-up if you are still choosing the best place. It is not a substitute for legal, tax,
+ construction, or licensing advice.
+
+
+
+
+
+
+
+
+
+
+ Founder-first AI workflow
+
+
+ Turn your business idea into a saved operating blueprint your family can actually build on.
+
+
+ One intake creates the first planning package across funding, land/site, legal research, design assets, staffing, training, and
+ launch marketing — all saved into your existing workspace so the next step is obvious.
+
+
+
+
+
Welcome back
+
{currentUser?.firstName || 'Founder'}
+
Start with the core vision, any location clues you have, and one reference upload.
+
+
+
What ships now
+
+ • Saved business idea + project workspace
+ • Launch phases, tasks, and compliance checklist
+ • Funding, property, staffing, marketing, and 3D asset briefs
+
+
+
+
+
+
+
+
+
+
+
Intake
+
Create the first launch blueprint
+
+ Keep it high-level. If you do not know the city or state yet, leave them blank and let the planner create a broader land/site
+ search path with candidate areas to explore.
+
+
+
+
+ {
+ setSubmissionError('');
+
+ const payload = buildBlueprintPayload(values);
+ const submissionStartedAt = Date.now();
+
+ try {
+ const response = await axios.post('/projects/legacy-builder', {
+ data: payload,
+ });
+ setResult(response.data);
+ setLivePropertySearch(buildInitialLivePropertySearch(payload, response.data));
+ setLivePropertyResults([]);
+ setLivePropertySearchError('');
+ setLivePropertySearchMeta(null);
+ setHasAttemptedLivePropertySearch(false);
+ await loadRecentPlans();
+ } catch (error: any) {
+ console.error('Legacy launchpad generation failed', error);
+
+ const recoveredResult = await recoverSavedBlueprint(payload, submissionStartedAt);
+
+ if (recoveredResult) {
+ setResult(recoveredResult);
+ setLivePropertySearch(buildInitialLivePropertySearch(payload, recoveredResult));
+ setLivePropertyResults([]);
+ setLivePropertySearchError('');
+ setLivePropertySearchMeta(null);
+ setHasAttemptedLivePropertySearch(false);
+ await loadRecentPlans();
+ } else {
+ setSubmissionError(formatApiErrorMessage(error, 'We could not generate the launch blueprint. Please try again.'));
+ }
+ } finally {
+ setSubmitting(false);
+ }
+ }}
+ >
+ {({ values, isSubmitting }) => {
+ const referenceImage = values.reference_images?.[0];
+ const referenceFile = values.reference_files?.[0];
+
+ return (
+
+ );
+ }}
+
+
+
+
+
+ Blueprint output
+ What this workflow creates right away
+
+ {[
+ {
+ icon: mdiLightbulbOnOutline,
+ title: 'Idea and project records',
+ text: 'A saved business idea, site-search record, and project workspace so the concept becomes a trackable program even before the final location is chosen.',
+ },
+ {
+ icon: mdiCashMultiple,
+ title: 'Funding and property path',
+ text: 'Capital milestones plus a first property/site brief so you can move from concept to a financeable, land-aware plan.',
+ },
+ {
+ icon: mdiScaleBalance,
+ title: 'Compliance research hub',
+ text: 'AI-generated legal and permit checkpoints for the chosen market or shortlist — with statuses and due dates.',
+ },
+ {
+ icon: mdiAccountTieOutline,
+ title: 'Staffing, training, and launch',
+ text: 'Planned roles, onboarding material, launch campaigns, and design-asset briefs for layout and walkthrough work.',
+ },
+ ].map((item) => (
+
+
+
+
+
+
{item.title}
+
{item.text}
+
+
+ ))}
+
+
+
+
+
+
+
Design note
+
3D-ready planning, not vaporware
+
+
+
+
+
+
+ This first version captures reference images/files, saves them with the business idea, and generates design-asset briefs for site
+ plans, walkthroughs, and 3D model work. It gives you a real design backlog the next iteration can expand into richer multimodal and
+ rendering workflows.
+
+
+
+
+
+ {result && (
+
+
+
+ Launch blueprint created. Your project, idea brief, timeline, and checklist records are now
+ saved in the workspace.
+
+ {Array.isArray(result.warnings) && result.warnings.length > 0 && (
+
+ {result.warnings[0]}
+
+ )}
+
+
+
+
+
+
+
+
+ {result.mode === 'ai' ? 'AI-assisted blueprint' : 'Template-assisted blueprint'}
+
+
{generatedProject?.project_name}
+
{result.summary?.executive_summary}
+
{result.summary?.budget_strategy}
+
+
+ {canReadProjects && (
+
+ )}
+ {canReadBusinessIdeas && (
+
+ )}
+ {canReadProjects && }
+
+
+
+
Compliance note
+
{result.summary?.compliance_notice}
+
+
+
Search area
+
{formatLocationSummary(result.location)}
+
+
+
Budget
+
{formatCurrency(generatedProject?.target_budget)}
+
+
+
Target open
+
{formatDate(generatedProject?.target_open_date)}
+
+
+
Generated
+
{formatDate(result.generatedAt)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {phaseItems.map((phase) => (
+
+
+
+
{phase.phase_name}
+
{formatDate(phase.start_at)} → {formatDate(phase.end_at)}
+
+
+ {humanize(phase.status)}
+
+
+
{phase.summary}
+
+ ))}
+
+
+
+ {taskItems.slice(0, 8).map((task) => (
+
+
+
+
{task.task_title}
+
+ {humanize(task.task_type)} · due {formatDate(task.due_at)}
+
+
+
+ {humanize(task.priority)}
+
+
+
{task.description}
+
Estimated cost: {formatCurrency(task.estimated_cost)}
+
+ ))}
+
+
+
+
+ {fundingItems.map((round) => (
+
+
+
+
{round.round_name}
+
{humanize(round.funding_type)} · {formatDate(round.open_at)} → {formatDate(round.close_at)}
+
+
+ {humanize(round.status)}
+
+
+
{round.notes}
+
Target amount: {formatCurrency(round.target_amount)}
+
+ ))}
+ {propertyItems.map((property) => (
+
+
+
+
{property.property_name}
+
+ {humanize(property.property_type)} · {humanize(property.acquisition_status)}
+
+
+
Lot size: {formatNumber(property.lot_size_acres)} acres
+
+
{property.notes}
+
Asking price target: {formatCurrency(property.asking_price)}
+
+ ))}
+
+
+
+
+
+ {legalItems.map((requirement) => (
+
+
+
+
{requirement.requirement_title}
+
+ {humanize(requirement.jurisdiction_level)} · {humanize(requirement.requirement_type)}
+
+
+
+ {humanize(requirement.status)}
+
+
+
{requirement.details}
+
+ Authority: {requirement.authority_name || 'To verify'} · Est. fees {formatCurrency(requirement.estimated_fees)}
+
+
+ ))}
+
+ {documentItems.map((document) => (
+
+
{document.document_title}
+
{humanize(document.document_type)}
+
{document.content}
+
+ ))}
+
+
+
+
+
+
+ {staffingItems.map((position) => (
+
+
+
+
{position.position_title}
+
{humanize(position.employment_type)} · starts {formatDate(position.target_start_at)}
+
+
+ {humanize(position.status)}
+
+
+
{position.job_description}
+
+ Salary range: {formatCurrency(position.salary_min)} to {formatCurrency(position.salary_max)}
+
+
+ ))}
+
+ {trainingItems.map((program) => (
+
+
{program.program_title}
+
{humanize(program.audience)}
+
{program.overview}
+
+ ))}
+ {marketingItems.map((campaign) => (
+
+
{campaign.campaign_name}
+
{humanize(campaign.channel)}
+
{campaign.objective}
+
Budget: {formatCurrency(campaign.budget)}
+
+ ))}
+
+
+
+
+
+
+ {designItems.map((asset) => (
+
+
+
+
{asset.asset_name}
+
{humanize(asset.asset_type)} · {humanize(asset.format)}
+
+
+
+
+
+
{asset.description}
+
+ ))}
+
+
+
+
+ )}
+
+ {result && (
+
+
+
+
+
Live market scan
+
Search live U.S. property listings
+
+ This first live search version lets you explore real sale listings from inside Launchpad. It works best for land and other
+ supported U.S. listing types while you compare possible places to build your idea.
+
+
+
+
Starting area
+
{formatLocationSummary(result.location)}
+
U.S. live listings only for now
+
+
+
+
+
+ setLivePropertySearch((current) => ({ ...current, city: event.target.value }))}
+ placeholder="Austin"
+ />
+
+
+ setLivePropertySearch((current) => ({ ...current, state: event.target.value }))}
+ placeholder="TX"
+ />
+
+
+ setLivePropertySearch((current) => ({ ...current, propertyType: event.target.value }))}
+ >
+ {livePropertyTypeOptions.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+
+ setLivePropertySearch((current) => ({ ...current, maxPrice: event.target.value }))}
+ placeholder="750000"
+ inputMode="numeric"
+ />
+
+
+ setLivePropertySearch((current) => ({ ...current, minLotAcres: event.target.value }))}
+ placeholder="3"
+ inputMode="decimal"
+ />
+
+
+
+
+
+
+ Tip: start with just a state if you want a wider market view, then narrow into cities that look promising.
+
+
+
+ {livePropertySearchError && (
+
+
+ {livePropertySearchError}
+
+
+ )}
+
+ {livePropertySearchLoading && (
+
+ Searching live property listings...
+
+ )}
+
+ {hasAttemptedLivePropertySearch && !livePropertySearchLoading && !livePropertySearchError && livePropertyResults.length === 0 && (
+
+ No live listings matched that search yet. Try widening the state search, removing a filter, or switching the listing type back to
+ "Any supported listing type."
+
+ )}
+
+ {!livePropertySearchLoading && livePropertyResults.length > 0 && (
+
+
+
+
Live results
+
+ Showing {livePropertyResults.length} live listing candidates{livePropertySearchMeta?.fetchedAt ? ` • refreshed ${formatDate(livePropertySearchMeta.fetchedAt)}` : ''}
+
+
+
+ Search filters: {livePropertySearch.city ? `${livePropertySearch.city}, ` : ''}{livePropertySearch.state || 'State needed'}
+
+
+
+
+ {livePropertyResults.map((listing) => (
+
+ {listing.photoUrl && (
+
+ )}
+
+
+
+
{listing.address || 'Property listing'}
+
+ {listing.city && listing.state ? `${listing.city}, ${listing.state}` : listing.state || 'U.S. listing'}
+
+
+
+ {humanize(listing.status || 'active')}
+
+
+
+
+
+
Price
+
{formatCurrency(listing.price)}
+
+
+
Type
+
{listing.propertyType || 'Property'}
+
+
+
Lot size
+
{formatNumber(listing.lotSizeAcres)} acres
+
+
+
Interior
+
+ {listing.squareFootage ? `${formatNumber(listing.squareFootage)} sqft` : 'Not listed'}
+
+
+
+
Beds / baths
+
+ {listing.bedrooms || '—'} / {listing.bathrooms || '—'}
+
+
+
+
Days on market
+
{formatNumber(listing.daysOnMarket)}
+
+
+
+ {listing.description && (
+
{listing.description}
+ )}
+
+ {listing.listingUrl && (
+
+ Open listing
+
+ )}
+
+
+ ))}
+
+
+ )}
+
+
+ )}
+
+
+
+
+
+
Workspace library
+
Recent launch blueprints
+
+ These are existing project workspaces in your admin app. Use them as saved launch programs you can refine with the generated CRUD
+ screens already in the platform.
+
+
+ {canReadProjects &&
}
+
+
+ {!canReadProjects && (
+
+ Your role can create new launch plans here, but it does not currently have permission to browse the full projects library.
+
+ )}
+
+ {canReadProjects && recentPlansError && (
+
+
+ {recentPlansError}
+
+
+ )}
+
+ {canReadProjects && !recentPlansError && (
+
+ {!recentPlansLoading && recentPlans.length === 0 && (
+
+
+
+
+
No saved launch plans yet
+
Generate your first blueprint above and it will appear here automatically.
+
+ )}
+
+ {recentPlansLoading && (
+
+ Loading recent plans...
+
+ )}
+
+ {!recentPlansLoading && recentPlans.map((plan) => (
+
+
+
{humanize(plan.stage)}
+
+ Open
+
+
+
{plan.project_name}
+
{plan.vision || 'Launch blueprint workspace ready for refinement.'}
+
+
+ Budget: {formatCurrency(plan.target_budget)}
+
+
+ Target open: {formatDate(plan.target_open_date)}
+
+ {plan.primary_location && (
+
+ Search area: {formatLocationSummary(plan.primary_location)}
+
+ )}
+ {plan.business_idea?.idea_title && (
+
+ Idea: {plan.business_idea.idea_title}
+
+ )}
+
+
+ ))}
+
+ )}
+
+
+
+ >
+ );
+};
+
+LegacyLaunchpad.getLayout = function getLayout(page: ReactElement) {
+ return {page} ;
+};
+
+export default LegacyLaunchpad;
diff --git a/frontend/src/pages/search.tsx b/frontend/src/pages/search.tsx
index 00f5168..005eb07 100644
--- a/frontend/src/pages/search.tsx
+++ b/frontend/src/pages/search.tsx
@@ -1,9 +1,7 @@
import React, { ReactElement, useEffect, useState } from 'react';
import Head from 'next/head';
import 'react-datepicker/dist/react-datepicker.css';
-import { useAppDispatch } from '../stores/hooks';
-
-import { useAppSelector } from '../stores/hooks';
+import { useAppDispatch, useAppSelector } from '../stores/hooks';
import { useRouter } from 'next/router';
import LayoutAuthenticated from '../layouts/Authenticated';