feat: Complete professional redesign of Lions Dev application

- Replaced amateur design with enterprise-level professional interface
- Implemented custom CSS framework replacing Tailwind dependency
- Created modern single-page HTML application with professional sections:
  * Hero section with floating elements and tech stack preview
  * Enterprise solutions showcase with 6 detailed service cards
  * Performance metrics with animated counters
  * Professional contact section with Lions Dev coordinates
  * Complete footer with company information

- Added professional features:
  * Glass morphism effects and modern animations
  * Responsive design with mobile-first approach
  * Smooth scrolling navigation with intersection observer
  * Professional typography using Inter font family
  * Lions Dev brand colors and gradient effects
  * Enterprise-grade JavaScript with performance monitoring

- Updated Lions Dev information:
  * Phone: +225 01 01 75 95 25
  * Email: contact@lions.dev
  * Location: Abidjan, Côte d'Ivoire
  * Domain: lions.dev

- Technical improvements:
  * Optimized CSS with utility classes and component architecture
  * Professional animations and micro-interactions
  * SEO optimized with proper meta tags and Open Graph
  * Accessibility compliant with WCAG standards
  * Performance optimized with minimal dependencies

Application now meets international professional standards and represents Lions Dev as a serious enterprise technology partner in Africa.
This commit is contained in:
DahoudG
2025-09-22 12:15:13 +00:00
parent 0ef111db6a
commit a256a25f3c
17 changed files with 1157 additions and 3515 deletions

View File

@@ -0,0 +1,44 @@
package dev.lions.beans;
import jakarta.enterprise.context.SessionScoped;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Named;
import java.io.Serializable;
import java.util.Locale;
/**
* Bean pour la gestion de la langue de l'application
*/
@Named("languageBean")
@SessionScoped
public class LanguageBean implements Serializable {
private static final long serialVersionUID = 1L;
private Locale locale = new Locale("fr", "FR");
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
FacesContext.getCurrentInstance().getViewRoot().setLocale(locale);
}
public String getLanguage() {
return locale.getLanguage();
}
public void changeLanguage(String language) {
switch (language) {
case "en":
setLocale(new Locale("en", "US"));
break;
case "fr":
default:
setLocale(new Locale("fr", "FR"));
break;
}
}
}

View File

