Add API, and object Article for api

This commit is contained in:
ExostFlash 2025-04-01 15:11:17 +02:00
parent 1f5890b4ff
commit f0fd146c54
19 changed files with 432 additions and 13 deletions

7
package-lock.json generated
View file

@ -17,6 +17,7 @@
"@angular/router": "^19.2.0",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3",
"file-saver": "^2.0.5",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
@ -7737,6 +7738,12 @@
"node": ">=0.8.0"
}
},
"node_modules/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
"license": "MIT"
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",

View file

@ -19,6 +19,7 @@
"@angular/router": "^19.2.0",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3",
"file-saver": "^2.0.5",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"

View file

@ -4,6 +4,7 @@ import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
/* Essential */
import { NavBarComponent } from './component/essential/nav-bar/nav-bar.component';
@ -11,6 +12,7 @@ import { FooterComponent } from './component/essential/footer/footer.component';
/* Home */
import { HomeComponent } from './component/home/home.component';
import { HomeActuComponent } from './component/home/all/home-actu/home-actu.component';
import { HomeAccueilComponent } from './component/home/all/home-accueil/home-accueil.component';
import { HomeSymptomsComponent } from './component/home/all/home-symptoms/home-symptoms.component';
@ -24,7 +26,6 @@ import { BlogsComponent } from './component/blogs/blogs.component';
import { ContactComponent } from './component/contact/contact.component';
@NgModule({
declarations: [
AppComponent,
@ -33,6 +34,7 @@ import { ContactComponent } from './component/contact/contact.component';
FooterComponent,
/* Home */
HomeComponent,
HomeActuComponent,
HomeAccueilComponent,
HomeSymptomsComponent,
/* Symptoms */
@ -46,7 +48,8 @@ import { ContactComponent } from './component/contact/contact.component';
BrowserModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule
ReactiveFormsModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]

View file

@ -0,0 +1,58 @@
/* Card Image */
.card-img-top {
height: 200px;
object-fit: cover;
width: 100%;
}
/* Assurer une taille uniforme pour les cards */
.card {
display: flex;
flex-direction: column;
height: 100%;
}
/* Body de la card pour avoir une hauteur fixe et uniforme */
.card-body {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 1.25rem;
height: 100%;
}
/* Titre de la card (limiter la taille du texte si trop long) */
.card-title {
font-size: 1.2rem;
font-weight: bold;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
max-height: 2.5em; /* Limite la hauteur du titre */
margin-bottom: 1rem;
}
/* Description de la card (maximiser l'espace disponible pour la description) */
.card-text {
font-size: 0.9rem;
color: #6c757d;
flex-grow: 1; /* Permet à la description de prendre l'espace restant */
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
margin-bottom: 1.5rem; /* Espacement entre la description et le bouton */
}
/* Bouton */
.btn-primary {
background-color: #007bff;
border-color: #007bff;
text-align: center;
}
.btn-primary:hover {
background-color: #0056b3;
border-color: #0056b3;
}

View file

@ -1 +1,16 @@
<p>blogs works!</p>
<div class="row row-cols-1 row-cols-md-3 g-4 mb-4">
<div *ngFor="let article of articles" class="col">
<div class="card h-100">
<img [src]="article.urlToImage || 'assets/default.jpg'" class="card-img-top" alt="Article image" loading="lazy">
<div class="card-body">
<h5 class="card-title">{{ article.title }}</h5>
<p class="card-text">{{ article.description }}</p>
<a [href]="article.url" target="_blank" class="btn btn-primary">Lire l'article</a>
</div>
</div>
</div>
</div>
<div *ngIf="articles.length === 0" class="alert alert-warning" role="alert">
Aucune actualité disponible pour le moment.
</div>

View file

@ -1,6 +1,7 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { DataService } from '@services/data.service';
import { Article } from '@interface/news';
@Component({
selector: 'app-blogs',
@ -8,10 +9,14 @@ import { DataService } from '@services/data.service';
templateUrl: './blogs.component.html',
styleUrl: './blogs.component.css'
})
export class BlogsComponent {
export class BlogsComponent implements OnInit {
articles: Article[] = [];
constructor(private dataService: DataService) {}
get blogs() {
return this.dataService.getBlogs();
ngOnInit(): void {
this.dataService.getNews().subscribe((response) => {
this.articles = response.articles;
});
}
}

View file

@ -0,0 +1,32 @@
/* Amélioration du formulaire */
.custom-input {
border: 1px solid #ced4da;
border-radius: 8px;
padding: 10px;
transition: border-color 0.3s ease-in-out, box-shadow 0.2s ease-in-out;
}
.custom-input:focus {
border-color: #007bff;
box-shadow: 0 0 5px rgba(0, 123, 255, 0.2);
outline: none;
}
/* Bouton amélioré */
.btn-primary {
background-color: #007bff;
border: none;
font-weight: 500;
transition: background 0.3s ease, transform 0.2s ease-in-out;
}
.btn-primary:hover {
background-color: #0056b3;
transform: scale(1.02);
}
/* Liste des centres */
.contact-card {
background: #f8f9fa;
border-radius: 10px;
}

View file

@ -1 +1,70 @@
<p>contact works!</p>
<div class="container mt-5">
<div class="text-center mb-5">
<h2 class="fw-bold text-primary">Contactez-nous</h2>
<p class="text-muted">Vous avez une question ? Contactez-nous via le formulaire ou rendez-vous dans l'un de nos centres.</p>
</div>
<div class="row g-4 mb-3">
<!-- Liste des centres -->
<div class="col-lg-6 mb-3">
<div class="card contact-card shadow-lg border-0 rounded-4">
<div class="card-body">
<h4 class="card-title text-primary mb-4">Nos Centres</h4>
<ul class="list-group list-group-flush">
<li class="list-group-item py-3 d-flex align-items-start" *ngFor="let address of addresses">
<i class="bi bi-geo-alt-fill text-primary me-3 fs-4"></i>
<div>
<h5 class="mb-1">{{ address.title }}</h5>
<p class="text-muted mb-1"><strong>Type :</strong> {{ address.type }}</p>
<p class="text-muted mb-0"><strong>Adresse :</strong> {{ address.address }}</p>
</div>
</li>
</ul>
</div>
</div>
</div>
<!-- Formulaire de contact -->
<div class="col-lg-6 mb-3">
<div class="card contact-form shadow-lg border-0 rounded-4">
<div class="card-body p-4">
<h4 class="card-title text-primary mb-4">Laissez-nous un message</h4>
<form>
<div class="mb-3">
<label for="name" class="form-label">Nom</label>
<div class="input-group">
<span class="input-group-text bg-light"><i class="bi bi-person-fill"></i></span>
<input type="text" class="form-control custom-input" id="name" placeholder="Votre nom">
</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<div class="input-group">
<span class="input-group-text bg-light"><i class="bi bi-envelope-fill"></i></span>
<input type="email" class="form-control custom-input" id="email" placeholder="Votre email">
</div>
</div>
<div class="mb-3">
<label for="subject" class="form-label">Sujet</label>
<div class="input-group">
<span class="input-group-text bg-light"><i class="bi bi-chat-left-text-fill"></i></span>
<input type="text" class="form-control custom-input" id="subject" placeholder="Sujet de votre message">
</div>
</div>
<div class="mb-3">
<label for="message" class="form-label">Message</label>
<textarea class="form-control custom-input" id="message" rows="4" placeholder="Votre message"></textarea>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 rounded-pill custom-btn">
<i class="bi bi-send-fill"></i> Envoyer
</button>
</form>
</div>
</div>
</div>
</div>
</div>

View file

@ -19,8 +19,8 @@
<a href="#" class="text-white me-3 SpaceMargin-02">Mentions légales</a>
<a href="#" class="text-white me-3">Cookies</a>
<a href="#" class="text-white me-3">Accessibilité</a>
<a routerLink="/contacts" class="text-white me-3">Nous contacter</a>
<a href="#" class="text-white me-3">Presse</a>
<a routerLink="/contact" class="text-white me-3">Nous contacter</a>
<a routerLink="/blogs" class="text-white me-3">Presse</a>
</div>
</div>
</footer>

View file

@ -0,0 +1,54 @@
#newsCarousel {
position: relative;
}
.carousel-item img {
width: 100%;
height: 350px; /* Ajuste la hauteur de l'image pour avoir une vue cohérente */
object-fit: cover;
border-radius: 10px;
}
.carousel-caption {
position: absolute;
bottom: 20px;
background-color: rgba(13, 110, 253, 0.7); /* Légère transparence pour le fond */
padding: 15px;
border-radius: 5px;
color: white;
}
.carousel-caption h5 {
font-size: 1.5rem;
font-weight: bold;
}
.carousel-caption p {
font-size: 1rem;
margin-top: 10px;
}
.carousel-caption .btn {
margin-top: 10px;
}
.carousel-control-prev,
.carousel-control-next {
background-color: transparent; /* Pas de fond sombre */
border-radius: 50%; /* Forme ronde */
border: none; /* Retirer la bordure */
}
.carousel-control-prev:hover,
.carousel-control-next:hover {
background-color: transparent; /* Pas de changement de fond au survol */
}
.carousel-control-next-icon-new svg {
transform: rotate(180deg); /* Rotation de 180° */
}
.carousel-control-prev-icon-new svg,
.carousel-control-next-icon-new svg {
fill: #0d95fd; /* Couleur de l'icône */
}

View file

@ -0,0 +1,41 @@
<div *ngIf="articles.length > 0" id="newsCarousel" class="carousel slide mb-4" data-bs-ride="carousel">
<div class="carousel-indicators">
<button *ngFor="let article of articles; let i = index"
type="button" data-bs-target="#newsCarousel"
[attr.data-bs-slide-to]="i"
[class.active]="i === 0"
[attr.aria-current]="i === 0 ? 'true' : null"
[attr.aria-label]="'Slide ' + (i + 1)">
</button>
</div>
<div class="carousel-inner">
<div *ngFor="let article of articles; let i = index" class="carousel-item" [class.active]="i === 0">
<img [src]="article.urlToImage || 'assets/default.jpg'" loading="lazy" class="d-block w-100" [alt]="article.title">
<div class="carousel-caption d-none d-md-block text-start">
<h5 style="color: black;">{{ article.title }}</h5>
<p>{{ article.description }}</p>
</div>
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#newsCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon-new" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 16 16">
<path d="M11.854 1.146a.5.5 0 0 1 0 .708L5.707 8l6.147 6.146a.5.5 0 1 1-.708.708l-6.5-6.5a.5.5 0 0 1 0-.708l6.5-6.5a.5.5 0 0 1 .708 0z"/>
</svg>
</span>
<span class="visually-hidden">Précédent</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#newsCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon-new" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 16 16">
<path d="M11.854 1.146a.5.5 0 0 1 0 .708L5.707 8l6.147 6.146a.5.5 0 1 1-.708.708l-6.5-6.5a.5.5 0 0 1 0-.708l6.5-6.5a.5.5 0 0 1 .708 0z"/>
</svg>
</span>
<span class="visually-hidden">Suivant</span>
</button>
</div>
<div *ngIf="articles.length === 0" class="alert alert-warning mb-4" role="alert">
Aucune actualité disponible pour le moment.
</div>

View file

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeActuComponent } from './home-actu.component';
describe('HomeActuComponent', () => {
let component: HomeActuComponent;
let fixture: ComponentFixture<HomeActuComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [HomeActuComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HomeActuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,22 @@
import { Component, OnInit } from '@angular/core';
import { DataService } from '@services/data.service';
import { Article } from '@interface/news';
@Component({
selector: 'app-home-actu',
standalone: false,
templateUrl: './home-actu.component.html',
styleUrls: ['./home-actu.component.css'] // Correction ici : styleUrls (pluriel)
})
export class HomeActuComponent implements OnInit {
articles: Article[] = [];
constructor(private dataService: DataService) {}
ngOnInit(): void {
this.dataService.getNews().subscribe((response) => {
this.articles = response.articles.slice(0, 5);
});
}
}

View file

@ -1,4 +1,5 @@
<div class="container py-5">
<app-home-actu></app-home-actu>
<app-home-accueil></app-home-accueil>
<app-home-symptoms></app-home-symptoms>
</div>

21
src/app/interface/news.ts Normal file
View file

@ -0,0 +1,21 @@
export interface Source {
id: string | null;
name: string;
}
export interface Article {
source: Source;
author: string;
title: string;
description: string;
url: string;
urlToImage: string;
publishedAt: string;
content: string;
}
export interface NewsResponse {
status: string;
totalResults: number;
articles: Article[];
}

View file

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { NewsService } from './news.service';
describe('NewsService', () => {
let service: NewsService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(NewsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View file

@ -0,0 +1,44 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { NewsResponse } from '@interface/news'; // Assure-toi que le chemin est correct
import { tap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class NewsService {
private apiKey: string = 'e9bb2770d3374edea421bcadbecdca5c';
private apiUrl: string = `https://newsapi.org/v2/everything?q=covid&language=fr&apiKey=${this.apiKey}`;
private storageKey: string = 'newsData'; // Clé utilisée pour le localStorage
private lastUpdateKey: string = 'lastUpdate'; // Clé pour stocker la date de la dernière mise à jour
private cacheDuration: number = 60 * 60 * 1000; // Durée en millisecondes (par exemple, 1 heure)
constructor(private http: HttpClient) {}
// Méthode pour récupérer les articles de l'API ou du localStorage
getNews(): Observable<NewsResponse> {
const storedData = localStorage.getItem(this.storageKey);
const lastUpdate = localStorage.getItem(this.lastUpdateKey);
// Vérifie si les données existent et si elles sont encore valides
const currentTime = new Date().getTime();
if (storedData && lastUpdate && (currentTime - Number(lastUpdate)) < this.cacheDuration) {
// Si les données sont présentes et récentes (moins de 1 heure par exemple), on les retourne
return new Observable(observer => {
observer.next(JSON.parse(storedData)); // On envoie les données stockées
observer.complete();
});
} else {
// Sinon, on effectue la requête à l'API
return this.http.get<NewsResponse>(this.apiUrl).pipe(
tap(response => {
// On stocke la réponse dans le localStorage et la date de mise à jour
localStorage.setItem(this.storageKey, JSON.stringify(response));
localStorage.setItem(this.lastUpdateKey, currentTime.toString()); // On enregistre l'heure de la mise à jour
})
);
}
}
}

View file

@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import { SymptomService } from './all/symptom/symptom.service';
import { BlogService } from './all/blog/blog.service';
import { AddressService } from './all/address/address.service';
import { NewsService } from './all/news/news.service';
@Injectable({
providedIn: 'root'
@ -12,7 +13,8 @@ export class DataService {
constructor(
private symptomService: SymptomService,
private blogService: BlogService,
private addressService: AddressService
private addressService: AddressService,
private newsService: NewsService
) {}
getSymptoms() {
@ -26,4 +28,8 @@ export class DataService {
getAddresses() {
return this.addressService.getAddresses();
}
getNews() {
return this.newsService.getNews();
}
}

View file

@ -19,7 +19,8 @@
"module": "ES2022",
"baseUrl": "./src",
"paths": {
"@services/*": ["app/service/*"]
"@services/*": ["app/service/*"],
"@interface/*": ["app/interface/*"]
}
},
"angularCompilerOptions": {