From a1cd43641c66117e4047737ea89525a4be43761c Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 28 Jul 2026 20:58:59 +0000 Subject: [PATCH] Ilha Marketing --- frontend/src/components/NavBarItem.tsx | 3 +- frontend/src/layouts/Authenticated.tsx | 3 +- frontend/src/pages/index.tsx | 665 +++++++++++++++++++------ 3 files changed, 525 insertions(+), 146 deletions(-) diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index 55559d2..93ddba0 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 b93241b..7858368 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,547 @@ - -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 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 { getPageTitle } from '../config'; -import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +type MarketPlan = { + id: string; + marketName: string; + deliveryFocus: string; + expectedCustomers: number; + weather: string; + tourism: string; + createdAt: string; + checklist: string[]; + featuredProducts: string[]; + revenueEstimate: number; +}; -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('video'); - const [contentPosition, setContentPosition] = useState('left'); - const textColor = useAppSelector((state) => state.style.linkColor); +type PlanForm = { + marketName: string; + deliveryFocus: string; + expectedCustomers: string; + weather: string; + tourism: string; +}; - const title = 'Ilha Market' +const storageKey = 'ilha-market-day-plans'; - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); +const featuredByFocus: Record = { + tropical: ['Coco gelado', 'Açaí da ilha', 'Banana-prata', 'Suco de caju'], + bakery: ['Pão francês', 'Bolo de fubá', 'Pão de queijo', 'Café coado'], + fishing: ['Tilápia fresca', 'Camarão', 'Isca artesanal', 'Gelo para pesca'], + farming: ['Mandioca', 'Couve', 'Tomate cereja', 'Flores tropicais'], +}; - const imageBlock = (image) => ( -
-
- - Photo by {image?.photographer} on Pexels - -
-
- ); +const focusLabels: Record = { + tropical: 'Tropical essentials', + bakery: 'Bakery morning rush', + fishing: 'Fishing dock supply', + farming: 'Farm-to-market harvest', +}; - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- -
- - Video by {video.user.name} on Pexels - -
-
) - } +const weatherLabels: Record = { + sunny: 'Sunny beach day', + rainy: 'Warm tropical rain', + storm: 'Storm watch', + fog: 'Foggy mountain morning', +}; + +const tourismLabels: Record = { + low: 'Local residents only', + normal: 'Balanced village day', + high: 'Cruise/tourist surge', +}; + +const currencyFormatter = new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL', +}); + +const dateFormatter = new Intl.DateTimeFormat('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', +}); + +function buildChecklist(form: PlanForm, customers: number) { + const base = [ + `Receive ${focusLabels[form.deliveryFocus].toLowerCase()} delivery at Porto da Esperança.`, + 'Restock hero shelf, cold aisle, and checkout impulse basket before opening.', + 'Assign one employee to PIX/card checkout and one to shelf recovery.', + ]; + + if (form.weather === 'storm') { + base.push('Move umbrellas, candles, batteries, and bottled water to the front display.'); + } + + if (form.weather === 'sunny') { + base.push('Boost cold drinks, beach snacks, sunscreen, and fresh fruit near the entrance.'); + } + + if (form.tourism === 'high') { + base.push('Prepare bilingual welcome signs and souvenir bundles for tourists.'); + } + + if (customers > 80) { + base.push('Schedule an afternoon micro-delivery so shelves do not empty after lunch.'); + } + + return base; +} + +function calculateRevenue(form: PlanForm, customers: number) { + const focusMultiplier = form.deliveryFocus === 'bakery' ? 31 : form.deliveryFocus === 'fishing' ? 44 : form.deliveryFocus === 'farming' ? 35 : 38; + const tourismMultiplier = form.tourism === 'high' ? 1.34 : form.tourism === 'low' ? 0.82 : 1; + const weatherMultiplier = form.weather === 'storm' ? 0.72 : form.weather === 'rainy' ? 0.9 : form.weather === 'sunny' ? 1.18 : 0.96; + + return Math.round(customers * focusMultiplier * tourismMultiplier * weatherMultiplier); +} + +function createPlan(form: PlanForm): MarketPlan { + const expectedCustomers = Number(form.expectedCustomers); + + return { + id: `${Date.now()}`, + marketName: form.marketName.trim(), + deliveryFocus: form.deliveryFocus, + expectedCustomers, + weather: form.weather, + tourism: form.tourism, + createdAt: new Date().toISOString(), + checklist: buildChecklist(form, expectedCustomers), + featuredProducts: featuredByFocus[form.deliveryFocus], + revenueEstimate: calculateRevenue(form, expectedCustomers), + }; +} + +export default function IlhaMarketLanding() { + const [form, setForm] = useState({ + marketName: 'Ilha Market', + deliveryFocus: 'tropical', + expectedCustomers: '64', + weather: 'sunny', + tourism: 'normal', + }); + const [plans, setPlans] = useState([]); + const [selectedPlanId, setSelectedPlanId] = useState(null); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(''); + + useEffect(() => { + try { + const savedPlans = window.localStorage.getItem(storageKey); + if (savedPlans) { + const parsedPlans = JSON.parse(savedPlans) as MarketPlan[]; + setPlans(parsedPlans); + setSelectedPlanId(parsedPlans[0]?.id ?? null); + } + } catch (err) { + console.error('Failed to load Ilha Market plans from localStorage:', err); + } + }, []); + + useEffect(() => { + try { + window.localStorage.setItem(storageKey, JSON.stringify(plans)); + } catch (err) { + console.error('Failed to save Ilha Market plans to localStorage:', err); + } + }, [plans]); + + const draftPlan = useMemo(() => { + const customers = Number(form.expectedCustomers) || 0; + + return { + checklist: buildChecklist(form, customers), + featuredProducts: featuredByFocus[form.deliveryFocus], + revenueEstimate: calculateRevenue(form, customers), }; + }, [form]); + + const selectedPlan = plans.find((plan) => plan.id === selectedPlanId) ?? plans[0] ?? null; + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + setError(''); + setSuccess(''); + + const expectedCustomers = Number(form.expectedCustomers); + + if (form.marketName.trim().length < 3) { + setError('Name the market with at least 3 characters.'); + return; + } + + if (!Number.isFinite(expectedCustomers) || expectedCustomers < 10 || expectedCustomers > 250) { + setError('Expected customers must be between 10 and 250 for this playable slice.'); + return; + } + + const nextPlan = createPlan(form); + setPlans((currentPlans) => [nextPlan, ...currentPlans].slice(0, 6)); + setSelectedPlanId(nextPlan.id); + setSuccess(`${nextPlan.marketName} is ready to open with ${nextPlan.featuredProducts.length} featured product groups.`); + }; + + const removePlan = (planId: string) => { + setPlans((currentPlans) => { + const nextPlans = currentPlans.filter((plan) => plan.id !== planId); + setSelectedPlanId(nextPlans[0]?.id ?? null); + return nextPlans; + }); + setSuccess('Market day removed from the planner.'); + }; return ( -
+ <> - {getPageTitle('Starter Page')} + {getPageTitle('Ilha Market')} + - -
- {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

+
+
+
+
+
+ + - - -
-
- -
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
+
+
+
+ + Cozy Rio-coast life sim + supermarket management +
+

+ Reopen the island’s only market and bring the community back to life. +

+

+ Plan delivery days, stock tropical products, prepare PIX/card checkout, and build the first playable management loop for Ilha Market™ — a warm, stylized Brazilian island game concept. +

+
+ + Start a market day + + + Open admin interface + +
-
+
+ {[ + ['9+', 'Island regions'], + ['4', 'Payment modes'], + ['24h', 'Living routines'], + ].map(([value, label]) => ( +
+
{value}
+
{label}
+
+ ))} +
+
+ +
+
+
+
+
+
+
+
+ +
+
ILHA MARKET
+
+
+
+
+
+
+
+
+
+
+
+
+ PIX • Credit • Cash + Open 08:00 +
+
+ +
🧺
+
🐟
+
🌴
+
🌺
+
+
+
+
+
+
+ +
+
+ {[ + ['Explore', 'Village, beach, forest, mangrove, farm, harbor, lookout, caves.'], + ['Manage', 'Deliveries, shelves, pricing, staff, reports, PIX and card checkout.'], + ['Connect', 'NPC routines, families, friendships, quests, festivals and reputation.'], + ['Grow', 'Farm, fish, craft, restore buildings, upgrade tourism and the harbor.'], + ].map(([title, text]) => ( +
+

{title}

+

{text}

+
+ ))} +
+
+ +
+
+
+

Playable MVP slice

+

Market Day Planner

+

+ Create a daily operating plan for Dona Percília’s reopened supermarket. The planner validates inputs, generates a stocking checklist, saves recent plans in this browser, and lets you inspect or remove each plan. +

+
+ +
+
+
+
+

Plan opening day

+

Tune demand, weather, and delivery focus.

+
+ Live +
+ +
+ + +
+ + + +
+ +
+ + + +
+
+ + {error &&

{error}

} + {success &&

{success}

} + + +
+ +
+
+
+
+

Live forecast

+

Preview before saving.

+
+ + {currencyFormatter.format(draftPlan.revenueEstimate)} + +
+ +
+ {draftPlan.featuredProducts.map((product) => ( + {product} + ))} +
+ +
    + {draftPlan.checklist.map((item, index) => ( +
  1. + {index + 1} + {item} +
  2. + ))} +
+
+ +
+
+
+

Saved market days

+

Recent plans are stored locally for this MVP.

+
+ {plans.length} +
+ + {plans.length === 0 ? ( +
+
🛒
+

No saved market days yet.

+

Generate a plan to create the first playable management checkpoint.

+
+ ) : ( +
+ {plans.map((plan) => ( + + ))} +
+ )} +
+
+
+ +
+ {selectedPlan ? ( +
+
+

Selected plan detail

+

{selectedPlan.marketName}

+

+ Created {dateFormatter.format(new Date(selectedPlan.createdAt))}. Prepare for {tourismLabels[selectedPlan.tourism].toLowerCase()} with {weatherLabels[selectedPlan.weather].toLowerCase()}. +

+
+
+

Customers

+

{selectedPlan.expectedCustomers}

+
+
+

Forecast

+

{currencyFormatter.format(selectedPlan.revenueEstimate)}

+
+
+ +
+
+
+

Featured shelf set

+
+ {selectedPlan.featuredProducts.map((product) => ( +
{product}
+ ))} +
+
+
+

Opening checklist

+
    + {selectedPlan.checklist.map((item) => ( +
  • + + {item} +
  • + ))} +
+
+
+
+ ) : ( +
+
🌅
+

Select or create a plan to inspect the playable detail view.

+

This is the first working loop for the Ilha Market management game.

+
+ )} +
+
+
+
+ ); } -Starter.getLayout = function getLayout(page: ReactElement) { +IlhaMarketLanding.getLayout = function getLayout(page: ReactElement) { return {page}; }; -