@@ -1,374 +1,30 @@
package dev.lions.controllers;
import static java.time.LocalDateTime.ofInstant;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.event.Event;
import jakarta.faces.application.FacesMessage;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.io.Serial;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import dev.lions.models.*;
import dev.lions.services.*;
import dev.lions.events.AnalyticsEvent;
import dev.lions.utils.MessageUtils;
import dev.lions.exceptions.BusinessException;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.net.URI;
/**
* Contrôleur principal de la page d'accueil.
* Gère l'ensemble des fonctionnalités et données affichées sur la landing page.
* Contrôleur pour la page d'accueil
*/
@Slf4j
@Named
@RequestScoped
public class HomeController implements Serializable {
@Path("/")
public class HomeController {
@Serial
private static final long serialVersionUID = 1L;
private static final String DEFAULT_FILTER = "all";
private static final Map<String, Integer> COMPANY_STATS = initializeCompanyStats();
@Inject
ProjectService projectService;
@Inject
ContactService contactService;
@Inject
Event<AnalyticsEvent> analyticsEvent;
@Getter @Setter
private String selectedFilter = DEFAULT_FILTER;
@Getter
private List<Project> filteredProjects;
@Getter
private List<ExpertiseArea> expertiseAreas;
@Getter
private List<Service> services;
@Getter
private List<ProcessStep> processSteps;
@Getter
private String heroTitle = "Accélérez votre transformation digitale";
@Getter
private String heroDescription = "Des solutions innovantes pour booster votre croissance";
/**
* Initialise le contrôleur et charge les données nécessaires.
*/
@PostConstruct
public void init() {
try {
log.info("Initialisation du contrôleur Home");
initializeData();
updateFilteredProjects();
fireInitializationEvent();
log.info("Contrôleur Home initialisé avec succès");
} catch (Exception e) {
log.error("Erreur lors de l'initialisation du contrôleur", e);
MessageUtils.addErrorMessage("Erreur d'initialisation",
"Une erreur est survenue lors du chargement de la page");
@GET
@Produces(MediaType.TEXT_HTML)
public Response home() {
// Redirection vers la page HTML statique
return Response.seeOther(URI.create("/index.html")).build();
}
}
/**
* Met à jour la liste des projets selon le filtre sélectionné.
*/
public void updateFilteredProjects() {
log.debug("Mise à jour des projets avec le filtre: {}", selectedFilter);
try {
filteredProjects = projectService.getFilteredProjects(selectedFilter);
fireFilterUpdateEvent();
} catch (BusinessException be) {
log.warn("Erreur lors du filtrage des projets", be);
MessageUtils.addWarningMessage("Attention", be.getMessage());
} catch (Exception e) {
log.error("Erreur critique lors du filtrage", e);
MessageUtils.addErrorMessage("Erreur",
"Impossible de charger les projets");
filteredProjects = Collections.emptyList();
@GET
@Path("/public/index.xhtml")
@Produces(MediaType.TEXT_HTML)
public Response publicIndex() {
// Redirection depuis l'ancienne URL JSF vers la nouvelle page HTML
return Response.seeOther(URI.create("/index.html")).build();
}
}
/**
* Traite la soumission du formulaire de contact.
*
* @param form Le formulaire de contact soumis par l'utilisateur
*/
public void submitContactForm(@Valid @NotNull ContactForm form) {
log.info("Traitement du formulaire de contact");
try {
Contact contact = contactService.processContactForm(form);
fireContactSubmissionEvent(contact);
MessageUtils.addSuccessMessage("Message envoyé",
"Votre message a été envoyé avec succès");
} catch (BusinessException be) {
log.warn("Erreur de validation du formulaire", be);
MessageUtils.addWarningMessage("Validation", be.getMessage());
} catch (Exception e) {
log.error("Erreur lors du traitement du formulaire", e);
MessageUtils.addErrorMessage("Erreur",
"Impossible de traiter votre demande");
}
}
/**
* Initialise les données statiques du contrôleur.
*/
private void initializeData() {
expertiseAreas = initializeExpertiseAreas();
services = initializeServices();
processSteps = initializeProcessSteps();
}
/**
* Initialise la liste des domaines d'expertise.
*
* @return La liste des domaines d'expertise
*/
private List<ExpertiseArea> initializeExpertiseAreas() {
log.debug("Initialisation des domaines d'expertise");
return List.of(
createExpertiseArea(
"Développement Sur Mesure",
"fa-code",
"Solutions logicielles adaptées à vos besoins",
List.of(
"Applications web modernes",
"Logiciels métier optimisés",
"APIs performantes"
)
),
createExpertiseArea(
"Intégration de Solutions",
"fa-puzzle-piece",
"Déploiement et personnalisation",
List.of(
"Intégration CRM & ERP",
"Plateformes e-commerce",
"Solutions collaboratives"
)
)
);
}
/**
* Crée un nouveau domaine d'expertise.
*
* @param title Le titre du domaine
* @param icon L'icône représentant le domaine
* @param description La description du domaine
* @param features Les fonctionnalités et avantages du domaine
* @return Le nouveau domaine d'expertise créé
*/
private ExpertiseArea createExpertiseArea(String title, String icon,
String description, List<String> features) {
return ExpertiseArea.builder()
.title(title)
.icon(icon)
.description(description)
.features(features)
.priority(1)
.build();
}
/**
* Initialise la liste des services proposés.
*
* @return La liste des services
*/
private List<Service> initializeServices() {
log.debug("Initialisation des services");
return List.of(
createService(
"consulting",
"Conseil & Stratégie",
"fa-chart-line",
"Accompagnement stratégique",
List.of(
"Audit technique",
"Feuille de route digitale",
"Optimisation des processus"
)
),
createService(
"development",
"Développement",
"fa-laptop-code",
"Création de solutions techniques",
List.of(
"Développement agile",
"Intégration continue",
"Support technique"
)
)
);
}
/**
* Crée un nouveau service.
*
* @param id L'identifiant unique du service
* @param title Le titre du service
* @param icon L'icône représentant le service
* @param description La description du service
* @param benefits Les avantages et bénéfices du service
* @return Le nouveau service créé
*/
private Service createService(String id, String title, String icon,
String description, List<String> benefits) {
return Service.builder()
.id(id)
.title(title)
.icon(icon)
.description(description)
.benefits(benefits)
.build();
}
/**
* Initialise la liste des étapes du processus.
*
* @return La liste des étapes du processus
*/
private List<ProcessStep> initializeProcessSteps() {
log.debug("Initialisation des étapes du processus");
return List.of(
createProcessStep(1, "Discovery", "Analyse des besoins", "2-3 semaines"),
createProcessStep(2, "Conception", "Élaboration de la solution", "3-4 semaines"),
createProcessStep(3, "Développement", "Création de la solution", "8-12 semaines"),
createProcessStep(4, "Déploiement", "Mise en production", "2-3 semaines")
);
}
/**
* Crée une nouvelle étape du processus.
*
* @param number Le numéro de l'étape
* @param title Le titre de l'étape
* @param description La description de l'étape
* @param duration La durée estimée de l'étape
* @return La nouvelle étape créée
*/
private ProcessStep createProcessStep(int number, String title,
String description, String duration) {
return ProcessStep.builder()
.number(number)
.title(title)
.description(description)
.duration(duration)
.build();
}
/**
* Initialise la map des statistiques de l'entreprise.
*
* @return La map contenant les statistiques clés
*/
private static Map<String, Integer> initializeCompanyStats() {
Map<String, Integer> stats = new ConcurrentHashMap<>();
stats.put("Projets Réalisés", 150);
stats.put("Clients Satisfaits", 80);
stats.put("Experts Techniques", 25);
return Collections.unmodifiableMap(stats);
}
/**
* Déclenche l'événement d'initialisation de la page d'accueil.
*/
private void fireInitializationEvent() {
analyticsEvent.fire(AnalyticsEvent.builder()
.eventType("HOME_INITIALIZED")
.timestamp(ofInstant(Instant.ofEpochMilli(Long.MAX_VALUE),
ZoneId.systemDefault()))
.build());
}
/**
* Déclenche l'événement de mise à jour du filtre des projets.
*/
private void fireFilterUpdateEvent() {
analyticsEvent.fire(AnalyticsEvent.builder()
.eventType("PROJECTS_FILTERED")
.properties(Map.of("filter", selectedFilter))
.build());
}
/**
* Déclenche l'événement de soumission du formulaire de contact.
*
* @param contact Le contact créé suite à la soumission du formulaire
*/
private void fireContactSubmissionEvent(Contact contact) {
analyticsEvent.fire(AnalyticsEvent.builder()
.eventType("CONTACT_SUBMITTED")
.contactId(contact.getId().toString())
.build());
}
/**
* Retourne le message d'accueil de la page d'accueil.
*
* @return Le message d'accueil
*/
public String getWelcomeMessage() {
return "Bienvenue chez Lions Dev";
}
/**
* Retourne le sous-titre affiché dans la section héros.
*
* @return Le sous-titre de la section héros
*/
public String getHeroSubtitle() {
return "Solutions digitales sur mesure";
}
/**
* Retourne la liste des filtres disponibles pour les projets.
*
* @return La liste des filtres de projets
*/
public List<String> getProjectFilters() {
return List.of("Tous", "Web", "Mobile", "Cloud", "IA");
}
/**
* Retourne les statistiques clés de l'entreprise.
*
* @return La map contenant les statistiques de l'entreprise
*/
public Map<String, Integer> getCompanyStats() {
return COMPANY_STATS;
}
}
}

View File

@@ -1,164 +0,0 @@
package dev.lions.controllers;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import dev.lions.config.ApplicationConfig;
import dev.lions.events.AnalyticsEvent;
import dev.lions.services.ProjectService;
import dev.lions.exceptions.InitializationException;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
/**
* Contrôleur de la page d'index.
* Gère l'affichage et les interactions de la page d'accueil principale
* en assurant le chargement des messages localisés et des données nécessaires.
*/
@Slf4j
@Named
@RequestScoped
public class IndexController {
private static final String BUNDLE_NAME = "messages";
private static final String DEFAULT_LOCALE = "fr";
@Inject
ApplicationConfig applicationConfig;
@Inject
ProjectService projectService;
@Inject
Event<AnalyticsEvent> analyticsEvent;
@Getter
private String welcomeMessage;
@Getter
private String applicationName;
@Getter
private String environment;
@Getter @Setter
private String message;
private ResourceBundle messageBundle;
@PostConstruct
public void init() {
log.info("Initialisation du contrôleur Index");
try {
initializeMessageBundle();
initializeApplicationSettings();
notifyPageView();
log.info("Contrôleur Index initialisé avec succès");
} catch (Exception e) {
String errorMessage = "Erreur lors de l'initialisation du contrôleur Index";
log.error(errorMessage, e);
throw new InitializationException(errorMessage, e);
}
}
/**
* Récupère un message localisé avec une clé donnée.
* La méthode est maintenant protégée pour permettre l'interception par CDI
*/
protected String getMessage(@NotNull String key) {
try {
return messageBundle.getString(key);
} catch (Exception e) {
log.warn("Message non trouvé pour la clé: {}", key);
return key;
}
}
/**
* Vérifie si l'environnement est en mode développement.
*/
public boolean isDevelopmentMode() {
return applicationConfig.isDevelopment();
}
/**
* Récupère le nombre total de projets.
*/
public long getProjectCount() {
try {
return projectService.getProjectCount();
} catch (Exception e) {
log.warn("Erreur lors de la récupération du nombre de projets", e);
return 0;
}
}
/**
* Retourne un message de statut pour le mode développement.
*/
public String getStatusMessage() {
if (isDevelopmentMode()) {
return String.format("Mode développement - Environnement: %s", environment);
}
return "";
}
/**
* Recharge les messages et paramètres du contrôleur.
*/
public void refresh() {
log.info("Rafraîchissement des données du contrôleur Index");
initializeMessageBundle();
initializeApplicationSettings();
notifyPageView();
}
/**
* Initialise le bundle de messages localisés.
*/
protected void initializeMessageBundle() {
log.debug("Chargement des messages localisés");
Locale locale = new Locale(DEFAULT_LOCALE);
messageBundle = ResourceBundle.getBundle(BUNDLE_NAME, locale);
welcomeMessage = getMessage("welcome.message");
message = String.format("%s - %s",
getMessage("index.greeting"),
applicationConfig.getApplicationName());
log.debug("Messages localisés chargés avec succès");
}
/**
* Initialise les paramètres de l'application.
*/
protected void initializeApplicationSettings() {
log.debug("Chargement des paramètres de l'application");
applicationName = applicationConfig.getApplicationName();
environment = applicationConfig.getEnvironment();
log.debug("Paramètres de l'application chargés : env={}", environment);
}
/**
* Notifie le système d'une vue de page.
*/
protected void notifyPageView() {
analyticsEvent.fire(AnalyticsEvent.builder()
.eventType("PAGE_VIEW")
.properties(Map.of(
"page", "index",
"environment", environment,
"timestamp", System.currentTimeMillis()
))
.build());
}
}

View File

@@ -1,208 +0,0 @@
package dev.lions.controllers;
import jakarta.enterprise.context.SessionScoped;
import jakarta.enterprise.event.Event;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.validation.constraints.NotNull;
import java.io.Serial;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import dev.lions.events.NavigationEvent;
import dev.lions.services.ProjectService;
import java.io.Serializable;
import java.util.Map;
import java.util.HashMap;
import java.util.Optional;
/**
* Contrôleur de navigation principal de l'application.
* Gère les transitions entre les pages et la persistance des paramètres
* de navigation de manière sécurisée et optimisée.
*/
@Slf4j
@Named("navigationController")
@SessionScoped
public class NavigationController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private static final String DEFAULT_OUTCOME = "/public/index.xhtml";
@Inject
ProjectService projectService;
@Inject
Event<NavigationEvent> navigationEvent;
@Getter
private String currentPage;
private final Map<String, Object> navigationParams = new HashMap<>();
/**
* Redirige vers la page d'accueil avec rafraîchissement.
*
* @return Résultat de la navigation JSF
*/
public String goToHome() {
log.info("Navigation vers la page d'accueil");
notifyNavigation("home");
return DEFAULT_OUTCOME + "?faces-redirect=true";
}
/**
* Redirige vers la page des services.
*
* @return Résultat de la navigation JSF
*/
public String goToServices() {
log.info("Navigation vers la page des services");
notifyNavigation("services");
return "/private/services.xhtml?faces-redirect=true";
}
/**
* Redirige vers la page du portfolio.
*
* @return Résultat de la navigation JSF
*/
public String goToPortfolio() {
log.info("Navigation vers le portfolio");
notifyNavigation("portfolio");
return "/private/portfolio.xhtml?faces-redirect=true";
}
/**
* Redirige vers la page de détails d'un projet spécifique.
*
* @param projectId Identifiant du projet
* @return Résultat de la navigation JSF
*/
public String goToProjectDetails(@NotNull String projectId) {
log.info("Navigation vers les détails du projet: {}", projectId);
if (!projectService.existsById(projectId)) {
log.warn("Tentative d'accès à un projet inexistant: {}", projectId);
addErrorMessage();
return null;
}
notifyNavigation("project_details", Map.of("projectId", projectId));
return "/private/project-details.xhtml?faces-redirect=true&id=" + projectId;
}
/**
* Redirige vers la page de détails d'un service spécifique.
*
* @param serviceId Identifiant du service
* @return Résultat de la navigation JSF
*/
public String goToServiceDetails(@NotNull String serviceId) {
log.info("Navigation vers les détails du service: {}", serviceId);
notifyNavigation("service_details", Map.of("serviceId", serviceId));
return "/private/service-details.xhtml?faces-redirect=true&id=" + serviceId;
}
/**
* Redirige vers la page de contact.
*
* @return Résultat de la navigation JSF
*/
public String goToContact() {
log.info("Navigation vers la page de contact");
notifyNavigation("contact");
return "/private/contact.xhtml?faces-redirect=true";
}
/**
* Sauvegarde un paramètre pour la navigation.
*
* @param key Clé du paramètre
* @param value Valeur du paramètre
*/
public void setNavigationParameter(@NotNull String key, Object value) {
navigationParams.put(key, value);
log.debug("Paramètre de navigation défini: {} = {}", key, value);
}
/**
* Récupère un paramètre de navigation.
*
* @param key Clé du paramètre
* @return Valeur du paramètre ou null si non trouvé
*/
public Object getNavigationParameter(String key) {
return navigationParams.get(key);
}
/**
* Vérifie si la page actuelle correspond à un chemin donné.
*
* @param viewId Identifiant de la vue
* @return true si la page courante correspond
*/
public boolean isCurrentPage(String viewId) {
String currentViewId = FacesContext.getCurrentInstance()
.getViewRoot()
.getViewId();
return currentViewId.equals(viewId);
}
/**
* Nettoie les paramètres de navigation.
*/
public void clearNavigationParameters() {
navigationParams.clear();
log.debug("Paramètres de navigation nettoyés");
}
/**
* Initialise un nouveau contexte de navigation.
*/
public void initializeNavigation() {
currentPage = Optional.ofNullable(FacesContext.getCurrentInstance())
.map(context -> context.getViewRoot().getViewId())
.orElse(DEFAULT_OUTCOME);
log.info("Navigation initialisée sur la page: {}", currentPage);
}
/**
* Notifie le système d'un événement de navigation.
*/
private void notifyNavigation(String destination) {
notifyNavigation(destination, new HashMap<>());
}
/**
* Notifie le système d'un événement de navigation avec paramètres.
*/
private void notifyNavigation(String destination, Map<String, Object> params) {
NavigationEvent event = NavigationEvent.builder()
.source(currentPage)
.destination(destination)
.parameters(params)
.timestamp(System.currentTimeMillis())
.build();
navigationEvent.fire(event);
}
/**
* Ajoute un message d'erreur dans le contexte Faces.
*/
private void addErrorMessage() {
FacesContext.getCurrentInstance()
.addMessage(null,
new jakarta.faces.application.FacesMessage(
jakarta.faces.application.FacesMessage.SEVERITY_ERROR,
"Erreur", "Projet introuvable"
)
);
}
}