turash/bugulma/frontend/components/landing/SymbiosisDemo.tsx
Damir Mukimov 6347f42e20
Consolidate repositories: Remove nested frontend .git and merge into main repository
- Remove nested git repository from bugulma/frontend/.git
- Add all frontend files to main repository tracking
- Convert from separate frontend/backend repos to unified monorepo
- Preserve all frontend code and development history as tracked files
- Eliminate nested repository complexity for simpler development workflow

This creates a proper monorepo structure with frontend and backend
coexisting in the same repository for easier development and deployment.
2025-11-25 06:02:57 +01:00

176 lines
6.0 KiB
TypeScript

import { AnimatePresence, motion } from 'framer-motion';
import React, { useCallback, useMemo, useState } from 'react';
import { symbiosisExamples } from '@/data/symbiosisExamples.ts';
import { useTranslation } from '@/hooks/useI18n.tsx';
import SectionHeader from '@/components/layout/SectionHeader.tsx';
import Button from '@/components/ui/Button.tsx';
import { Container, Flex, Grid } from '@/components/ui/layout';
import Select from '@/components/ui/Select.tsx';
import DemoCard from '@/components/landing/DemoCard.tsx';
interface SymbiosisDemoProps {
onNavigateToMap: (page: 'map') => void;
}
const SymbiosisDemo = ({ onNavigateToMap }: SymbiosisDemoProps) => {
const { t } = useTranslation();
// State now holds only the key of the selected offer.
const [selectedOfferKey, setSelectedOfferKey] = useState(symbiosisExamples[0].offer.key);
// Derive the selected offer object from the key using useMemo for performance.
const selectedOffer = useMemo(
() => symbiosisExamples.find((ex) => ex.offer.key === selectedOfferKey) || null,
[selectedOfferKey]
);
// Derive the available needs based on the selected offer.
const availableNeeds = useMemo(() => selectedOffer?.needs || [], [selectedOffer]);
// Initialize selected need key based on available needs
const initialSelectedNeedKey = availableNeeds[0]?.key || '';
const [selectedNeedKey, setSelectedNeedKey] = useState(initialSelectedNeedKey);
// Derive the selected need object from its key.
const selectedNeed = useMemo(
() => availableNeeds.find((n) => n.key === selectedNeedKey) || null,
[availableNeeds, selectedNeedKey]
);
// Event handlers now only update the keys.
const handleOfferChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedOfferKey(e.target.value);
}, []);
const handleNeedChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedNeedKey(e.target.value);
}, []);
const translatedSectors = useMemo(
() => ({
production: t('sectors.production'),
construction: t('sectors.construction'),
recreation: t('sectors.recreation'),
logistics: t('sectors.logistics'),
}),
[t]
);
return (
<section
className="bg-background min-h-screen flex items-center scroll-snap-align-start"
aria-labelledby="symbiosis-demo-title"
>
<Container className="py-16 sm:py-24 w-full">
<SectionHeader
id="symbiosis-demo-title"
title={t('symbiosisDemo.title')}
subtitle={t('symbiosisDemo.subtitle')}
className="mb-10 sm:mb-12"
/>
<Grid cols={{ md: 2 }} gap="md" className="mb-12">
<div>
<label htmlFor="offer-select" className="block text-sm font-medium mb-1">
{t('symbiosisDemo.offerLabel')}
</label>
<Select id="offer-select" value={selectedOfferKey} onChange={handleOfferChange}>
{symbiosisExamples.map((ex) => (
<option key={ex.offer.key} value={ex.offer.key}>
{t(ex.offer.nameKey)}
</option>
))}
</Select>
</div>
<div>
<label htmlFor="need-select" className="block text-sm font-medium mb-1">
{t('symbiosisDemo.needLabel')}
</label>
<Select
id="need-select"
value={selectedNeedKey}
onChange={handleNeedChange}
disabled={!selectedOffer}
>
{availableNeeds.map((need) => (
<option key={need.key} value={need.key}>
{t(need.nameKey)}
</option>
))}
</Select>
</div>
</Grid>
<Flex
direction="col"
align="center"
justify="between"
gap="2xl"
className="relative mt-16 sm:flex-row sm:gap-0"
>
{selectedOffer && (
<DemoCard
icon={selectedOffer.offer.icon}
sector={translatedSectors[selectedOffer.offer.sector]}
item={t(selectedOffer.offer.nameKey)}
type="Offer"
/>
)}
<div className="w-full sm:w-2/12 h-16 sm:h-auto flex items-center justify-center">
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
<motion.path
key={`${selectedOffer?.offer.key}-${selectedNeed?.key}`}
d="M 5 50 Q 50 20 95 50"
stroke="hsl(var(--primary))"
strokeWidth="2"
fill="none"
strokeDasharray="4 4"
initial={{ pathLength: 0, opacity: 0 }}
animate={{ pathLength: 1, opacity: 1 }}
transition={{ duration: 0.8, ease: 'easeInOut' }}
/>
</svg>
</div>
{selectedNeed && (
<DemoCard
icon={selectedNeed.icon}
sector={translatedSectors[selectedNeed.sector]}
item={t(selectedNeed.nameKey)}
type="Need"
/>
)}
</Flex>
<AnimatePresence mode="wait">
<motion.div
key={selectedNeed?.key}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3 }}
className="mt-12 text-center max-w-2xl mx-auto p-4 bg-muted rounded-lg"
aria-live="polite"
>
<p className="text-lg text-foreground">
{selectedNeed ? t(selectedNeed.descriptionKey) : ' '}
</p>
</motion.div>
</AnimatePresence>
<div className="text-center mt-16">
<Button
size="lg"
onClick={() => onNavigateToMap('map')}
className="shadow-lg shadow-primary/20 hover:shadow-xl hover:shadow-primary/30"
>
{t('symbiosisDemo.ctaButton')}
</Button>
</div>
</Container>
</section>
);
};
export default React.memo(SymbiosisDemo);