FlowWeb / src /layouts /Layout.jsx
danylokhodus's picture
feat: rebrand QuestboardAI -> TaskFlow everywhere (UI, locales, index.html)
abb5ee1
Raw
History Blame Contribute Delete
8.68 kB
import React from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Link, useLocation } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import logo from '../assets/logo.png';
const Layout = ({ children }) => {
const { user } = useAuth();
const location = useLocation();
const isLanding = location.pathname === '/';
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
const navItems = [
{ name: 'Product', href: '#ai-agent' },
{ name: 'How it Flows', href: '#how-it-works' },
{ name: 'Templates', href: '#templates' },
{ name: 'Pricing', href: '#pricing' }
];
const [activeSection, setActiveSection] = React.useState('#ai-agent');
React.useEffect(() => {
if (!isLanding) return;
const timer = setTimeout(() => {
const observerOptions = {
root: null,
rootMargin: '-30% 0px -60% 0px',
threshold: 0
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
if (entry.target.id === 'hero') {
setActiveSection('#ai-agent');
} else {
setActiveSection(`#${entry.target.id}`);
}
}
});
}, observerOptions);
const sectionsToObserve = ['hero', 'ai-agent', 'how-it-works', 'templates', 'pricing'];
sectionsToObserve.forEach(id => {
const el = document.getElementById(id);
if (el) observer.observe(el);
});
return () => observer.disconnect();
}, 100);
return () => clearTimeout(timer);
}, [isLanding]);
return (
<div className="flex flex-col min-h-screen text-on-background">
{/* TopNavBar */}
<nav className="bg-surface-container-lowest border-b-2 border-primary shadow-[6px_6px_0px_0px_var(--color-primary)] dark:shadow-[6px_6px_0px_0px_rgba(255,255,255,0.07)] sticky top-0 z-50">
<div className="flex justify-between items-center w-full px-margin_edge py-5 max-w-screen-2xl mx-auto">
<Link to="/">
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
className="font-display-lg text-4xl text-primary tracking-tight cursor-pointer flex items-center gap-3"
>
<img src={logo} alt="TaskFlow Logo" className="w-10 h-10 object-contain" />
<span>TaskFlow</span>
</motion.div>
</Link>
<div className="hidden md:flex gap-gutter items-center">
{navItems.map((item, i) => {
const isActive = activeSection === item.href;
return (
<motion.a
key={item.name}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 * i }}
className={`text-xl transition-all relative py-2 ${
isActive
? 'text-primary font-bold border-b-2 border-secondary'
: 'text-on-surface-variant hover:text-primary'
}`}
href={isLanding ? item.href : `/${item.href}`}
>
{item.name}
</motion.a>
);
})}
</div>
<div className="flex gap-6 items-center">
<div className="hidden sm:flex gap-6 items-center">
{user ? (
<Link to="/dashboard">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="bg-primary text-on-primary font-bold py-3 px-6 rough-border rough-shadow-hover transition-all text-xl"
>
Go to Workspace
</motion.button>
</Link>
) : (
<>
<Link to="/auth">
<button className="text-xl font-medium text-on-surface-variant hover:text-primary transition-colors">Log In</button>
</Link>
<Link to="/auth">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="bg-primary text-on-primary font-bold py-3 px-6 rough-border rough-shadow-hover transition-all"
>
Start Sketching
</motion.button>
</Link>
</>
)}
</div>
{/* Mobile Menu Toggle */}
<button
className="md:hidden p-2 text-primary"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={isMobileMenuOpen ? "M6 18L18 6M6 6l12 12" : "M4 6h16M4 12h16M4 18h16"} />
</svg>
</button>
</div>
</div>
{/* Mobile Menu Overlay */}
<AnimatePresence>
{isMobileMenuOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="md:hidden bg-surface-container-lowest border-t-2 border-primary overflow-hidden"
>
<div className="flex flex-col p-margin_edge gap-6">
{navItems.map((item) => {
const isActive = activeSection === item.href;
return (
<a
key={item.name}
href={isLanding ? item.href : `/${item.href}`}
className={`text-2xl font-display-lg transition-colors ${
isActive ? 'text-primary font-bold' : 'text-on-surface-variant hover:text-primary'
}`}
onClick={() => setIsMobileMenuOpen(false)}
>
{item.name}
</a>
);
})}
<div className="flex flex-col gap-4 pt-6 border-t border-dashed border-primary/20">
{user ? (
<Link to="/dashboard" onClick={() => setIsMobileMenuOpen(false)}>
<button className="w-full py-4 text-2xl font-bold bg-primary text-on-primary rough-border rough-shadow">Go to Workspace</button>
</Link>
) : (
<>
<Link to="/auth" onClick={() => setIsMobileMenuOpen(false)}>
<button className="w-full py-4 text-2xl font-bold text-primary rough-border">Log In</button>
</Link>
<Link to="/auth" onClick={() => setIsMobileMenuOpen(false)}>
<button className="w-full py-4 text-2xl font-bold bg-primary text-on-primary rough-border rough-shadow">Start Sketching</button>
</Link>
</>
)}
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</nav>
{/* Page Content */}
<main className="flex-grow">
{children}
</main>
{/* Footer */}
<footer className="bg-surface-container-lowest w-full border-t-4 border-primary mt-20">
<div className="grid grid-cols-1 md:grid-cols-2 gap-gutter px-margin_edge py-16 w-full max-w-screen-2xl mx-auto">
<div className="flex flex-col gap-6">
<div className="text-5xl font-display-lg text-primary flex items-center gap-3">
<img src={logo} alt="TaskFlow Logo" className="w-12 h-12 object-contain" />
<span>TaskFlow</span>
</div>
<div className="text-xl text-on-surface-variant">
© 2026 TaskFlow. Designed for focus, built for resilience.
</div>
</div>
<div className="flex flex-wrap gap-10 items-center md:justify-end">
{['Privacy', 'Terms', 'Twitter', 'Changelog'].map(item => (
<a key={item} className="text-xl font-bold text-on-surface-variant hover:text-primary underline decoration-secondary decoration-4 transition-all" href="#">{item}</a>
))}
</div>
</div>
</footer>
</div>
);
};
export default Layout;