feat: Add TravelMate project page and restructure site with routing, including new Home, Policies, and Footer components.
This commit is contained in:
60
package-lock.json
generated
60
package-lock.json
generated
@@ -12,7 +12,8 @@
|
|||||||
"framer-motion": "^12.23.24",
|
"framer-motion": "^12.23.24",
|
||||||
"lucide-react": "^0.553.0",
|
"lucide-react": "^0.553.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0"
|
"react-dom": "^19.2.0",
|
||||||
|
"react-router-dom": "^7.10.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
@@ -2308,6 +2309,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@@ -3362,6 +3376,44 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "7.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz",
|
||||||
|
"integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "^1.0.1",
|
||||||
|
"set-cookie-parser": "^2.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "7.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.10.1.tgz",
|
||||||
|
"integrity": "sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-router": "7.10.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||||
@@ -3501,6 +3553,12 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/set-cookie-parser": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
"framer-motion": "^12.23.24",
|
"framer-motion": "^12.23.24",
|
||||||
"lucide-react": "^0.553.0",
|
"lucide-react": "^0.553.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0"
|
"react-dom": "^19.2.0",
|
||||||
|
"react-router-dom": "^7.10.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
|
|||||||
30
src/App.tsx
30
src/App.tsx
@@ -1,12 +1,11 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||||
import { LanguageProvider } from './contexts/LanguageContext';
|
import { LanguageProvider } from './contexts/LanguageContext';
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import Hero from './components/Hero';
|
import Footer from './components/Footer';
|
||||||
import About from './components/About';
|
import Home from './components/Home';
|
||||||
import Skills from './components/Skills';
|
import TravelMate from './components/TravelMate';
|
||||||
import Projects from './components/Projects';
|
import Policies from './components/Policies';
|
||||||
import Education from './components/Education';
|
|
||||||
import Contact from './components/Contact';
|
|
||||||
import './styles/main.scss';
|
import './styles/main.scss';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -17,7 +16,6 @@ function App() {
|
|||||||
if (savedTheme) {
|
if (savedTheme) {
|
||||||
setDarkMode(JSON.parse(savedTheme));
|
setDarkMode(JSON.parse(savedTheme));
|
||||||
} else {
|
} else {
|
||||||
// Détection automatique du thème préféré de l'utilisateur
|
|
||||||
setDarkMode(window.matchMedia('(prefers-color-scheme: dark)').matches);
|
setDarkMode(window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
@@ -33,17 +31,19 @@ function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LanguageProvider>
|
<LanguageProvider>
|
||||||
<div className={`app ${darkMode ? 'dark' : 'light'}`}>
|
<BrowserRouter>
|
||||||
|
<div className={`app ${darkMode ? 'dark' : 'light'}`} style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
||||||
<Header darkMode={darkMode} toggleDarkMode={toggleDarkMode} />
|
<Header darkMode={darkMode} toggleDarkMode={toggleDarkMode} />
|
||||||
<main>
|
<main style={{ flex: 1 }}>
|
||||||
<Hero />
|
<Routes>
|
||||||
<About />
|
<Route path="/" element={<Home />} />
|
||||||
<Skills />
|
<Route path="/travelmate" element={<TravelMate />} />
|
||||||
<Projects />
|
<Route path="/travelmate/policies" element={<Policies />} />
|
||||||
<Education />
|
</Routes>
|
||||||
<Contact />
|
|
||||||
</main>
|
</main>
|
||||||
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
</BrowserRouter>
|
||||||
</LanguageProvider>
|
</LanguageProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/assets/app_icon.png
Normal file
BIN
src/assets/app_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 694 KiB |
@@ -105,7 +105,7 @@ const Contact = () => {
|
|||||||
{
|
{
|
||||||
icon: <Github size={24} />,
|
icon: <Github size={24} />,
|
||||||
name: "GitHub",
|
name: "GitHub",
|
||||||
url: "https://github.com/Dayron-HELHa", // Remplacez par votre profil
|
url: "https://git.xeewy.be/Xeewy", // Remplacez par votre profil
|
||||||
color: "#333"
|
color: "#333"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -373,18 +373,6 @@ const Contact = () => {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<motion.footer
|
|
||||||
className="contact-footer"
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
whileInView={{ opacity: 1 }}
|
|
||||||
transition={{ duration: 0.8, delay: 0.5 }}
|
|
||||||
viewport={{ once: true }}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
© 2025 Dayron Van Leemput.
|
|
||||||
</p>
|
|
||||||
</motion.footer>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
28
src/components/Footer.tsx
Normal file
28
src/components/Footer.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
const Footer = () => {
|
||||||
|
return (
|
||||||
|
<motion.footer
|
||||||
|
className="footer"
|
||||||
|
style={{
|
||||||
|
padding: '2rem 0',
|
||||||
|
textAlign: 'center',
|
||||||
|
borderTop: '1px solid var(--border-color, rgba(255, 255, 255, 0.1))',
|
||||||
|
background: 'var(--bg-secondary, rgba(0, 0, 0, 0.2))',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
whileInView={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.8, delay: 0.2 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
>
|
||||||
|
<p style={{ opacity: 0.7, margin: 0 }}>
|
||||||
|
© {new Date().getFullYear()} Dayron Van Leemput.
|
||||||
|
</p>
|
||||||
|
</motion.footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Menu, X, Sun, Moon, Globe } from 'lucide-react';
|
import { Menu, X, Sun, Moon, Globe } from 'lucide-react';
|
||||||
import { useLanguage } from '../contexts/LanguageContext';
|
import { useLanguage } from '../contexts/LanguageContext';
|
||||||
|
import { useNavigate, useLocation, Link } from 'react-router-dom';
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
darkMode: boolean;
|
darkMode: boolean;
|
||||||
@@ -11,6 +12,8 @@ interface HeaderProps {
|
|||||||
const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
|
const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const { language, setLanguage, t } = useLanguage();
|
const { language, setLanguage, t } = useLanguage();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ id: 'home', name: t('nav.home'), href: '#hero' },
|
{ id: 'home', name: t('nav.home'), href: '#hero' },
|
||||||
@@ -21,12 +24,26 @@ const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
|
|||||||
{ id: 'contact', name: t('nav.contact'), href: '#contact' }
|
{ id: 'contact', name: t('nav.contact'), href: '#contact' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const scrollToSection = (href: string) => {
|
const handleNavigation = (href: string) => {
|
||||||
|
setIsMenuOpen(false);
|
||||||
|
|
||||||
|
if (location.pathname === '/') {
|
||||||
|
// Already on home, just scroll
|
||||||
const element = document.querySelector(href);
|
const element = document.querySelector(href);
|
||||||
if (element) {
|
if (element) {
|
||||||
element.scrollIntoView({ behavior: 'smooth' });
|
element.scrollIntoView({ behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
setIsMenuOpen(false);
|
} else {
|
||||||
|
// Not on home, navigate then scroll (using a simple timeout for simplicity or hash)
|
||||||
|
navigate('/');
|
||||||
|
// Small timeout to allow navigation to render Home before scrolling
|
||||||
|
setTimeout(() => {
|
||||||
|
const element = document.querySelector(href);
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleLanguage = () => {
|
const toggleLanguage = () => {
|
||||||
@@ -46,9 +63,12 @@ const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
|
|||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
>
|
>
|
||||||
<a href="#hero" onClick={(e) => { e.preventDefault(); scrollToSection('#hero'); }}>
|
<Link to="/" onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleNavigation('#hero');
|
||||||
|
}}>
|
||||||
Dayron Van Leemput
|
Dayron Van Leemput
|
||||||
</a>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Navigation desktop */}
|
{/* Navigation desktop */}
|
||||||
@@ -64,7 +84,7 @@ const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
|
|||||||
href={item.href}
|
href={item.href}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
scrollToSection(item.href);
|
handleNavigation(item.href);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
@@ -123,7 +143,7 @@ const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
|
|||||||
href={item.href}
|
href={item.href}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
scrollToSection(item.href);
|
handleNavigation(item.href);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ const Hero = () => {
|
|||||||
transition={{ duration: 0.8, delay: 1.2 }}
|
transition={{ duration: 0.8, delay: 1.2 }}
|
||||||
>
|
>
|
||||||
<motion.a
|
<motion.a
|
||||||
href="https://github.com/Dayron-HELHa" // Remplacez par votre profil GitHub
|
href="https://git.xeewy.be/Xeewy" // Remplacez par votre profil GitHub
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="social-link"
|
className="social-link"
|
||||||
|
|||||||
21
src/components/Home.tsx
Normal file
21
src/components/Home.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import Hero from './Hero';
|
||||||
|
import About from './About';
|
||||||
|
import Skills from './Skills';
|
||||||
|
import Projects from './Projects';
|
||||||
|
import Education from './Education';
|
||||||
|
import Contact from './Contact';
|
||||||
|
|
||||||
|
const Home = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Hero />
|
||||||
|
<About />
|
||||||
|
<Skills />
|
||||||
|
<Projects />
|
||||||
|
<Education />
|
||||||
|
<Contact />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
||||||
127
src/components/Policies.tsx
Normal file
127
src/components/Policies.tsx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { useLanguage } from '../contexts/LanguageContext';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { ArrowLeft, Shield, Lock, Eye, Mail, Camera, MapPin, Bell } from 'lucide-react';
|
||||||
|
|
||||||
|
const Policies = () => {
|
||||||
|
const { t } = useLanguage();
|
||||||
|
|
||||||
|
const containerVariants = {
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sectionVariants = {
|
||||||
|
hidden: { opacity: 0, y: 20 },
|
||||||
|
visible: { opacity: 1, y: 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="policies-page" style={{ paddingTop: '100px', minHeight: '100vh', paddingBottom: '50px' }}>
|
||||||
|
<div className="container" style={{ maxWidth: '800px', margin: '0 auto', padding: '0 20px' }}>
|
||||||
|
<Link
|
||||||
|
to="/travelmate"
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
marginBottom: '40px',
|
||||||
|
color: 'var(--text-color)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
fontSize: '1rem',
|
||||||
|
opacity: 0.8
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowLeft size={18} />
|
||||||
|
{t('policies.back')}
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
variants={containerVariants}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<motion.div variants={sectionVariants} style={{ marginBottom: '4rem', textAlign: 'center' }}>
|
||||||
|
<Shield size={64} color="var(--primary-color)" style={{ marginBottom: '1rem' }} />
|
||||||
|
<h1 style={{ fontSize: '2.5rem', marginBottom: '0.5rem' }}>{t('policies.title')}</h1>
|
||||||
|
<p style={{ opacity: 0.6 }}>{t('policies.lastUpdated')}</p>
|
||||||
|
<p style={{ marginTop: '1rem', opacity: 0.9 }}>{t('policies.intro')}</p>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Section 1: Data & Permissions */}
|
||||||
|
<motion.div variants={sectionVariants} className="policy-section" style={{ marginBottom: '3rem' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '1.5rem' }}>
|
||||||
|
<Eye color="var(--primary-color)" size={28} />
|
||||||
|
<h2 style={{ fontSize: '1.8rem', margin: 0 }}>{t('policies.data.title')}</h2>
|
||||||
|
</div>
|
||||||
|
<p style={{ marginBottom: '2rem', opacity: 0.9 }}>{t('policies.data.intro')}</p>
|
||||||
|
|
||||||
|
<div style={{ display: 'grid', gap: '1.5rem', paddingLeft: '1rem' }}>
|
||||||
|
|
||||||
|
{/* Camera */}
|
||||||
|
<div style={{ background: 'var(--card-bg, rgba(255,255,255,0.05))', padding: '1.5rem', borderRadius: '0.5rem' }}>
|
||||||
|
<h3 style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '0.5rem' }}>
|
||||||
|
<Camera size={20} className="text-primary" /> {t('policies.data.camera')}
|
||||||
|
</h3>
|
||||||
|
<p style={{ opacity: 0.8, lineHeight: '1.6' }}>{t('policies.data.camera.desc')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* GPS */}
|
||||||
|
<div style={{ background: 'var(--card-bg, rgba(255,255,255,0.05))', padding: '1.5rem', borderRadius: '0.5rem' }}>
|
||||||
|
<h3 style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '0.5rem' }}>
|
||||||
|
<MapPin size={20} className="text-primary" /> {t('policies.data.gps')}
|
||||||
|
</h3>
|
||||||
|
<p style={{ opacity: 0.8, lineHeight: '1.6' }}>{t('policies.data.gps.desc')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Notifications */}
|
||||||
|
<div style={{ background: 'var(--card-bg, rgba(255,255,255,0.05))', padding: '1.5rem', borderRadius: '0.5rem' }}>
|
||||||
|
<h3 style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '0.5rem' }}>
|
||||||
|
<Bell size={20} className="text-primary" /> {t('policies.data.notif')}
|
||||||
|
</h3>
|
||||||
|
<p style={{ opacity: 0.8, lineHeight: '1.6' }}>{t('policies.data.notif.desc')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Section 2: Usage */}
|
||||||
|
<motion.div variants={sectionVariants} className="policy-section" style={{ marginBottom: '3rem' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '1rem' }}>
|
||||||
|
<Lock color="var(--primary-color)" size={28} />
|
||||||
|
<h2 style={{ fontSize: '1.8rem', margin: 0 }}>{t('policies.usage.title')}</h2>
|
||||||
|
</div>
|
||||||
|
<p style={{ lineHeight: '1.6', paddingLeft: '40px', opacity: 0.9 }}>
|
||||||
|
{t('policies.usage.content')}
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Section 3: Contact */}
|
||||||
|
<motion.div variants={sectionVariants} className="policy-section" style={{ marginBottom: '3rem' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '1rem' }}>
|
||||||
|
<Mail color="var(--primary-color)" size={28} />
|
||||||
|
<h2 style={{ fontSize: '1.8rem', margin: 0 }}>{t('policies.contact.title')}</h2>
|
||||||
|
</div>
|
||||||
|
<div style={{ paddingLeft: '40px' }}>
|
||||||
|
<p style={{ lineHeight: '1.6', opacity: 0.9, marginBottom: '0.5rem' }}>
|
||||||
|
{t('policies.contact.content')}
|
||||||
|
</p>
|
||||||
|
<a href="mailto:dev.dayronvl@gmail.com" style={{ color: 'var(--primary-color)', fontWeight: 'bold', fontSize: '1.1rem' }}>
|
||||||
|
dev.dayronvl@gmail.com
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Policies;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ExternalLink, MapPin, Wine } from 'lucide-react';
|
import { ExternalLink, MapPin, Wine } from 'lucide-react';
|
||||||
import { useLanguage } from '../contexts/LanguageContext';
|
import { useLanguage } from '../contexts/LanguageContext';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
const Projects = () => {
|
const Projects = () => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
@@ -20,7 +21,7 @@ const Projects = () => {
|
|||||||
color: "#4CAF50",
|
color: "#4CAF50",
|
||||||
icon: <MapPin size={24} />,
|
icon: <MapPin size={24} />,
|
||||||
links: {
|
links: {
|
||||||
demo: "#"
|
demo: "/travelmate"
|
||||||
},
|
},
|
||||||
image: "/travel-mate-preview.png" // Ajoutez votre image dans le dossier public
|
image: "/travel-mate-preview.png" // Ajoutez votre image dans le dossier public
|
||||||
},
|
},
|
||||||
@@ -182,6 +183,18 @@ const Projects = () => {
|
|||||||
<div className="project-links">
|
<div className="project-links">
|
||||||
|
|
||||||
{project.links.demo !== "#" && (
|
{project.links.demo !== "#" && (
|
||||||
|
project.links.demo.startsWith('/') ? (
|
||||||
|
<Link to={project.links.demo} style={{ textDecoration: 'none' }}>
|
||||||
|
<motion.div
|
||||||
|
className="project-link primary"
|
||||||
|
whileHover={{ scale: 1.1 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
>
|
||||||
|
<ExternalLink size={20} />
|
||||||
|
{t('projects.btn.viewProject')}
|
||||||
|
</motion.div>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
<motion.a
|
<motion.a
|
||||||
href={project.links.demo}
|
href={project.links.demo}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -193,6 +206,7 @@ const Projects = () => {
|
|||||||
<ExternalLink size={20} />
|
<ExternalLink size={20} />
|
||||||
{t('projects.btn.viewProject')}
|
{t('projects.btn.viewProject')}
|
||||||
</motion.a>
|
</motion.a>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
266
src/components/TravelMate.tsx
Normal file
266
src/components/TravelMate.tsx
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
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 } = useLanguage();
|
||||||
|
|
||||||
|
const containerVariants = {
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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="/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;
|
||||||
@@ -139,6 +139,65 @@ const translations = {
|
|||||||
'education.highschool.highlights.1': 'Sciences (Physique, Chimie, Biologie)',
|
'education.highschool.highlights.1': 'Sciences (Physique, Chimie, Biologie)',
|
||||||
'education.highschool.highlights.2': 'Langues',
|
'education.highschool.highlights.2': 'Langues',
|
||||||
|
|
||||||
|
// Travel Mate Page
|
||||||
|
'travelmate.page.mainTitle': 'Travel Mate 🌍',
|
||||||
|
'travelmate.page.subtitle': 'Le compagnon de voyage indispensable',
|
||||||
|
'travelmate.page.intro': 'Redécouvrez le voyage en groupe avec Travel Mate. Plus qu\'une simple application, c\'est votre copilote pour des aventures sans friction. Oubliez les tableaux Excel complexes et les débats sur "qui doit combien". Concentrez-vous sur l\'essentiel : créer des souvenirs inoubliables.',
|
||||||
|
|
||||||
|
'travelmate.highlights.title': '✨ Points Forts',
|
||||||
|
'travelmate.highlight.1.title': 'Gestion de Groupe Simplifiée',
|
||||||
|
'travelmate.highlight.1.desc': 'Créez votre voyage et invitez vos compagnons en un clic via un lien unique. L\'organisation démarre instantanément.',
|
||||||
|
'travelmate.highlight.2.title': 'Dépenses Maîtrisées (Split)',
|
||||||
|
'travelmate.highlight.2.desc': 'Suivez les dépenses en temps réel et laissez l\'application équilibrer les comptes automatiquement. Fini les calculs compliqués !',
|
||||||
|
'travelmate.highlight.3.title': 'Planification Collaborative',
|
||||||
|
'travelmate.highlight.3.desc': 'Un agenda partagé et une carte interactive pour que chacun puisse proposer et visualiser les activités du groupe.',
|
||||||
|
'travelmate.highlight.4.title': 'Communication Fluide',
|
||||||
|
'travelmate.highlight.4.desc': 'Un chat intégré pour centraliser les discussions et garder tout le monde sur la même longueur d\'onde.',
|
||||||
|
|
||||||
|
'travelmate.page.conclusion': 'Prêt à partir ? Avec Travel Mate, l\'aventure commence dès l\'organisation. Voyagez l\'esprit léger, on s\'occupe du reste.',
|
||||||
|
|
||||||
|
'travelmate.tech.title': '🛠️ Technologies utilisées',
|
||||||
|
'travelmate.tech.frontend': 'Frontend',
|
||||||
|
'travelmate.tech.frontend.1': 'Flutter - Framework de développement mobile cross-platform',
|
||||||
|
'travelmate.tech.frontend.2': 'Dart - Langage de programmation',
|
||||||
|
|
||||||
|
'travelmate.tech.frontend.3': 'BloC - Gestion d\'état',
|
||||||
|
|
||||||
|
'travelmate.tech.backend': 'Backend & Services',
|
||||||
|
'travelmate.tech.backend.1': 'Firebase Authentication - Gestion des utilisateurs',
|
||||||
|
'travelmate.tech.backend.2': 'Cloud Firestore - Base de données NoSQL',
|
||||||
|
'travelmate.tech.backend.3': 'Firebase Storage - Stockage de fichiers (photos, documents)',
|
||||||
|
'travelmate.tech.backend.4': 'Firebase Cloud Messaging - Notifications push',
|
||||||
|
|
||||||
|
'travelmate.tech.api': 'APIs externes',
|
||||||
|
'travelmate.tech.api.1': 'Google Places API - Recherche de lieux et points d\'intérêt',
|
||||||
|
'travelmate.tech.api.2': 'Google Maps API - Cartes et navigation',
|
||||||
|
'travelmate.tech.api.3': 'Google Directions API - Calcul d\'itinéraires',
|
||||||
|
|
||||||
|
'travelmate.policies.link': 'Voir la politique de confidentialité',
|
||||||
|
|
||||||
|
// Policies
|
||||||
|
'policies.title': 'Politique de Confidentialité - Travel Mate',
|
||||||
|
'policies.lastUpdated': 'Dernière mise à jour : 05/12/2025',
|
||||||
|
'policies.intro': 'Cette politique de confidentialité explique comment l\'application Travel Mate utilise et protège vos données.',
|
||||||
|
|
||||||
|
'policies.data.title': '1. Données collectées et Permissions',
|
||||||
|
'policies.data.intro': 'Pour fonctionner correctement, l\'application demande les permissions suivantes :',
|
||||||
|
'policies.data.camera': 'Caméra et Galerie (Storage)',
|
||||||
|
'policies.data.camera.desc': 'Pour vous permettre d\'ajouter des photos à vos lieux, activités et photos de profil. Ces images sont stockées sur nos serveurs sécurisés (Firebase).',
|
||||||
|
'policies.data.gps': 'Localisation (GPS)',
|
||||||
|
'policies.data.gps.desc': 'Pour vous montrer votre position sur la carte et calculer des itinéraires. Ces données de localisation ne sont pas enregistrées en permanence dans notre historique.',
|
||||||
|
'policies.data.notif': 'Notifications',
|
||||||
|
'policies.data.notif.desc': 'Pour vous alerter des nouveaux messages ou activités dans vos groupes de voyage.',
|
||||||
|
|
||||||
|
'policies.usage.title': '2. Utilisation des données',
|
||||||
|
'policies.usage.content': 'Vos données (email, profil, voyages) sont stockées de manière sécurisée via les services Google Firebase. Elles ne sont utilisées que pour le fonctionnement de l\'application et ne sont jamais revendues à des tiers.',
|
||||||
|
|
||||||
|
'policies.contact.title': '3. Contact',
|
||||||
|
'policies.contact.content': 'Pour toute question concernant vos données ou pour demander leur suppression, vous pouvez nous contacter à :',
|
||||||
|
|
||||||
|
'policies.back': 'Retour à Travel Mate',
|
||||||
|
|
||||||
// Contact
|
// Contact
|
||||||
'contact.title': 'Contactez-moi',
|
'contact.title': 'Contactez-moi',
|
||||||
'contact.subtitle': 'Une question, un projet ou simplement envie d\'échanger ? N\'hésitez pas à me contacter !',
|
'contact.subtitle': 'Une question, un projet ou simplement envie d\'échanger ? N\'hésitez pas à me contacter !',
|
||||||
@@ -276,6 +335,65 @@ const translations = {
|
|||||||
'education.highschool.highlights.1': 'Sciences (Physics, Chemistry, Biology)',
|
'education.highschool.highlights.1': 'Sciences (Physics, Chemistry, Biology)',
|
||||||
'education.highschool.highlights.2': 'Languages',
|
'education.highschool.highlights.2': 'Languages',
|
||||||
|
|
||||||
|
// Travel Mate Page
|
||||||
|
'travelmate.page.mainTitle': 'Travel Mate 🌍',
|
||||||
|
'travelmate.page.subtitle': 'The essential travel companion',
|
||||||
|
'travelmate.page.intro': 'Rediscover group travel with Travel Mate. More than just an app, it\'s your essential co-pilot for friction-free adventures. Forget complex spreadsheets and debates about "who owes what". Focus on what matters: making unforgettable memories.',
|
||||||
|
|
||||||
|
'travelmate.highlights.title': '✨ Key Highlights',
|
||||||
|
'travelmate.highlight.1.title': 'Simplified Group Management',
|
||||||
|
'travelmate.highlight.1.desc': 'Create your trip and invite companions with a single click via a unique link. Organization starts instantly.',
|
||||||
|
'travelmate.highlight.2.title': 'Mastered Expenses (Split)',
|
||||||
|
'travelmate.highlight.2.desc': 'Track expenses in real-time and let the app balance accounts automatically. No more complicated math!',
|
||||||
|
'travelmate.highlight.3.title': 'Collaborative Planning',
|
||||||
|
'travelmate.highlight.3.desc': 'A shared agenda and interactive map so everyone can suggest and visualize group activities.',
|
||||||
|
'travelmate.highlight.4.title': 'Seamless Communication',
|
||||||
|
'travelmate.highlight.4.desc': 'An integrated chat to centralize discussions and keep everyone on the same page.',
|
||||||
|
|
||||||
|
'travelmate.page.conclusion': 'Ready to go? With Travel Mate, the adventure begins with the planning. Travel with peace of mind, we\'ll handle the rest.',
|
||||||
|
|
||||||
|
'travelmate.tech.title': '🛠️ Technologies Used',
|
||||||
|
'travelmate.tech.frontend': 'Frontend',
|
||||||
|
'travelmate.tech.frontend.1': 'Flutter - Cross-platform mobile development framework',
|
||||||
|
'travelmate.tech.frontend.2': 'Dart - Programming language',
|
||||||
|
|
||||||
|
'travelmate.tech.frontend.3': 'BloC - State management',
|
||||||
|
|
||||||
|
'travelmate.tech.backend': 'Backend & Services',
|
||||||
|
'travelmate.tech.backend.1': 'Firebase Authentication - User management',
|
||||||
|
'travelmate.tech.backend.2': 'Cloud Firestore - NoSQL database',
|
||||||
|
'travelmate.tech.backend.3': 'Firebase Storage - File storage (photos, documents)',
|
||||||
|
'travelmate.tech.backend.4': 'Firebase Cloud Messaging - Push notifications',
|
||||||
|
|
||||||
|
'travelmate.tech.api': 'External APIs',
|
||||||
|
'travelmate.tech.api.1': 'Google Places API - Places and POI search',
|
||||||
|
'travelmate.tech.api.2': 'Google Maps API - Maps and navigation',
|
||||||
|
'travelmate.tech.api.3': 'Google Directions API - Route selection',
|
||||||
|
|
||||||
|
'travelmate.policies.link': 'View Privacy Policy',
|
||||||
|
|
||||||
|
// Policies
|
||||||
|
'policies.title': 'Privacy Policy - Travel Mate',
|
||||||
|
'policies.lastUpdated': 'Last updated: 05/12/2025',
|
||||||
|
'policies.intro': 'This privacy policy explains how the Travel Mate application uses and protects your data.',
|
||||||
|
|
||||||
|
'policies.data.title': '1. Collected Data and Permissions',
|
||||||
|
'policies.data.intro': 'To function correctly, the application requests the following permissions:',
|
||||||
|
'policies.data.camera': 'Camera and Gallery (Storage)',
|
||||||
|
'policies.data.camera.desc': 'To allow you to add photos to your places, activities, and profile pictures. These images are stored on our secure servers (Firebase).',
|
||||||
|
'policies.data.gps': 'Location (GPS)',
|
||||||
|
'policies.data.gps.desc': 'To show your position on the map and calculate routes. This location data is not permanently recorded in our history.',
|
||||||
|
'policies.data.notif': 'Notifications',
|
||||||
|
'policies.data.notif.desc': 'To alert you of new messages or activities in your travel groups.',
|
||||||
|
|
||||||
|
'policies.usage.title': '2. Data Usage',
|
||||||
|
'policies.usage.content': 'Your data (email, profile, trips) is stored securely via Google Firebase services. It is used only for the application\'s operation and is never sold to third parties.',
|
||||||
|
|
||||||
|
'policies.contact.title': '3. Contact',
|
||||||
|
'policies.contact.content': 'For any questions regarding your data or to request its deletion, you can contact us at:',
|
||||||
|
|
||||||
|
'policies.back': 'Back to Travel Mate',
|
||||||
|
|
||||||
// Contact
|
// Contact
|
||||||
'contact.title': 'Contact me',
|
'contact.title': 'Contact me',
|
||||||
'contact.subtitle': 'A question, a project or just want to chat? Feel free to contact me!',
|
'contact.subtitle': 'A question, a project or just want to chat? Feel free to contact me!',
|
||||||
|
|||||||
Reference in New Issue
Block a user