Compare commits

..

2 Commits

20 changed files with 155 additions and 5677 deletions

View File

@ -1,31 +0,0 @@
name: Build
run-name: CI/CD website
on:
push:
branches:
- main
- dev
pull_request:
types: [opened, synchronize, reopened]
branches:
- main
- dev
jobs:
build-check:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '24.x'
- name: Install dependencies
run: |
cd banquise-website
npm ci
- name: Building
run: |
cd banquise-website
npm run build

4
.gitignore vendored
View File

@ -30,3 +30,7 @@ pnpm-debug.log*
# optional: nix store link if using nix develop # optional: nix store link if using nix develop
.result .result
# optional: lockfiles you don't use
package-lock.json

File diff suppressed because it is too large Load Diff

View File

@ -1,67 +1,103 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Navigation } from './components/layout/Navigation'; import { Navigation } from './components/layout/Navigation';
import { HeroSection } from './components/sections/HeroSection';
import { ServicesSection } from './components/sections/ServicesSection';
import { TechFeaturesSection } from './components/sections/TechFeaturesSection';
import { AboutSection } from './components/sections/AboutSection';
import { Footer } from './components/layout/Footer'; import { Footer } from './components/layout/Footer';
import { HeroSection } from './components/sections/HeroSection';
import { TechFeaturesSection } from './components/sections/TechFeaturesSection';
import { ServicesSection } from './components/sections/ServicesSection';
import { AboutSection } from './components/sections/AboutSection';
import { Popup } from './components/ui/Popup'; import { Popup } from './components/ui/Popup';
import { ScrollToTopButton } from './components/ui/ScrollToTopButton'; import { ScrollToTopButton } from './components/ui/ScrollToTopButton';
import { ParallaxBackground } from './components/ui/ParallaxBackground'; import { URLS } from './config/constants';
import { LanguageSwitcher } from './components/ui/LanguageSwitcher';
import { useTranslation } from './hooks/useTranslation'; // Define Service interface directly in App
import type { Service } from './types/service'; interface Service {
name: string;
url: string;
image: string;
description: string;
features: string[];
icon: string;
}
const App: React.FC = () => { const App: React.FC = () => {
const { t, currentLanguage, changeLanguage, availableLanguages } = useTranslation(); // Define services directly in the component with enhanced data
const [selectedService, setSelectedService] = useState<Service | null>(null); const services: Service[] = [
const [openAccordion, setOpenAccordion] = useState<string | null>(null); {
name: "Wiki",
url: URLS.services.wiki,
image: "/src/assets/iceberg.png",
icon: "📚",
description: "Notre wiki collaboratif est votre centre de documentation technique. Accédez à des guides détaillés, des tutoriels et de la documentation API pour tous nos services.",
features: [
"Documentation collaborative en temps réel",
"Guides d'installation détaillés",
"API et références techniques",
"Tutoriels pas à pas",
"Base de connaissances communautaire",
"Recherche avancée intégrée"
]
},
{
name: "Gitea",
url: URLS.services.gitea,
image: "/src/assets/iceberg.png",
icon: "🔧",
description: "Instance Gitea auto-hébergée pour la gestion de vos dépôts Git. Collaborez sur vos projets avec un contrôle total sur votre code source.",
features: [
"Dépôts Git illimités",
"Issues et pull requests",
"Actions CI/CD intégrées",
"Gestion d'équipes et permissions",
"Interface web intuitive",
"Intégration avec outils externes"
]
},
{
name: "Panel",
url: URLS.services.panel,
image: "/src/assets/iceberg.png",
icon: "🎮",
description: "Interface de gestion centralisée pour vos serveurs de jeux. Déployez, configurez et surveillez vos serveurs gaming en quelques clics.",
features: [
"Déploiement automatisé de serveurs",
"Monitoring en temps réel",
"Gestion des ressources système",
"Interface d'administration web",
"Support multi-jeux",
"Sauvegarde automatique des données"
]
}
];
const [selectedService, setSelectedService] = useState<Service | null>(null);
// Inline accordion logic
const [openAccordion, setOpenAccordion] = useState<string | null>(null);
const toggleAccordion = (title: string) => { const toggleAccordion = (title: string) => {
setOpenAccordion(openAccordion === title ? null : title); setOpenAccordion(openAccordion === title ? null : title);
}; };
return ( return (
<div className="min-h-screen bg-gradient-to-b from-banquise-blue-dark via-banquise-blue-dark/95 to-banquise-blue-dark text-white overflow-x-hidden relative"> <div className="flex flex-col min-h-screen w-full">
{/* Background Effects */} <Navigation />
<ParallaxBackground />
{/* Main Content */}
<div className="relative z-10">
{/* Navigation avec sélecteur de langue */}
<Navigation
translations={t.navigation}
languageSwitcher={
<LanguageSwitcher
currentLanguage={currentLanguage}
onLanguageChange={changeLanguage}
availableLanguages={availableLanguages}
/>
}
/>
<HeroSection translations={t.hero} />
<ServicesSection
services={t.services}
onServiceClick={setSelectedService}
translations={t.common}
/>
<TechFeaturesSection />
<AboutSection openAccordion={openAccordion} toggleAccordion={toggleAccordion} />
<Footer />
</div>
{/* UI Components */} <main className="flex-1 flex flex-col overflow-x-hidden overflow-y-auto">
<div className="relative flex-1 bg-ocean-gradient w-full min-h-screen flex flex-col justify-start items-center overflow-x-hidden">
<HeroSection />
<TechFeaturesSection />
<ServicesSection services={services} onServiceClick={setSelectedService} />
<AboutSection openAccordion={openAccordion} toggleAccordion={toggleAccordion} />
</div>
</main>
<Footer />
{/* Bouton de retour en haut */}
<ScrollToTopButton /> <ScrollToTopButton />
{selectedService && ( {selectedService && (
<Popup <Popup service={selectedService} onClose={() => setSelectedService(null)} />
service={selectedService}
onClose={() => setSelectedService(null)}
translations={t.common}
/>
)} )}
</div> </div>
); );

View File

@ -24,26 +24,6 @@ export const Footer: React.FC = () => (
Panel Panel
</a> </a>
</li> </li>
<li>
<a href={URLS.services.pelican} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base">
Pelican
</a>
</li>
<li>
<a href={URLS.services.intra} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base">
Intranet
</a>
</li>
<li>
<a href={URLS.services.mails} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base">
Webmail
</a>
</li>
<li>
<a href={URLS.services.opencloud} className="text-banquise-gray/80 no-underline transition-all duration-200 inline-flex items-center hover:text-banquise-gray hover:translate-x-1 text-sm sm:text-base">
OpenCloud
</a>
</li>
</ul> </ul>
</div> </div>

View File

@ -2,15 +2,13 @@ import React, { useEffect } from 'react';
import banquiseServer from '../../assets/banquise_server.svg' import banquiseServer from '../../assets/banquise_server.svg'
import { URLS, SITE_CONFIG } from '../../config/constants'; import { URLS, SITE_CONFIG } from '../../config/constants';
import { commonStyles } from '../../styles/components'; import { commonStyles } from '../../styles/components';
import type { Translation } from '../../types/i18n';
interface MobileMenuProps { interface MobileMenuProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
translations: Translation['navigation'];
} }
export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, translations }) => { export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose }) => {
useEffect(() => { useEffect(() => {
if (isOpen) { if (isOpen) {
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
@ -78,7 +76,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, transla
</svg> </svg>
</div> </div>
<div> <div>
<span className="font-semibold text-lg">{translations.services}</span> <span className="font-semibold text-lg">Nos Services</span>
<p className="text-white/60 text-sm">Découvrir notre offre</p> <p className="text-white/60 text-sm">Découvrir notre offre</p>
</div> </div>
</div> </div>
@ -95,7 +93,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, transla
</svg> </svg>
</div> </div>
<div> <div>
<span className="font-semibold text-lg">{translations.about}</span> <span className="font-semibold text-lg">À propos</span>
<p className="text-white/60 text-sm">En savoir plus sur nous</p> <p className="text-white/60 text-sm">En savoir plus sur nous</p>
</div> </div>
</div> </div>
@ -108,7 +106,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, transla
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<div className={`${commonStyles.icons.small} ${commonStyles.gradients.discord} group-hover:scale-110 transition-transform duration-200`}> <div className={`${commonStyles.icons.small} ${commonStyles.gradients.discord} group-hover:scale-110 transition-transform duration-200`}>
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.211.375-.445.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.30z"/> <path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.211.375-.445.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z"/>
</svg> </svg>
</div> </div>
<div> <div>

View File

@ -3,14 +3,8 @@ import { MobileMenu } from './MobileMenu';
import banquiseServer from '/src/assets/banquise_server.svg' import banquiseServer from '/src/assets/banquise_server.svg'
import { URLS, SITE_CONFIG } from '../../config/constants'; import { URLS, SITE_CONFIG } from '../../config/constants';
import { commonStyles } from '../../styles/components'; import { commonStyles } from '../../styles/components';
import type { Translation } from '../../types/i18n';
interface NavigationProps { export const Navigation: React.FC = () => {
translations: Translation['navigation'];
languageSwitcher: React.ReactElement;
}
export const Navigation: React.FC<NavigationProps> = ({ translations, languageSwitcher }) => {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [scrolled, setScrolled] = useState(false); const [scrolled, setScrolled] = useState(false);
@ -19,6 +13,7 @@ export const Navigation: React.FC<NavigationProps> = ({ translations, languageSw
const isScrolled = window.scrollY > 20; const isScrolled = window.scrollY > 20;
setScrolled(isScrolled); setScrolled(isScrolled);
}; };
window.addEventListener('scroll', handleScroll); window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll);
}, []); }, []);
@ -29,6 +24,7 @@ export const Navigation: React.FC<NavigationProps> = ({ translations, languageSw
setMobileMenuOpen(false); setMobileMenuOpen(false);
} }
}; };
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize);
}, []); }, []);
@ -36,21 +32,22 @@ export const Navigation: React.FC<NavigationProps> = ({ translations, languageSw
return ( return (
<> <>
<nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${ <nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
scrolled scrolled
? 'bg-banquise-blue-dark/98 backdrop-blur-xl shadow-2xl border-b border-banquise-blue-lightest/30' ? 'bg-banquise-blue-dark/98 backdrop-blur-xl shadow-2xl border-b border-banquise-blue-lightest/30'
: 'bg-banquise-blue-dark/95 backdrop-blur-lg shadow-xl border-b border-banquise-blue-lightest/20' : 'bg-banquise-blue-dark/95 backdrop-blur-lg shadow-xl border-b border-banquise-blue-lightest/20'
}`}> }`}>
<div className={commonStyles.layout.container}> <div className={commonStyles.layout.container}>
<div className="flex justify-between items-center h-16 sm:h-18 lg:h-20 px-4 sm:px-6 lg:px-8"> <div className="flex justify-between items-center h-16 sm:h-18 lg:h-20 px-4 sm:px-6 lg:px-8">
{/* Logo section */} {/* Logo section */}
<div className="flex items-center space-x-3 sm:space-x-4 group"> <div className="flex items-center space-x-3 sm:space-x-4 group">
<div className="relative"> <div className="relative">
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-full blur-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> <div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-full blur-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
<img <img
src={banquiseServer} src={banquiseServer}
alt="Logo La Banquise" alt="Logo La Banquise"
className="h-10 sm:h-12 lg:h-14 w-auto relative z-10 transition-transform duration-300 group-hover:scale-110" className="h-10 sm:h-12 lg:h-14 w-auto relative z-10 transition-transform duration-300 group-hover:scale-110"
style={{ filter: 'drop-shadow(0 0 12px rgba(168, 218, 255, 0.4))' }} style={{ filter: 'drop-shadow(0 0 12px rgba(168, 218, 255, 0.4))' }}
/> />
</div> </div>
<div className="hidden sm:block"> <div className="hidden sm:block">
@ -65,30 +62,20 @@ export const Navigation: React.FC<NavigationProps> = ({ translations, languageSw
{/* Navigation links desktop */} {/* Navigation links desktop */}
<div className="hidden md:flex items-center space-x-1 lg:space-x-2"> <div className="hidden md:flex items-center space-x-1 lg:space-x-2">
<a href="#home" className={commonStyles.nav.link}>
<span className="relative z-10">{translations.home}</span>
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
</a>
<a href="#services" className={commonStyles.nav.link}> <a href="#services" className={commonStyles.nav.link}>
<span className="relative z-10">{translations.services}</span> <span className="relative z-10">Services</span>
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> <div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
</a> </a>
<a href="#about" className={commonStyles.nav.link}> <a href="#about" className={commonStyles.nav.link}>
<span className="relative z-10">{translations.about}</span> <span className="relative z-10">À propos</span>
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
</a>
<a href="#contact" className={commonStyles.nav.link}>
<span className="relative z-10">{translations.contact}</span>
<div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> <div className="absolute inset-0 bg-gradient-to-r from-banquise-blue-light/20 to-banquise-blue/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
</a> </a>
</div> </div>
{/* Action buttons desktop */} {/* Action buttons desktop */}
<div className="hidden md:flex items-center space-x-3 lg:space-x-4"> <div className="hidden md:flex items-center space-x-3 lg:space-x-4">
{/* Language switcher */} <a
{languageSwitcher}
<a
href={URLS.social.discord} href={URLS.social.discord}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
@ -102,19 +89,20 @@ export const Navigation: React.FC<NavigationProps> = ({ translations, languageSw
<span>Discord</span> <span>Discord</span>
</div> </div>
</a> </a>
<a
<a
href={URLS.services.auth} href={URLS.services.auth}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className={`${commonStyles.buttons.auth} ${ className={`${commonStyles.buttons.auth} ${
scrolled scrolled
? `${commonStyles.gradients.primary} border border-banquise-blue-lightest/30 hover:shadow-banquise-blue/25` ? `${commonStyles.gradients.primary} border border-banquise-blue-lightest/30 hover:shadow-banquise-blue/25`
: 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue border-2 border-white/20 hover:shadow-banquise-blue-light/25' : 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue border-2 border-white/20 hover:shadow-banquise-blue-light/25'
}`} }`}
> >
<div className={`absolute inset-0 transition-opacity duration-300 opacity-0 group-hover:opacity-100 ${ <div className={`absolute inset-0 transition-opacity duration-300 opacity-0 group-hover:opacity-100 ${
scrolled scrolled
? 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue' ? 'bg-gradient-to-r from-banquise-blue-light to-banquise-blue'
: 'bg-gradient-to-r from-white/10 to-banquise-blue-lightest/20' : 'bg-gradient-to-r from-white/10 to-banquise-blue-lightest/20'
}`}></div> }`}></div>
<div className="relative z-10 flex items-center space-x-2"> <div className="relative z-10 flex items-center space-x-2">
@ -127,32 +115,32 @@ export const Navigation: React.FC<NavigationProps> = ({ translations, languageSw
</div> </div>
{/* Mobile menu button */} {/* Mobile menu button */}
<button <button
className="md:hidden relative p-3 rounded-xl bg-white/10 hover:bg-white/20 transition-all duration-300 hover:scale-105 active:scale-95 group" className="md:hidden relative p-3 rounded-xl bg-white/10 hover:bg-white/20 transition-all duration-300 hover:scale-105 active:scale-95 group"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)} onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-label={mobileMenuOpen ? "Fermer le menu" : "Ouvrir le menu"} aria-label={mobileMenuOpen ? "Fermer le menu" : "Ouvrir le menu"}
aria-expanded={mobileMenuOpen} aria-expanded={mobileMenuOpen}
> >
<div className="w-6 h-6 relative"> <div className="w-6 h-6 relative">
<span className={`absolute block w-6 h-6 bg-white transition-all duration-300 ${mobileMenuOpen ? 'rotate-45 top-3' : 'top-1'}`}></span> <span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 ${mobileMenuOpen ? 'rotate-45 top-3' : 'top-1'}`}></span>
<span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 top-3 ${mobileMenuOpen ? 'opacity-0 scale-0' : 'opacity-100'}`}></span> <span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 top-3 ${mobileMenuOpen ? 'opacity-0 scale-0' : 'opacity-100'}`}></span>
<span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 ${mobileMenuOpen ? '-rotate-45 top-3' : 'top-5'}`}></span> <span className={`absolute block w-6 h-0.5 bg-white transition-all duration-300 ${mobileMenuOpen ? '-rotate-45 top-3' : 'top-5'}`}></span>
</div> </div>
</button> </button>
</div> </div>
</div> </div>
{/* Glassmorphism effect bar */} {/* Glassmorphism effect bar */}
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-banquise-blue-lightest/30 to-transparent"></div> <div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-banquise-blue-lightest/30 to-transparent"></div>
</nav> </nav>
{/* Spacer pour compenser la navbar fixed */} {/* Spacer pour compenser la navbar fixed */}
<div className="h-16 sm:h-18 lg:h-20"></div> <div className="h-16 sm:h-18 lg:h-20"></div>
{/* Menu mobile */} {/* Menu mobile */}
<MobileMenu <MobileMenu
isOpen={mobileMenuOpen} isOpen={mobileMenuOpen}
onClose={() => setMobileMenuOpen(false)} onClose={() => setMobileMenuOpen(false)}
translations={translations}
/> />
</> </>
); );

View File

@ -80,38 +80,6 @@ export const AboutSection: React.FC<AboutSectionProps> = ({ openAccordion, toggl
<p className="text-banquise-gray/80 text-sm">Interface de gestion pour serveurs de jeux</p> <p className="text-banquise-gray/80 text-sm">Interface de gestion pour serveurs de jeux</p>
</div> </div>
</div> </div>
<div className={`flex items-start space-x-4 p-4 ${commonStyles.gradients.card} rounded-xl ${commonStyles.cards.base}`}>
<div className={`${commonStyles.icons.small} ${commonStyles.gradients.primaryBr} font-bold`}>🐧</div>
<div>
<h4 className="font-semibold text-banquise-gray mb-1">Pelican</h4>
<p className="text-banquise-gray/80 text-sm">Générateur de sites statiques</p>
</div>
</div>
<div className={`flex items-start space-x-4 p-4 ${commonStyles.gradients.card} rounded-xl ${commonStyles.cards.base}`}>
<div className={`${commonStyles.icons.small} ${commonStyles.gradients.primaryBr} font-bold`}>🏢</div>
<div>
<h4 className="font-semibold text-banquise-gray mb-1">Intranet</h4>
<p className="text-banquise-gray/80 text-sm">Espace privé de l'association</p>
</div>
</div>
<div className={`flex items-start space-x-4 p-4 ${commonStyles.gradients.card} rounded-xl ${commonStyles.cards.base}`}>
<div className={`${commonStyles.icons.small} ${commonStyles.gradients.primaryBr} font-bold`}>📧</div>
<div>
<h4 className="font-semibold text-banquise-gray mb-1">Webmail</h4>
<p className="text-banquise-gray/80 text-sm">Service de messagerie électronique</p>
</div>
</div>
<div className={`flex items-start space-x-4 p-4 ${commonStyles.gradients.card} rounded-xl ${commonStyles.cards.base}`}>
<div className={`${commonStyles.icons.small} ${commonStyles.gradients.primaryBr} font-bold`}></div>
<div>
<h4 className="font-semibold text-banquise-gray mb-1">OpenCloud</h4>
<p className="text-banquise-gray/80 text-sm">Plateforme cloud collaborative</p>
</div>
</div>
</div> </div>
<p className={`${commonStyles.text.muted} mt-4`}> <p className={`${commonStyles.text.muted} mt-4`}>
Tous nos services sont maintenus avec soin et régulièrement mis à jour pour garantir une expérience optimale. Tous nos services sont maintenus avec soin et régulièrement mis à jour pour garantir une expérience optimale.

View File

@ -1,12 +1,7 @@
import React from 'react'; import React from 'react';
import banquiseServer from '/src/assets/banquise_server.svg' import banquiseServer from '/src/assets/banquise_server.svg'
import type { Translation } from '../../types/i18n';
interface HeroSectionProps { export const HeroSection: React.FC = () => (
translations: Translation['hero'];
}
export const HeroSection: React.FC<HeroSectionProps> = ({ translations }) => (
<section className="min-h-[calc(80vh-72px)] flex flex-col justify-center items-center text-center py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8 relative z-3"> <section className="min-h-[calc(80vh-72px)] flex flex-col justify-center items-center text-center py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8 relative z-3">
<div className="mb-8 sm:mb-10 md:mb-12 w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48 rounded-full bg-gradient-to-br from-banquise-blue-dark/20 to-banquise-blue/10 p-4 sm:p-5 md:p-6 shadow-2xl backdrop-blur-sm border border-banquise-blue-lightest/30 relative group"> <div className="mb-8 sm:mb-10 md:mb-12 w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48 rounded-full bg-gradient-to-br from-banquise-blue-dark/20 to-banquise-blue/10 p-4 sm:p-5 md:p-6 shadow-2xl backdrop-blur-sm border border-banquise-blue-lightest/30 relative group">
<img <img
@ -20,15 +15,15 @@ export const HeroSection: React.FC<HeroSectionProps> = ({ translations }) => (
</div> </div>
<h1 className="text-banquise-gray text-3xl sm:text-4xl md:text-5xl lg:text-6xl mb-6 sm:mb-7 md:mb-8 font-extrabold leading-tight max-w-4xl font-heading px-2 relative z-10" style={{ textShadow: '0 2px 10px rgba(0, 0, 0, 0.3)' }}> <h1 className="text-banquise-gray text-3xl sm:text-4xl md:text-5xl lg:text-6xl mb-6 sm:mb-7 md:mb-8 font-extrabold leading-tight max-w-4xl font-heading px-2 relative z-10" style={{ textShadow: '0 2px 10px rgba(0, 0, 0, 0.3)' }}>
{translations.title} Bienvenue sur La Banquise
</h1> </h1>
<p className="text-banquise-gray text-lg sm:text-xl md:text-2xl mb-8 sm:mb-10 md:mb-12 max-w-3xl font-normal opacity-90 leading-relaxed px-2 relative z-10" style={{ textShadow: '0 1px 4px rgba(0, 0, 0, 0.2)' }}> <p className="text-banquise-gray text-lg sm:text-xl md:text-2xl mb-8 sm:mb-10 md:mb-12 max-w-3xl font-normal opacity-90 leading-relaxed px-2 relative z-10" style={{ textShadow: '0 1px 4px rgba(0, 0, 0, 0.2)' }}>
{translations.subtitle} Association d'hébergement et lab réseau pour tous les étudiants et associations de l'EPITA !
</p> </p>
<a href="#services" className="inline-flex items-center justify-center bg-gradient-to-r from-banquise-gray to-white text-banquise-blue-dark border-0 py-4 sm:py-5 px-8 sm:px-10 md:px-12 rounded-2xl text-base sm:text-lg font-bold no-underline shadow-xl transition-all duration-300 min-w-48 sm:min-w-56 md:min-w-64 hover:-translate-y-2 hover:shadow-2xl hover:scale-105 backdrop-blur-sm border border-banquise-blue-lightest/20 mx-4 group relative z-10"> <a href="#services" className="inline-flex items-center justify-center bg-gradient-to-r from-banquise-gray to-white text-banquise-blue-dark border-0 py-4 sm:py-5 px-8 sm:px-10 md:px-12 rounded-2xl text-base sm:text-lg font-bold no-underline shadow-xl transition-all duration-300 min-w-48 sm:min-w-56 md:min-w-64 hover:-translate-y-2 hover:shadow-2xl hover:scale-105 backdrop-blur-sm border border-banquise-blue-lightest/20 mx-4 group relative z-10">
<span className="text-center text-banquise-blue-dark">{translations.cta}</span> <span className="text-center text-banquise-blue-dark">Découvrir nos services</span>
<span className="ml-2 sm:ml-3 text-lg sm:text-xl transition-transform duration-300 group-hover:translate-x-1 text-banquise-blue-dark"></span> <span className="ml-2 sm:ml-3 text-lg sm:text-xl transition-transform duration-300 group-hover:translate-x-1 text-banquise-blue-dark"></span>
</a> </a>
</section> </section>

View File

@ -1,19 +1,22 @@
import React from 'react'; import React from 'react';
import type { Service } from '../../types/service';
// Declare the Service interface here
interface Service {
name: string;
url: string;
image: string;
icon: string;
description: string;
features: string[];
}
// Define interface directly in the component file
interface ServicesSectionProps { interface ServicesSectionProps {
services: Service[]; services: Service[];
onServiceClick: (service: Service) => void; onServiceClick: (service: Service) => void;
translations: {
discoverFeatures: string;
};
} }
export const ServicesSection: React.FC<ServicesSectionProps> = ({ export const ServicesSection: React.FC<ServicesSectionProps> = ({ services, onServiceClick }) => (
services,
onServiceClick,
translations
}) => (
<section id="services" className="relative z-2 py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8"> <section id="services" className="relative z-2 py-12 sm:py-16 md:py-20 w-full max-w-6xl mx-auto px-4 sm:px-6 md:px-8">
<div className="w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto mb-6 sm:mb-8 rounded-full"></div> <div className="w-20 h-1 bg-gradient-to-r from-banquise-blue-lightest to-banquise-blue mx-auto mb-6 sm:mb-8 rounded-full"></div>
<h2 className="text-banquise-gray text-2xl sm:text-3xl md:text-4xl mb-4 sm:mb-6 text-center font-heading font-bold tracking-tight px-2" style={{ textShadow: '0 2px 4px rgba(0, 0, 0, 0.2)' }}> <h2 className="text-banquise-gray text-2xl sm:text-3xl md:text-4xl mb-4 sm:mb-6 text-center font-heading font-bold tracking-tight px-2" style={{ textShadow: '0 2px 4px rgba(0, 0, 0, 0.2)' }}>
@ -47,7 +50,7 @@ export const ServicesSection: React.FC<ServicesSectionProps> = ({
{/* CTA */} {/* CTA */}
<div className="flex items-center justify-center text-banquise-blue-light font-bold group-hover:text-banquise-blue-lightest transition-colors duration-300 text-sm sm:text-base"> <div className="flex items-center justify-center text-banquise-blue-light font-bold group-hover:text-banquise-blue-lightest transition-colors duration-300 text-sm sm:text-base">
<span className="text-center">{translations.discoverFeatures}</span> <span className="text-center">Découvrir toutes les fonctionnalités</span>
<span className="ml-2 text-lg transition-transform duration-300 group-hover:translate-x-2"></span> <span className="ml-2 text-lg transition-transform duration-300 group-hover:translate-x-2"></span>
</div> </div>

View File

@ -1,37 +0,0 @@
import React from 'react';
import type { Language } from '../../types/i18n';
interface LanguageSwitcherProps {
currentLanguage: Language;
onLanguageChange: (language: Language) => void;
availableLanguages: Language[];
}
export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
currentLanguage,
onLanguageChange,
availableLanguages
}) => {
const languageNames: Record<Language, string> = {
fr: '🇫🇷 Français',
en: '🇬🇧 English',
//es: '🇪🇸 Español',
//de: '🇩🇪 Deutsch'
};
return (
<div className="relative inline-block">
<select
value={currentLanguage}
onChange={(e) => onLanguageChange(e.target.value as Language)}
className="bg-banquise-blue-dark/20 border border-banquise-blue-lightest/30 text-banquise-gray rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-banquise-blue-light"
>
{availableLanguages.map((lang) => (
<option key={lang} value={lang} className="bg-banquise-blue-dark text-banquise-gray">
{languageNames[lang]}
</option>
))}
</select>
</div>
);
};

View File

@ -1,19 +1,25 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { URLS } from '../../config/constants'; import { URLS } from '../../config/constants';
import type { Service } from '../../types/service';
import type { Translation } from '../../types/i18n'; interface Service {
name: string;
url: string;
image: string;
icon: string;
description: string;
features: string[];
}
interface PopupProps { interface PopupProps {
service: Service; service: Service;
onClose: () => void; onClose: () => void;
translations: Translation['common'];
} }
export const Popup: React.FC<PopupProps> = ({ service, onClose, translations }) => { export const Popup: React.FC<PopupProps> = ({ service, onClose }) => {
// Empêcher le scroll du body quand la popup est ouverte // Empêcher le scroll du body quand la popup est ouverte
useEffect(() => { useEffect(() => {
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
return () => { return () => {
document.body.style.overflow = 'unset'; document.body.style.overflow = 'unset';
}; };
@ -22,18 +28,18 @@ export const Popup: React.FC<PopupProps> = ({ service, onClose, translations })
return ( return (
<div className="fixed inset-0 bg-black/60 flex justify-center items-center z-50 p-4 backdrop-blur-md animate-fadeIn"> <div className="fixed inset-0 bg-black/60 flex justify-center items-center z-50 p-4 backdrop-blur-md animate-fadeIn">
<div className="bg-white text-banquise-blue-dark rounded-3xl max-w-4xl w-full max-h-[90vh] shadow-2xl relative animate-slideUp border border-banquise-blue-lightest/20 overflow-hidden"> <div className="bg-white text-banquise-blue-dark rounded-3xl max-w-4xl w-full max-h-[90vh] shadow-2xl relative animate-slideUp border border-banquise-blue-lightest/20 overflow-hidden">
{/* Bouton de fermeture fixe au-dessus du contenu */} {/* Bouton de fermeture fixe au-dessus du contenu */}
<div className="absolute top-4 right-4 z-50"> <div className="absolute top-4 right-4 z-50">
<button <button
onClick={onClose} onClick={onClose}
className="bg-white/90 hover:bg-white border border-banquise-blue/20 text-xl cursor-pointer text-banquise-blue-dark flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 rounded-full transition-all duration-200 hover:scale-110 active:scale-95 shadow-lg backdrop-blur-sm" className="bg-white/90 hover:bg-white border border-banquise-blue/20 text-xl cursor-pointer text-banquise-blue-dark flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 rounded-full transition-all duration-200 hover:scale-110 active:scale-95 shadow-lg backdrop-blur-sm"
aria-label={translations.close} aria-label="Fermer la popup"
> >
× ×
</button> </button>
</div> </div>
{/* Contenu avec scroll vertical uniquement */} {/* Contenu avec scroll vertical uniquement */}
<div className="overflow-y-auto overflow-x-hidden max-h-[90vh] popup-content"> <div className="overflow-y-auto overflow-x-hidden max-h-[90vh] popup-content">
{/* Header */} {/* Header */}
@ -94,7 +100,7 @@ export const Popup: React.FC<PopupProps> = ({ service, onClose, translations })
{/* Fonctionnalités */} {/* Fonctionnalités */}
<h3 className="text-xl sm:text-2xl lg:text-3xl mb-4 lg:mb-6 text-banquise-blue-dark font-heading font-bold flex items-center"> <h3 className="text-xl sm:text-2xl lg:text-3xl mb-4 lg:mb-6 text-banquise-blue-dark font-heading font-bold flex items-center">
<span className="text-xl sm:text-2xl lg:text-3xl mr-3"></span> <span className="text-xl sm:text-2xl lg:text-3xl mr-3"></span>
{translations.discoverFeatures} Fonctionnalités principales
</h3> </h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
{service.features.map((feature, index) => ( {service.features.map((feature, index) => (
@ -118,7 +124,7 @@ export const Popup: React.FC<PopupProps> = ({ service, onClose, translations })
<span className="mr-3 text-xl lg:text-2xl">🚀</span> <span className="mr-3 text-xl lg:text-2xl">🚀</span>
<span>Accéder à {service.name}</span> <span>Accéder à {service.name}</span>
</a> </a>
<p className="text-center text-sm text-banquise-blue-dark/60 mt-4"> <p className="text-center text-sm text-banquise-blue-dark/60 mt-4">
Besoin d'aide ? Rejoignez notre <a href={URLS.social.discord} className="text-banquise-blue hover:text-banquise-blue-dark transition-colors duration-200 font-medium">Discord</a> pour obtenir du support Besoin d'aide ? Rejoignez notre <a href={URLS.social.discord} className="text-banquise-blue hover:text-banquise-blue-dark transition-colors duration-200 font-medium">Discord</a> pour obtenir du support
</p> </p>

View File

@ -3,15 +3,10 @@ export const URLS = {
wiki: "https://wiki.la-banquise.fr", wiki: "https://wiki.la-banquise.fr",
gitea: "https://git.la-banquise.fr", gitea: "https://git.la-banquise.fr",
panel: "https://panel.la-banquise.fr", panel: "https://panel.la-banquise.fr",
auth: "https://auth.la-banquise.fr", auth: "https://auth.la-banquise.fr"
pelican: "https://pelican.la-banquise.fr",
intra: "https://intra.la-banquise.fr",
mails: "https://mails.la-banquise.fr",
opencloud: "https://opencloud.la-banquise.fr",
ssp: "https://ssp.la-banquise.fr"
}, },
social: { social: {
discord: "https://discord.gg/bJhM97wans" discord: "https://discord.gg/labanquise"
}, },
contact: { contact: {
email: "mailto:contact@la-banquise.fr" email: "mailto:contact@la-banquise.fr"

View File

@ -1,153 +0,0 @@
import type { Translation } from '../../types/i18n';
import { URLS } from '../../config/constants';
export const en: Translation = {
services: [
{
name: "Wiki",
url: URLS.services.wiki,
image: "/path/to/wiki-image.jpg",
icon: "📚",
description: "Collaborative technical documentation and knowledge sharing platform. Create, edit and organize your guides, tutorials and documentation as a team with integrated versioning system.",
features: [
"Advanced markdown editor with real-time preview",
"Versioning system to track changes",
"Real-time collaboration with multiple contributors",
"Smart search across all documents",
"Predefined templates for different documentation types",
"Comment and revision system",
"PDF and HTML export for external sharing",
"Git integration for backup"
]
},
{
name: "Gitea",
url: URLS.services.gitea,
image: "/path/to/gitea-image.jpg",
icon: "🔧",
description: "Lightweight and performant self-hosted Git service for your development projects. Open-source alternative to GitHub with all essential features for managing your repositories.",
features: [
"Unlimited public and private Git repositories",
"Intuitive web interface for project management",
"Issues and pull requests with review system",
"Integrated wiki for each project",
"CI/CD actions for automation",
"Fine-grained permissions and team management",
"Complete REST API for integration",
"Webhooks for external notifications"
]
},
{
name: "Gaming Panel",
url: URLS.services.panel,
image: "/path/to/panel-image.jpg",
icon: "🎮",
description: "Centralized management interface for all your game servers. Easily deploy, configure and monitor your Minecraft, CS2, Garry's Mod and many other servers.",
features: [
"Support for 20+ popular games (Minecraft, CS2, GMod...)",
"One-click deployment with pre-configured templates",
"Real-time administration console",
"File management with integrated editor",
"Performance and resource monitoring",
"Automatic backup system",
"Automated task scheduler"
]
},
{
name: "Pelican",
url: URLS.services.pelican,
image: "/path/to/pelican-image.jpg",
icon: "🐧",
description: "Ultra-fast and flexible Python static site generator. Create blogs, portfolios or documentation sites with optimal performance and simplified deployment.",
features: [
"Game server management with dedicated servers (Minecraft, CS2, Palworld...)",
"One-click deployment with pre-configured templates",
"Real-time administration console",
"File management with integrated editor",
"Performance and resource monitoring",
"Automatic backup system",
"Automated task scheduler"
]
},
{
name: "Intranet",
url: URLS.services.intra,
image: "/path/to/intra-image.jpg",
icon: "🏢",
description: "Secure private space for the association to centralize internal resources, communications and collaboration tools between members.",
features: [
"Personalized dashboard for each member",
"Event and meeting calendar",
"Secure file sharing",
"Private discussion forums",
"Member directory with profiles",
"Internal notification system",
"Project and task management",
"Archive of decisions and minutes"
]
},
{
name: "Mails",
url: URLS.services.mails,
image: "/path/to/mails-image.jpg",
icon: "📧",
description: "Professional email service with modern web interface. Benefit from a personalized @la-banquise.fr email address with all advanced features.",
features: [
"Personalized @la-banquise.fr email addresses",
"Modern and responsive webmail interface",
"Integrated anti-spam and antivirus filters",
"Synchronized contacts and calendar",
"IMAP/SMTP support for external clients",
"Generous storage with archiving",
"Communication encryption",
"Automatic data backup"
]
},
{
name: "Password Change",
url: URLS.services.ssp,
image: "/path/to/ssp-image.jpg",
icon: "🔐",
description: "Secure interface for autonomous password management. Easily change your credentials safely.",
features: [
"Secure interface to change your password",
"Password complexity validation",
"Email notifications of changes",
"Modification history"
]
},
{
name: "OpenCloud",
url: URLS.services.opencloud,
image: "/path/to/opencloud-image.jpg",
icon: "☁️",
description: "Open-source collaborative cloud platform for file storage, sharing and synchronization. Free alternative to Google Drive with full control over your data.",
features: [
"Secure and encrypted cloud storage",
"Multi-device synchronization",
"File sharing with secure links",
"Collaborative document editing",
"Automatic file versioning",
"Native mobile applications",
"Integration with external tools",
"Geo-redundant data backup"
]
}
],
hero: {
title: "Welcome to La Banquise",
subtitle: "Your trusted technology partner",
cta: "Discover our services"
},
navigation: {
home: "Home",
services: "Services",
about: "About",
contact: "Contact"
},
common: {
discoverFeatures: "Discover all features",
close: "Close",
loading: "Loading..."
}
};

View File

@ -1,153 +0,0 @@
import type { Translation } from '../../types/i18n';
import { URLS } from '../../config/constants';
export const fr: Translation = {
services: [
{
name: "Wiki",
url: URLS.services.wiki,
image: "/path/to/wiki-image.jpg",
icon: "📚",
description: "Plateforme collaborative de documentation technique et de partage de connaissances. Créez, modifiez et organisez vos guides, tutoriels et documentations en équipe avec un système de versioning intégré.",
features: [
"Éditeur markdown avancé avec prévisualisation en temps réel",
"Système de versioning pour suivre les modifications",
"Collaboration en temps réel avec plusieurs contributeurs",
"Recherche intelligente dans tous les documents",
"Templates prédéfinis pour différents types de documentation",
"Système de commentaires et de révisions",
"Export PDF et HTML pour partage externe",
"Intégration avec Git pour la sauvegarde"
]
},
{
name: "Gitea",
url: URLS.services.gitea,
image: "/path/to/gitea-image.jpg",
icon: "🔧",
description: "Service Git auto-hébergé lightweight et performant pour vos projets de développement. Alternative open-source à GitHub avec toutes les fonctionnalités essentielles pour gérer vos repositories.",
features: [
"Repositories Git illimités publics et privés",
"Interface web intuitive pour la gestion des projets",
"Issues et pull requests avec système de review",
"Wiki intégré pour chaque projet",
"Actions CI/CD pour l'automatisation",
"Gestion fine des permissions et des équipes",
"API REST complète pour l'intégration",
"Webhooks pour les notifications externes"
]
},
{
name: "Panel Gaming",
url: URLS.services.panel,
image: "/path/to/panel-image.jpg",
icon: "🎮",
description: "Interface de gestion centralisée pour tous vos serveurs de jeux. Déployez, configurez et surveillez facilement vos serveurs Minecraft, CS2, Garry's Mod et bien d'autres.",
features: [
"Support de 20+ jeux populaires (Minecraft, CS2, GMod...)",
"Déploiement en un clic avec templates préconfigurés",
"Console d'administration en temps réel",
"Gestion des fichiers avec éditeur intégré",
"Monitoring des performances et ressources",
"Système de sauvegarde automatique",
"Planificateur de tâches automatisées"
]
},
{
name: "Pelican",
url: URLS.services.pelican,
image: "/path/to/pelican-image.jpg",
icon: "🐧",
description: "Générateur de sites statiques Python ultra-rapide et flexible. Créez des blogs, portfolios ou sites de documentation avec une performance optimale et un déploiement simplifié.",
features: [
"Gestion de serveurs de jeux avec serveurs dédiés (Minecraft, CS2, Palworld...)",
"Déploiement en un clic avec templates préconfigurés",
"Console d'administration en temps réel",
"Gestion des fichiers avec éditeur intégré",
"Monitoring des performances et ressources",
"Système de sauvegarde automatique",
"Planificateur de tâches automatisées"
]
},
{
name: "Intranet",
url: URLS.services.intra,
image: "/path/to/intra-image.jpg",
icon: "🏢",
description: "Espace privé sécurisé de l'association pour centraliser les ressources internes, communications et outils de collaboration entre membres.",
features: [
"Tableau de bord personnalisé pour chaque membre",
"Calendrier des événements et réunions",
"Partage de fichiers sécurisé",
"Forums de discussion privés",
"Annuaire des membres avec profils",
"Système de notifications internes",
"Gestion des projets et tâches",
"Archive des décisions et procès-verbaux"
]
},
{
name: "Mails",
url: URLS.services.mails,
image: "/path/to/mails-image.jpg",
icon: "📧",
description: "Service de messagerie électronique professionnel avec interface web moderne. Bénéficiez d'une adresse email personnalisée @la-banquise.fr avec toutes les fonctionnalités avancées.",
features: [
"Adresses email personnalisées @la-banquise.fr",
"Interface webmail moderne et responsive",
"Filtres anti-spam et antivirus intégrés",
"Contacts et calendrier synchronisés",
"Support IMAP/SMTP pour clients externes",
"Stockage généreux avec archivage",
"Chiffrement des communications",
"Sauvegarde automatique des données"
]
},
{
name: "Changement de mot de passe",
url: URLS.services.ssp,
image: "/path/to/ssp-image.jpg",
icon: "🔐",
description: "Interface sécurisée pour la gestion autonome de vos mots de passe. Changez facilement vos identifiants en toute sécurité.",
features: [
"Interface sécurisée pour changer votre mot de passe",
"Validation de la complexité des mots de passe",
"Notifications par email des changements",
"Historique des modifications"
]
},
{
name: "OpenCloud",
url: URLS.services.opencloud,
image: "/path/to/opencloud-image.jpg",
icon: "☁️",
description: "Plateforme cloud collaborative open-source pour le stockage, le partage et la synchronisation de fichiers. Alternative libre à Google Drive avec contrôle total sur vos données.",
features: [
"Stockage cloud sécurisé et chiffré",
"Synchronisation multi-appareils",
"Partage de fichiers avec liens sécurisés",
"Édition collaborative de documents",
"Versioning automatique des fichiers",
"Applications mobiles natives",
"Intégration avec outils externes",
"Sauvegarde géoredondante des données"
]
}
],
hero: {
title: "Bienvenue chez La Banquise",
subtitle: "Votre partenaire technologique de confiance",
cta: "Découvrir nos services"
},
navigation: {
home: "Accueil",
services: "Services",
about: "À propos",
contact: "Contact"
},
common: {
discoverFeatures: "Découvrir toutes les fonctionnalités",
close: "Fermer",
loading: "Chargement..."
}
};

View File

@ -1,14 +0,0 @@
import { fr } from './fr';
import { en } from './en';
import type { Language, Translation } from '../../types/i18n';
export const translations: Record<Language, Translation> = {
fr,
en,
// Ajoutez d'autres langues ici :
// es,
// de,
};
export const defaultLanguage: Language = 'fr';
export const availableLanguages: Language[] = ['fr', 'en'];

View File

@ -1,31 +0,0 @@
import { useState, useEffect } from 'react';
import type { Language, Translation } from '../types/i18n';
import { translations, defaultLanguage } from '../data/translations';
export const useTranslation = () => {
const [currentLanguage, setCurrentLanguage] = useState<Language>(() => {
// Récupérer la langue depuis localStorage ou utiliser la langue par défaut
const saved = localStorage.getItem('language') as Language;
return saved && translations[saved] ? saved : defaultLanguage;
});
const [t, setT] = useState<Translation>(translations[currentLanguage]);
useEffect(() => {
setT(translations[currentLanguage]);
localStorage.setItem('language', currentLanguage);
}, [currentLanguage]);
const changeLanguage = (language: Language) => {
if (translations[language]) {
setCurrentLanguage(language);
}
};
return {
t,
currentLanguage,
changeLanguage,
availableLanguages: Object.keys(translations) as Language[]
};
};

View File

@ -1,23 +0,0 @@
import type { Service } from './service';
export interface Translation {
services: Service[];
hero: {
title: string;
subtitle: string;
cta: string;
};
navigation: {
home: string;
services: string;
about: string;
contact: string;
};
common: {
discoverFeatures: string;
close: string;
loading: string;
};
}
export type Language = 'fr' | 'en'; //| 'es' | 'de';

View File

@ -1,8 +0,0 @@
export interface Service {
name: string;
url: string;
image: string;
icon: string;
description: string;
features: string[];
}

6
package-lock.json generated
View File

@ -1,6 +0,0 @@
{
"name": "website-front",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}