diff --git a/README.md b/README.md index 1b32d9d..623c378 100644 --- a/README.md +++ b/README.md @@ -76,10 +76,12 @@ voituresAPI-Front/ | Méthode | Route | Action correspondante | Accès | |---|---|---|---| -| `GET` | `/` ou `/cars` | Liste tous les véhicules (`GET /cars`) | Public | +| `GET` | `/` | Page d'accueil & présentation du projet + bouton création | Public | +| `GET` | `/cars` | Parc automobile complet (`GET /cars`) ou mes véhicules (`GET /cars/mes-voitures`) selon rôle | Authentifié (Admin / User) | | `GET` | `/cars?mine=1` | Liste mes véhicules (`GET /cars/mes-voitures`) | Authentifié | +| `GET` | `/cars?immatriculation=...` | Recherche par plaque (`GET /cars/immatriculation/{plaque}`) | Admin uniquement | | `GET` | `/cars/{id}` | Fiche détaillée avec km et entretiens (`GET /cars/{id}`) | Public | -| `GET` | `/cars/create` | Formulaire d'ajout d'un véhicule | Authentifié | +| `GET` | `/cars/create` | Formulaire d'ajout d'un véhicule | Authentifié (Connexion requise) | | `POST`| `/cars/create` | Envoi à l'API (`POST /cars`) | Authentifié | | `GET` | `/cars/{id}/edit` | Formulaire d'édition | Propriétaire / Admin | | `POST`| `/cars/{id}/edit` | Mise à jour dans l'API (`PUT /cars/{id}`) | Propriétaire / Admin | @@ -87,12 +89,21 @@ voituresAPI-Front/ | `POST`| `/cars/{id}/kilometrage` | Ajout relevé km (`POST /cars/{id}/kilometrage`) | Propriétaire / Admin | | `POST`| `/cars/{id}/maintenance` | Ajout entretien (`POST /cars/{id}/maintenance`) | Propriétaire / Admin | | `POST`| `/cars/{id}/notes` | Ajout note de suivi (`POST /cars/{id}/notes`) | Propriétaire / Admin | -| `GET` | `/login` / `POST /login` | Connexion (`POST /auth/login`) | Public | +| `GET` | `/login` / `POST /login` | Connexion (`POST /auth/login`) avec support de redirection (`?redirect=...`) | Public | | `GET` | `/register` / `POST /register` | Inscription (`POST /auth/register`) | Public | | `GET` | `/logout` | Déconnexion (`POST /auth/logout`) | Authentifié | --- +## 🔒 Sécurité & Privilèges (API REST v1.1.0) + +Conformément à la version **1.1.0** de l'API : +- **Privatisation de `GET /cars`** : L'accès à la flotte complète est restreint au rôle `admin` (`401` si non authentifié, `403` si `user`). Les utilisateurs accèdent automatiquement à leurs véhicules déclarés (`GET /cars/mes-voitures`). +- **Privatisation de la recherche d'immatriculation** : `GET /cars/immatriculation/{plaque}` est réservé aux administrateurs. +- **Page d'accueil dédiée** : Route `/` présentant le projet et disposant d'un appel à l'action pour créer une voiture (orientant les visiteurs non connectés vers la page d'authentification). + +--- + ## 💻 Démarrage du serveur Assurez-vous que l'API tourne sur `http://localhost:8888`. diff --git a/public/assets/css/style.css b/public/assets/css/style.css index 4b6b47e..4b4ff52 100644 --- a/public/assets/css/style.css +++ b/public/assets/css/style.css @@ -926,3 +926,300 @@ body { border-radius: 4px; color: #0f172a; } + +/* ========================================================================== + Homepage / Hero & Features Styles + ========================================================================== */ + +.hero-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: 3.5rem 2.5rem; + text-align: center; + box-shadow: var(--shadow-md); + margin-bottom: 3rem; + position: relative; + overflow: hidden; +} + +.hero-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, #2563eb, #3b82f6, #60a5fa); +} + +.hero-badge-wrapper { + display: inline-flex; + margin-bottom: 1.5rem; +} + +.hero-badge { + display: inline-flex; + align-items: center; + gap: 0.5rem; + font-size: 0.85rem; + font-weight: 700; + padding: 0.35rem 0.9rem; + border-radius: 9999px; + letter-spacing: 0.02em; +} + +.badge-online { + background: #ecfdf5; + color: #065f46; + border: 1px solid #a7f3d0; +} + +.badge-offline { + background: #fef2f2; + color: #991b1b; + border: 1px solid #fecaca; +} + +.hero-title { + font-size: 2.5rem; + font-weight: 800; + line-height: 1.25; + color: var(--text-main); + margin-bottom: 1.25rem; + letter-spacing: -0.02em; +} + +.hero-title-highlight { + background: linear-gradient(135deg, #2563eb, #1d4ed8); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.hero-lead { + font-size: 1.15rem; + color: var(--text-muted); + max-width: 820px; + margin: 0 auto 2.25rem auto; + line-height: 1.7; +} + +.hero-cta-group { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + flex-wrap: wrap; + margin-bottom: 1.5rem; +} + +.btn-cta { + padding: 0.85rem 1.75rem; + font-size: 1.05rem; + font-weight: 700; + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); + transition: transform 0.15s ease, box-shadow 0.15s ease; +} + +.btn-cta:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.hero-requirement-badge { + display: inline-flex; + align-items: center; + gap: 0.6rem; + background: #fffbeb; + border: 1px solid #fde68a; + color: #92400e; + padding: 0.6rem 1.25rem; + border-radius: 9999px; + font-size: 0.9rem; + margin-top: 0.5rem; +} + +.hero-note { + font-size: 0.95rem; + color: var(--text-muted); + margin-top: 0.5rem; +} + +/* Features Grid */ +.section-title-wrap { + text-align: center; + margin-bottom: 2.5rem; +} + +.section-title { + font-size: 1.85rem; + font-weight: 800; + color: var(--text-main); + margin-bottom: 0.5rem; +} + +.section-subtitle { + font-size: 1rem; + color: var(--text-muted); + max-width: 650px; + margin: 0 auto; +} + +.features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 1.75rem; + margin-bottom: 3rem; +} + +.feature-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: 2rem 1.75rem; + transition: transform 0.2s ease, box-shadow 0.2s ease; + display: flex; + flex-direction: column; +} + +.feature-card:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-md); +} + +.feature-icon-wrapper { + width: 52px; + height: 52px; + border-radius: var(--radius-md); + background: #eff6ff; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.6rem; + margin-bottom: 1.25rem; +} + +.feature-title { + font-size: 1.2rem; + font-weight: 700; + color: var(--text-main); + margin-bottom: 0.6rem; +} + +.feature-desc { + font-size: 0.92rem; + color: var(--text-muted); + line-height: 1.6; + flex: 1; +} + +/* Security & Changelog Section */ +.security-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: 2.5rem; + margin-bottom: 3rem; + box-shadow: var(--shadow-sm); +} + +.security-header { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1.5rem; + padding-bottom: 1rem; + border-bottom: 1px solid var(--border-color); +} + +.security-icon { + font-size: 2rem; +} + +.security-rules-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.25rem; +} + +.security-rule-box { + background: #f8fafc; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + padding: 1.25rem; +} + +.security-rule-title { + font-size: 0.95rem; + font-weight: 700; + margin-bottom: 0.4rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.security-rule-desc { + font-size: 0.88rem; + color: var(--text-muted); + line-height: 1.5; +} + +/* Demo accounts banner */ +.demo-banner { + background: linear-gradient(135deg, #1e293b, #0f172a); + color: #ffffff; + border-radius: var(--radius-lg); + padding: 2.5rem; + margin-bottom: 3rem; + display: flex; + align-items: center; + justify-content: space-between; + gap: 2rem; + flex-wrap: wrap; +} + +.demo-banner-content h3 { + font-size: 1.4rem; + font-weight: 800; + margin-bottom: 0.5rem; +} + +.demo-banner-content p { + color: #94a3b8; + font-size: 0.95rem; + max-width: 600px; +} + +.demo-banner-buttons { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; +} + +.btn-demo { + background: rgba(255, 255, 255, 0.1); + color: #ffffff; + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.btn-demo:hover { + background: rgba(255, 255, 255, 0.2); + color: #ffffff; +} + +@media (max-width: 768px) { + .hero-card { + padding: 2.5rem 1.5rem; + } + .hero-title { + font-size: 1.9rem; + } + .hero-lead { + font-size: 1rem; + } + .features-grid { + grid-template-columns: 1fr; + } +} + diff --git a/public/index.php b/public/index.php index c29ed8d..a3c1ee0 100644 --- a/public/index.php +++ b/public/index.php @@ -30,11 +30,15 @@ require_once __DIR__ . '/../src/Core/Autoloader.php'; \App\Core\Autoloader::register(); use App\Core\Router; +use App\Controllers\HomeController; use App\Controllers\CarController; use App\Controllers\AuthController; $router = new Router(); +// --- Page d'accueil (Présentation du projet) --- +$router->get('/', [HomeController::class, 'index']); + // --- Routes d'authentification --- $router->get('/login', [AuthController::class, 'login']); $router->post('/login', [AuthController::class, 'authenticate']); @@ -43,7 +47,6 @@ $router->post('/register', [AuthController::class, 'storeUser']); $router->get('/logout', [AuthController::class, 'logout']); // --- Routes des véhicules --- -$router->get('/', [CarController::class, 'index']); $router->get('/cars', [CarController::class, 'index']); // Création d'un véhicule diff --git a/src/Controllers/AuthController.php b/src/Controllers/AuthController.php index afce566..71654c8 100644 --- a/src/Controllers/AuthController.php +++ b/src/Controllers/AuthController.php @@ -21,13 +21,17 @@ class AuthController extends BaseController */ public function login(): void { + $redirect = trim((string)($_GET['redirect'] ?? '')); + if (!empty($_SESSION['user'])) { - $this->redirect('/cars'); + $target = (!empty($redirect) && str_starts_with($redirect, '/') && !str_starts_with($redirect, '//')) ? $redirect : '/cars'; + $this->redirect($target); return; } $this->render('auth/login', [ - 'title' => 'Connexion à l\'espace gestionnaire' + 'title' => 'Connexion à l\'espace gestionnaire', + 'redirect' => $redirect ]); } @@ -38,10 +42,11 @@ class AuthController extends BaseController { $username = trim($_POST['username'] ?? ''); $password = (string)($_POST['password'] ?? ''); + $redirect = trim((string)($_POST['redirect'] ?? '')); if (empty($username) || empty($password)) { $this->setFlash('danger', 'Veuillez saisir votre nom d\'utilisateur et mot de passe.'); - $this->redirect('/login'); + $this->redirect('/login' . (!empty($redirect) ? '?redirect=' . urlencode($redirect) : '')); return; } @@ -55,10 +60,15 @@ class AuthController extends BaseController $_SESSION['user'] = $response['data']['user']; $this->setFlash('success', "Bienvenue, {$response['data']['user']['nom']} ! Connecté en tant que " . ucfirst($response['data']['user']['role'])); - $this->redirect('/cars'); + + if (!empty($redirect) && str_starts_with($redirect, '/') && !str_starts_with($redirect, '//')) { + $this->redirect($redirect); + } else { + $this->redirect('/cars'); + } } else { $this->setFlash('danger', $response['error'] ?? 'Identifiants invalides'); - $this->redirect('/login'); + $this->redirect('/login' . (!empty($redirect) ? '?redirect=' . urlencode($redirect) : '')); } } @@ -116,6 +126,6 @@ class AuthController extends BaseController unset($_SESSION['token'], $_SESSION['user']); $this->setFlash('info', 'Vous avez été déconnecté avec succès.'); - $this->redirect('/cars'); + $this->redirect('/'); } } diff --git a/src/Controllers/BaseController.php b/src/Controllers/BaseController.php index 7adca4e..3bc27ab 100644 --- a/src/Controllers/BaseController.php +++ b/src/Controllers/BaseController.php @@ -26,9 +26,9 @@ class BaseController } // Chargement du layout global - require_once $baseViewDir . 'layout/header.php'; - require_once $viewFile; - require_once $baseViewDir . 'layout/footer.php'; + require $baseViewDir . 'layout/header.php'; + require $viewFile; + require $baseViewDir . 'layout/footer.php'; } /** diff --git a/src/Controllers/CarController.php b/src/Controllers/CarController.php index 55ee74f..972b49e 100644 --- a/src/Controllers/CarController.php +++ b/src/Controllers/CarController.php @@ -18,24 +18,52 @@ class CarController extends BaseController } /** - * Liste des véhicules avec support des filtres (mes voitures, immatriculation) + * Liste des véhicules avec support des rôles (API v1.1.0) */ public function index(): void { - $queryParams = []; - $isMineOnly = !empty($_GET['mine']); + $user = $_SESSION['user'] ?? null; + $token = $_SESSION['token'] ?? null; - if ($isMineOnly && !empty($_SESSION['token'])) { - $response = $this->apiService->get('/cars/mes-voitures'); - } elseif (!empty($_GET['immatriculation'])) { - $plate = trim((string)$_GET['immatriculation']); - $response = $this->apiService->get("/cars/immatriculation/{$plate}"); - } else { - $response = $this->apiService->get('/cars'); + // Si l'utilisateur n'est pas connecté : redirection vers la connexion + if (empty($token) || empty($user)) { + $this->setFlash('warning', 'La consultation des véhicules requiert d\'être connecté. Connectez-vous avec votre compte utilisateur ou administrateur.'); + $this->redirect('/login?redirect=/cars'); + return; } + $isAdmin = ($user['role'] ?? '') === 'admin'; + $searchedPlate = trim((string)($_GET['immatriculation'] ?? '')); + $isMineRequested = !empty($_GET['mine']); + $cars = []; $apiError = null; + $isMineOnly = false; + + // Règle API v1.1.0 : + // - Les utilisateurs standards (role: user) doivent utiliser GET /cars/mes-voitures + // - Seuls les administrateurs ont accès à GET /cars et GET /cars/immatriculation/{plaque} + if (!$isAdmin) { + $isMineOnly = true; + + if (!empty($searchedPlate)) { + $this->setFlash('warning', 'La recherche par plaque d\'immatriculation est réservée aux administrateurs (API v1.1.0).'); + $this->redirect('/cars'); + return; + } + + $response = $this->apiService->get('/cars/mes-voitures'); + } else { + // Administrateur + if ($isMineRequested) { + $isMineOnly = true; + $response = $this->apiService->get('/cars/mes-voitures'); + } elseif (!empty($searchedPlate)) { + $response = $this->apiService->get("/cars/immatriculation/{$searchedPlate}"); + } else { + $response = $this->apiService->get('/cars'); + } + } if ($response['success']) { $data = $response['data']; @@ -52,11 +80,12 @@ class CarController extends BaseController } $this->render('cars/index', [ - 'title' => 'Parc Automobile - Véhicules', - 'cars' => $cars, - 'apiError' => $apiError, - 'isMineOnly' => $isMineOnly, - 'searchedPlate' => $_GET['immatriculation'] ?? '' + 'title' => $isMineOnly ? 'Mes Véhicules' : 'Parc Automobile (Administration)', + 'cars' => $cars, + 'apiError' => $apiError, + 'isMineOnly' => $isMineOnly, + 'isAdmin' => $isAdmin, + 'searchedPlate' => $searchedPlate ]); } @@ -69,7 +98,7 @@ class CarController extends BaseController if (!$carResponse['success'] || empty($carResponse['data'])) { $this->setFlash('danger', $carResponse['error'] ?? "Véhicule #{$id} introuvable."); - $this->redirect('/cars'); + $this->redirect(empty($_SESSION['token']) ? '/' : '/cars'); return; } @@ -109,7 +138,7 @@ class CarController extends BaseController { if (empty($_SESSION['token'])) { $this->setFlash('warning', 'Veuillez vous connecter pour ajouter un véhicule.'); - $this->redirect('/login'); + $this->redirect('/login?redirect=/cars/create'); return; } @@ -128,7 +157,7 @@ class CarController extends BaseController { if (empty($_SESSION['token'])) { $this->setFlash('danger', 'Session expirée ou non connecté.'); - $this->redirect('/login'); + $this->redirect('/login?redirect=/cars/create'); return; } diff --git a/src/Controllers/HomeController.php b/src/Controllers/HomeController.php new file mode 100644 index 0000000..d8ea4d2 --- /dev/null +++ b/src/Controllers/HomeController.php @@ -0,0 +1,36 @@ +apiService = new ApiService(); + } + + /** + * Affiche la page d'accueil présentant le projet et ses fonctionnalités + */ + public function index(): void + { + // Interrogation de la racine de l'API pour récupérer les métadonnées et le statut + $apiResponse = $this->apiService->get('/'); + $apiOnline = $apiResponse['success'] ?? false; + $apiData = $apiResponse['data'] ?? null; + + $this->render('home/index', [ + 'title' => 'Accueil - Présentation du projet ' . APP_NAME, + 'apiOnline' => $apiOnline, + 'apiData' => $apiData, + 'currentUser' => $_SESSION['user'] ?? null + ]); + } +} diff --git a/src/Views/auth/login.php b/src/Views/auth/login.php index 682f14c..5733047 100644 --- a/src/Views/auth/login.php +++ b/src/Views/auth/login.php @@ -6,7 +6,14 @@
Accédez à la gestion de vos véhicules et carnets d'entretien
+ +