feat: Initialize backend with Express and MySQL, and restructure frontend with new routing and language support.

This commit is contained in:
Van Leemput Dayron
2025-12-15 17:03:03 +01:00
parent 56897a0c2d
commit 6c11cf5213
54 changed files with 2000 additions and 174 deletions

View File

@@ -0,0 +1,271 @@
import { useEffect } from 'react';
import { motion } from 'framer-motion';
import { useLanguage } from '../contexts/LanguageContext';
import { Link, Outlet } from 'react-router-dom';
import { Shield, Smartphone, Map, DollarSign, Users, Globe, Code } from 'lucide-react';
import appIcon from '../assets/app_icon.png';
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
};
const FeatureCard = ({ title, icon: Icon, description }: { title: string, icon: any, description: string }) => (
<motion.div
variants={itemVariants}
className="feature-card"
style={{
background: 'var(--card-bg, rgba(255, 255, 255, 0.03))', // Slightly more transparent for glass effect
backdropFilter: 'blur(10px)', // Glassmorphism
padding: '2rem',
borderRadius: '1.5rem',
border: '1px solid var(--border-color, rgba(255, 255, 255, 0.08))',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center', // Center items horizontally
textAlign: 'center', // Center text
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)'
}}
whileHover={{
y: -5,
boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
borderColor: 'var(--primary-color, #4f46e5)'
}}
>
<div style={{
marginBottom: '1.5rem',
background: 'var(--primary-color-alpha, rgba(79, 70, 229, 0.1))',
width: 'fit-content',
padding: '12px',
borderRadius: '12px'
}}>
<Icon size={32} color="var(--primary-color)" />
</div>
<h3 style={{
marginBottom: '1rem',
fontSize: '1.5rem',
fontWeight: '600',
color: 'var(--text-color)' // Explicit color for dark mode
}}>
{title}
</h3>
<p style={{
opacity: 0.8,
lineHeight: '1.7',
flex: 1,
fontSize: '1rem',
color: 'var(--text-color)' // Explicit color
}}>
{description}
</p>
</motion.div>
);
const TravelMate = () => {
const { t, language } = useLanguage();
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2
}
}
};
useEffect(() => {
document.title = "Travel Mate";
}, []);
return (
<div className="travel-mate-page" style={{ paddingTop: '100px', minHeight: '100vh', paddingBottom: '50px' }}>
<div className="container" style={{ maxWidth: '1400px', margin: '0 auto', padding: '0 20px' }}>
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
>
{/* Header Section */}
<motion.div variants={itemVariants} style={{ textAlign: 'center', marginBottom: '4rem', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<motion.img
src={appIcon}
alt="Travel Mate Icon"
style={{ width: '120px', height: '120px', borderRadius: '24px', marginBottom: '2rem', boxShadow: '0 10px 30px rgba(0,0,0,0.2)' }}
whileHover={{ scale: 1.05, rotate: 5 }}
transition={{ type: "spring", stiffness: 300 }}
/>
<h1 className="gradient-text" style={{ fontSize: '4rem', fontWeight: '800', marginBottom: '1rem' }}>
{t('travelmate.page.mainTitle')}
</h1>
<p style={{ fontSize: '1.5rem', opacity: 0.7, maxWidth: '600px', margin: '0 auto 2rem auto' }}>
{t('travelmate.page.subtitle')}
</p>
{/* View Code Button */}
<motion.a
href="https://git.xeewy.be/Xeewy/TravelMate"
target="_blank"
rel="noopener noreferrer"
className="btn btn-primary"
style={{
display: 'inline-flex',
alignItems: 'center',
gap: '10px',
textDecoration: 'none',
fontSize: '1.1rem',
padding: '0.8rem 1.5rem',
borderRadius: '50px'
}}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Code size={20} />
{t('travelmate.viewCode') || "Voir le code"}
</motion.a>
</motion.div>
{/* Description as Intro */}
<motion.div variants={itemVariants} style={{ marginBottom: '5rem', maxWidth: '800px', margin: '0 auto 5rem auto', textAlign: 'center' }}>
<p style={{
lineHeight: '1.8',
fontSize: '1.4rem',
opacity: 0.9,
fontStyle: 'italic',
color: 'var(--text-color)'
}}>
"{t('travelmate.page.intro')}"
</p>
</motion.div>
{/* Highlights Sections */}
<motion.div variants={itemVariants} style={{ marginBottom: '6rem' }}>
<h2 style={{
textAlign: 'center',
marginBottom: '4rem',
fontSize: '2.5rem',
fontWeight: '700',
color: 'var(--text-color)'
}}>{t('travelmate.highlights.title')}</h2>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '2.5rem' }}>
{[1, 2, 3, 4].map((num) => (
<FeatureCard
key={num}
title={t(`travelmate.highlight.${num}.title`)}
description={t(`travelmate.highlight.${num}.desc`)}
icon={
num === 1 ? Users :
num === 2 ? DollarSign :
num === 3 ? Map :
Smartphone
}
/>
))}
</div>
</motion.div>
{/* Conclusion */}
<motion.div variants={itemVariants} style={{ marginBottom: '6rem', textAlign: 'center', maxWidth: '800px', margin: '0 auto 6rem auto' }}>
<p style={{
fontSize: '1.8rem',
fontWeight: 'bold',
color: 'var(--primary-color)',
lineHeight: '1.4'
}}>
{t('travelmate.page.conclusion')}
</p>
</motion.div>
{/* Tech Stack */}
<motion.div variants={itemVariants} style={{ marginBottom: '5rem' }}>
<h2 style={{ textAlign: 'center', marginBottom: '3rem' }}>{t('travelmate.tech.title')}</h2>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '2rem' }}>
{/* Frontend */}
<div style={{ padding: '1.5rem' }}>
<h3 style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '1.5rem', color: 'var(--primary-color)' }}>
<Smartphone /> {t('travelmate.tech.frontend')}
</h3>
<ul style={{ listStyle: 'none', padding: 0 }}>
{[1, 2, 3].map(i => (
<li key={i} style={{ marginBottom: '0.8rem', paddingLeft: '1rem', borderLeft: '2px solid var(--primary-color)' }}>
{t(`travelmate.tech.frontend.${i}`)}
</li>
))}
</ul>
</div>
{/* Backend */}
<div style={{ padding: '1.5rem' }}>
<h3 style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '1.5rem', color: 'var(--primary-color)' }}>
<Globe /> {t('travelmate.tech.backend')}
</h3>
<ul style={{ listStyle: 'none', padding: 0 }}>
{[1, 2, 3, 4].map(i => (
<li key={i} style={{ marginBottom: '0.8rem', paddingLeft: '1rem', borderLeft: '2px solid var(--primary-color)' }}>
{t(`travelmate.tech.backend.${i}`)}
</li>
))}
</ul>
</div>
{/* API */}
<div style={{ padding: '1.5rem' }}>
<h3 style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '1.5rem', color: 'var(--primary-color)' }}>
<Code /> {t('travelmate.tech.api')}
</h3>
<ul style={{ listStyle: 'none', padding: 0 }}>
{[1, 2, 3].map(i => (
<li key={i} style={{ marginBottom: '0.8rem', paddingLeft: '1rem', borderLeft: '2px solid var(--primary-color)' }}>
{t(`travelmate.tech.api.${i}`)}
</li>
))}
</ul>
</div>
</div>
</motion.div>
{/* Policies CTA */}
<motion.div
variants={itemVariants}
style={{
padding: '2rem',
background: 'var(--card-bg, rgba(255, 255, 255, 0.05))',
borderRadius: '1rem',
border: '1px solid var(--border-color, rgba(255, 255, 255, 0.1))',
textAlign: 'center',
maxWidth: '600px',
margin: '0 auto'
}}
>
<Shield className="text-primary" size={48} style={{ marginBottom: '1rem', color: 'var(--primary-color)' }} />
<h3 style={{ marginBottom: '1.5rem' }}>
{t('policies.title')}
</h3>
<Link
to={`/${language}/travelmate/policies`}
className="btn btn-secondary"
style={{
display: 'inline-flex',
alignItems: 'center',
gap: '10px',
textDecoration: 'none'
}}
>
{t('travelmate.policies.link')}
</Link>
</motion.div>
</motion.div>
<Outlet />
</div>
</div>
);
};
export default TravelMate;