diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index 72935e6..fcbd9b9 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,6 +1,5 @@ -import React, {useEffect, useRef} from 'react' +import React, { useEffect, useRef, useState } from 'react' import Link from 'next/link' -import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..73d8391 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' +import React, { ReactNode, useEffect, useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index ed3bfe5..2da67cd 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,403 @@ - -import React, { useEffect, useState } from 'react'; -import type { ReactElement } from 'react'; +import React, { FormEvent, ReactElement, useEffect, useMemo, useState } from 'react'; import Head from 'next/head'; import Link from 'next/link'; +import { + mdiArrowRight, + mdiCheckCircle, + mdiClipboardTextOutline, + mdiRocketLaunchOutline, + mdiShieldCheckOutline, + mdiStarFourPoints, + mdiViewDashboardOutline, +} from '@mdi/js'; import BaseButton from '../components/BaseButton'; -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 BaseIcon from '../components/BaseIcon'; +import CardBox from '../components/CardBox'; +import LayoutGuest from '../layouts/Guest'; import { getPageTitle } from '../config'; -import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +type Brief = { + id: string; + idea: string; + stage: string; + priority: string; + createdAt: string; +}; + +const stageOptions = ['Idea', 'Prototype', 'Live product']; +const priorityOptions = ['Find users', 'Launch faster', 'Automate operations']; + +const workflowSteps = [ + 'Capture the product direction', + 'Confirm a focused MVP slice', + 'Track launch-ready tasks', + 'Continue in the admin workspace', +]; + +const features = [ + { + title: 'Domain workflow first', + text: 'Start with a visible customer journey instead of another generic CRUD table.', + }, + { + title: 'Built on your admin core', + text: 'Use auth, roles, search, tables, and data tools that already exist in this app.', + }, + { + title: 'Ready for iteration', + text: 'Turn the selected slice into real entities, API routes, and dashboards as requirements sharpen.', + }, +]; + +const makePlan = (brief: Brief | null) => { + const priority = brief?.priority || 'Launch faster'; + + if (priority === 'Find users') { + return [ + 'Publish a landing page with one clear audience and promise.', + 'Collect early adopter notes in the admin workspace.', + 'Review requests weekly and convert the strongest ones into features.', + ]; + } + + if (priority === 'Automate operations') { + return [ + 'Map the repeated manual task into a create → review → status workflow.', + 'Use role permissions to separate operators, managers, and admins.', + 'Add dashboard metrics once real workflow records are flowing.', + ]; + } + + return [ + 'Ship one end-to-end path before polishing every module.', + 'Reuse generated CRUD as the admin control room.', + 'Add only the missing business rules after the first workflow is validated.', + ]; +}; 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 [idea, setIdea] = useState('AI-powered client onboarding portal'); + const [stage, setStage] = useState(stageOptions[0]); + const [priority, setPriority] = useState(priorityOptions[1]); + const [briefs, setBriefs] = useState([]); + const [selectedBriefId, setSelectedBriefId] = useState(null); + const [error, setError] = useState(''); - const title = 'App Preview' + useEffect(() => { + try { + const storedBriefs = window.localStorage.getItem('flatlogic-mvp-briefs'); + if (storedBriefs) { + const parsedBriefs = JSON.parse(storedBriefs) as Brief[]; + setBriefs(parsedBriefs); + setSelectedBriefId(parsedBriefs[0]?.id || null); + } + } catch (localStorageError) { + console.error('Unable to load saved MVP briefs:', localStorageError); + } + }, []); - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); + useEffect(() => { + try { + window.localStorage.setItem('flatlogic-mvp-briefs', JSON.stringify(briefs)); + } catch (localStorageError) { + console.error('Unable to save MVP briefs:', localStorageError); + } + }, [briefs]); - const imageBlock = (image) => ( -
-
- - Photo by {image?.photographer} on Pexels - -
-
- ); + const selectedBrief = useMemo( + () => briefs.find((brief) => brief.id === selectedBriefId) || briefs[0] || null, + [briefs, selectedBriefId], + ); - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- -
- - Video by {video.user.name} on Pexels - -
-
) - } + const plan = useMemo(() => makePlan(selectedBrief), [selectedBrief]); + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + const trimmedIdea = idea.trim(); + + if (trimmedIdea.length < 8) { + setError('Describe the project in at least 8 characters so the MVP slice is meaningful.'); + return; + } + + const newBrief: Brief = { + id: `${Date.now()}`, + idea: trimmedIdea, + stage, + priority, + createdAt: new Date().toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }), }; + setBriefs((currentBriefs) => [newBrief, ...currentBriefs].slice(0, 4)); + setSelectedBriefId(newBrief.id); + setError(''); + }; + + const clearBriefs = () => { + setBriefs([]); + setSelectedBriefId(null); + setError(''); + }; + return ( -
+ <> - {getPageTitle('Starter Page')} + {getPageTitle('Launch Studio')} + - -
- {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

-
- - - - - -
+
+