All checks were successful
Deploy React portfolio / deploy (push) Successful in 31s
Sets up a CI/CD pipeline to automate the deployment of the portfolio website to a web server. The pipeline includes steps for: - Checking out the code - Setting up Node.js - Installing dependencies - Building the application - Deploying the built application to the web server via SSH. This automated deployment ensures that the portfolio is always up-to-date with the latest changes. feat: update to use correct ssh Updates deployment script to use dist folder Modifies the deployment script to correctly reference the `dist` directory instead of the `build` directory. This ensures that the correct files are deployed to the web server. Automates deployment workflow using Gitea Actions Sets up a Gitea Actions workflow to automatically build and deploy the React portfolio website to the production server. This includes: - Using a containerized environment for consistent builds. - Installing dependencies and building the project. - Deploying the built files to the web server via SSH and rsync. - Reloading the web server configuration to apply changes. Also, this change updates the .gitignore file to exclude the 'dist' directory. edit to use ssh secret instead fix: english translation for education fix: correct contact mail fix: store system choice for theme
147 lines
6.5 KiB
TypeScript
147 lines
6.5 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { useTranslation } from 'react-i18next';
|
|
import Hero from './Hero';
|
|
import About from './About';
|
|
import Experience from './Experience/Experience';
|
|
import Skills from './Skills';
|
|
import Education from './Education';
|
|
import Contact from './Contact';
|
|
import { ThemeMenu, ThemeType, ThemeSelected } from './ThemeMenu';
|
|
|
|
function App() {
|
|
const { t, i18n } = useTranslation();
|
|
const [theme, setTheme] = useState<ThemeType>(() => {
|
|
const savedTheme = localStorage.getItem('theme');
|
|
return (savedTheme as ThemeType) || 'dark';
|
|
});
|
|
const [themeSelected, setThemeSelected] = useState<ThemeSelected>(() => {
|
|
const savedThemeSelected = localStorage.getItem('themeSelected');
|
|
return (savedThemeSelected as ThemeSelected) || 'system';
|
|
});
|
|
|
|
// Charger la langue sauvegardée au démarrage
|
|
useEffect(() => {
|
|
const savedLanguage = localStorage.getItem('language');
|
|
if (savedLanguage && (savedLanguage === 'fr' || savedLanguage === 'en')) {
|
|
i18n.changeLanguage(savedLanguage);
|
|
}
|
|
}, [i18n]);
|
|
|
|
useEffect(() => {
|
|
if (themeSelected === 'system') {
|
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
const handleChange = () => {
|
|
const systemTheme = mediaQuery.matches ? 'dark' : 'light';
|
|
setTheme(systemTheme);
|
|
document.documentElement.className = `theme-${systemTheme}`;
|
|
};
|
|
|
|
handleChange();
|
|
localStorage.setItem('themeSelected', themeSelected);
|
|
mediaQuery.addEventListener('change', handleChange);
|
|
|
|
return () => mediaQuery.removeEventListener('change', handleChange);
|
|
} else {
|
|
setTheme(themeSelected);
|
|
document.documentElement.className = `theme-${themeSelected}`;
|
|
}
|
|
|
|
// Sauvegarder le thème sélectionné
|
|
localStorage.setItem('themeSelected', themeSelected);
|
|
localStorage.setItem('theme', theme);
|
|
}, [themeSelected, theme]);
|
|
|
|
const toggleLanguage = () => {
|
|
const newLang = i18n.language === 'fr' ? 'en' : 'fr';
|
|
i18n.changeLanguage(newLang);
|
|
localStorage.setItem('language', newLang);
|
|
};
|
|
|
|
const handleThemeChange = (newTheme: ThemeSelected) => {
|
|
setThemeSelected(newTheme);
|
|
};
|
|
|
|
const scrollToSection = (e: React.MouseEvent<HTMLAnchorElement>, id: string) => {
|
|
e.preventDefault();
|
|
const section = document.getElementById(id);
|
|
if (section) {
|
|
section.scrollIntoView({ behavior: 'smooth' });
|
|
window.history.pushState(null, '', `#${id}`);
|
|
}
|
|
}
|
|
|
|
|
|
return (
|
|
<div className="min-h-screen text-secondary bg-primary/80 transition-colors duration-100">
|
|
{/* Background Blobs */}
|
|
<div className="background-blobs">
|
|
<div className="blob blob-1"></div>
|
|
<div className="blob blob-2"></div>
|
|
<div className="blob blob-3"></div>
|
|
</div>
|
|
|
|
{/* Header */}
|
|
<header className='fixed top-0 left-0 right-0 z-50 flex justify-between items-center gap-4 text-sm w-full px-8 py-6 bg-primary/80 backdrop-blur-md border-b border-secondary/10'>
|
|
<h1 className='text-2xl font-bold'>
|
|
Louis EMARD
|
|
</h1>
|
|
<nav className='flex gap-6 h-full justify-center items-center'>
|
|
<a href='#about' onClick={(e) => scrollToSection(e, 'about')} className='hidden md:block hover:text-accent transition-colors cursor-pointer'>{t('nav.about')}</a>
|
|
<a href='#experience' onClick={(e) => scrollToSection(e, 'experience')} className='hidden md:block hover:text-accent transition-colors cursor-pointer'>{t('nav.projects')}</a>
|
|
<a href='#skills' onClick={(e) => scrollToSection(e, 'skills')} className='hidden md:block hover:text-accent transition-colors cursor-pointer'>{t('nav.skills')}</a>
|
|
<a href='#education' onClick={(e) => scrollToSection(e, 'education')} className='hidden md:block hover:text-accent transition-colors cursor-pointer'>{t('nav.education')}</a>
|
|
<a href='#contact' onClick={(e) => scrollToSection(e, 'contact')} className='hidden md:block hover:text-accent transition-colors cursor-pointer'>{t('nav.contact')}</a>
|
|
|
|
{/* Language Toggle */}
|
|
<button
|
|
onClick={toggleLanguage}
|
|
className='px-3 py-1 border border-secondary/20 rounded hover:border-accent hover:text-accent transition-all'
|
|
>
|
|
{i18n.language.toUpperCase()}
|
|
</button>
|
|
|
|
{/* Theme Menu */}
|
|
<ThemeMenu
|
|
theme={theme}
|
|
themeSelected={themeSelected}
|
|
onThemeChange={handleThemeChange}
|
|
/>
|
|
</nav>
|
|
</header>
|
|
|
|
{/* Main Content */}
|
|
<main>
|
|
<Hero />
|
|
<About />
|
|
<Experience />
|
|
<Skills />
|
|
<Education />
|
|
<Contact />
|
|
</main>
|
|
|
|
{/* Footer */}
|
|
<footer className='py-8 border-t border-secondary/10 bg-secondary/5'>
|
|
<div className='flex gap-6 justify-center items-center text-sm'>
|
|
<a href='https://git.louisemard.dev' target='_blank' rel='noopener noreferrer' className='hover:text-accent transition-colors'>
|
|
Gitea
|
|
</a>
|
|
<a href='https://github.com/LouisDrame' target='_blank' rel='noopener noreferrer' className='hover:text-accent transition-colors'>
|
|
GitHub
|
|
</a>
|
|
<a href='https://www.linkedin.com/in/louis-emard-9b6a931a2/' target='_blank' rel='noopener noreferrer' className='hover:text-accent transition-colors'>
|
|
LinkedIn
|
|
</a>
|
|
<a href='https://www.collective.work/profile/louis-emard' target='_blank' rel='noopener noreferrer' className='hover:text-accent transition-colors'>
|
|
Collective
|
|
</a>
|
|
</div>
|
|
<p className='text-center mt-4 text-secondary/50 text-xs'>
|
|
© {new Date().getFullYear()} Louis EMARD. All rights reserved.
|
|
</p>
|
|
</footer>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default App
|