diff --git a/app/api/api-client.ts b/app/api/api-client.ts index d630371..a28dd98 100644 --- a/app/api/api-client.ts +++ b/app/api/api-client.ts @@ -12,6 +12,7 @@ import { listLeagues, listNewsArticles, Pageable, + PageHighlightDto, } from './generated' import { client } from './generated/client.gen' @@ -61,7 +62,7 @@ export const apiClient = { const { data, error } = await listFixtures({ query: { leagueApiId, - pageable, + ...pageable, }, }) if (error) { @@ -123,13 +124,13 @@ export const apiClient = { highlights: { async findAll(pageable: Pageable = DEFAULT_PAGEABLE) { try { - const { data, error } = await listHighlights({ query: { pageable } }) + const { data, error } = await listHighlights({ query: pageable }) if (error) { console.warn(`Error fetching highlights:`, error) return [] } - return data?.content ?? [] + return data as PageHighlightDto } catch (error) { console.warn('Error fetching highlights:', error) return [] @@ -156,7 +157,7 @@ export const apiClient = { try { // Assuming getLatestAdvertisements is the correct method const { data, error } = await getLatestAdvertisements({ - query: { pageable }, + query: pageable, }) if (error) { @@ -198,7 +199,7 @@ export const apiClient = { query: { country, type, - pageable, + ...pageable, }, }) diff --git a/app/api/generated/types.gen.ts b/app/api/generated/types.gen.ts index ad812bf..9e3b83c 100644 --- a/app/api/generated/types.gen.ts +++ b/app/api/generated/types.gen.ts @@ -1209,8 +1209,8 @@ export type ListLeaguesData = { query: { country?: string; type?: string; - pageable: Pageable; - }; + // pageable: Pageable; + } & Pageable; url: '/api/v1/leagues'; }; @@ -1242,9 +1242,7 @@ export type CreateLeagueResponse = CreateLeagueResponses[keyof CreateLeagueRespo export type ListHighlightsData = { body?: never; path?: never; - query: { - pageable: Pageable; - }; + query: Pageable; url: '/api/v1/highlights'; }; @@ -1278,8 +1276,7 @@ export type ListFixturesData = { path?: never; query: { leagueApiId?: string; - pageable: Pageable; - }; + } & Pageable; url: '/api/v1/fixtures'; }; @@ -1759,9 +1756,7 @@ export type GetFixturesByDateResponse = GetFixturesByDateResponses[keyof GetFixt export type GetLatestAdvertisementsData = { body?: never; path?: never; - query: { - pageable: Pageable; - }; + query: Pageable; url: '/api/v1/advertisements/latest'; }; diff --git a/app/api/types.ts b/app/api/types.ts index 53a8df0..1e533ce 100644 --- a/app/api/types.ts +++ b/app/api/types.ts @@ -31,6 +31,8 @@ export type Highlight = { apiHighlightId: string | null; }; + + export type League = { id: string; createdAt: Date; diff --git a/app/globals.css b/app/globals.css index 108c467..e045d9a 100644 --- a/app/globals.css +++ b/app/globals.css @@ -4,49 +4,54 @@ @custom-variant dark (&:is(.dark *)); :root { - --background: oklch(0.99 0.00 258.32); + --background: oklch(0.99 0 258.32); --foreground: oklch(0.28 0.04 260.03); - --card: oklch(1.00 0 0); + --card: oklch(1 0 0); --card-foreground: oklch(0.28 0.04 260.03); - --popover: oklch(1.00 0 0); + --popover: oklch(1 0 0); --popover-foreground: oklch(0.28 0.04 260.03); --primary: oklch(0.68 0.16 276.93); - --primary-foreground: oklch(1.00 0 0); + --primary-foreground: oklch(1 0 0); --secondary: oklch(0.79 0.12 45.79); --secondary-foreground: oklch(0.37 0.03 259.73); - --muted: oklch(0.97 0.00 264.54); + --muted: oklch(0.97 0 264.54); --muted-foreground: oklch(0.55 0.02 264.36); --accent: oklch(0.93 0.03 272.79); --accent-foreground: oklch(0.37 0.03 259.73); --destructive: oklch(0.64 0.21 25.33); - --destructive-foreground: oklch(1.00 0 0); + --destructive-foreground: oklch(1 0 0); --border: oklch(0.87 0.01 258.34); --input: oklch(0.87 0.01 258.34); - --ring: oklch(0.59 0.20 277.12); - --chart-1: oklch(0.59 0.20 277.12); + --ring: oklch(0.59 0.2 277.12); + --chart-1: oklch(0.59 0.2 277.12); --chart-2: oklch(0.51 0.23 276.97); --chart-3: oklch(0.46 0.21 277.02); - --chart-4: oklch(0.40 0.18 277.37); - --chart-5: oklch(0.36 0.14 278.70); - --sidebar: oklch(0.97 0.00 264.54); + --chart-4: oklch(0.4 0.18 277.37); + --chart-5: oklch(0.36 0.14 278.7); + --sidebar: oklch(0.97 0 264.54); --sidebar-foreground: oklch(0.28 0.04 260.03); - --sidebar-primary: oklch(0.59 0.20 277.12); - --sidebar-primary-foreground: oklch(1.00 0 0); + --sidebar-primary: oklch(0.59 0.2 277.12); + --sidebar-primary-foreground: oklch(1 0 0); --sidebar-accent: oklch(0.93 0.03 272.79); --sidebar-accent-foreground: oklch(0.37 0.03 259.73); --sidebar-border: oklch(0.87 0.01 258.34); - --sidebar-ring: oklch(0.59 0.20 277.12); + --sidebar-ring: oklch(0.59 0.2 277.12); --font-sans: Open Sans, sans-serif; --font-serif: Merriweather, serif; --font-mono: JetBrains Mono, monospace; --radius: 0.5rem; --shadow-2xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05); --shadow-xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05); - --shadow-sm: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10); - --shadow: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10); - --shadow-md: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10); - --shadow-lg: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10); - --shadow-xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10); + --shadow-sm: + 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 1px 2px -2px hsl(0 0% 0% / 0.1); + --shadow: + 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 1px 2px -2px hsl(0 0% 0% / 0.1); + --shadow-md: + 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 2px 4px -2px hsl(0 0% 0% / 0.1); + --shadow-lg: + 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 4px 6px -2px hsl(0 0% 0% / 0.1); + --shadow-xl: + 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 8px 10px -2px hsl(0 0% 0% / 0.1); --shadow-2xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.25); } @@ -67,21 +72,21 @@ --accent-foreground: oklch(0.87 0.01 258.34); --destructive: oklch(0.64 0.21 25.33); --destructive-foreground: oklch(0.21 0.04 265.75); - --border: oklch(0.45 0.03 256.80); - --input: oklch(0.45 0.03 256.80); + --border: oklch(0.45 0.03 256.8); + --input: oklch(0.45 0.03 256.8); --ring: oklch(0.68 0.16 276.93); --chart-1: oklch(0.68 0.16 276.93); - --chart-2: oklch(0.59 0.20 277.12); + --chart-2: oklch(0.59 0.2 277.12); --chart-3: oklch(0.51 0.23 276.97); --chart-4: oklch(0.46 0.21 277.02); - --chart-5: oklch(0.40 0.18 277.37); + --chart-5: oklch(0.4 0.18 277.37); --sidebar: oklch(0.28 0.04 260.03); --sidebar-foreground: oklch(0.93 0.01 255.51); --sidebar-primary: oklch(0.68 0.16 276.93); --sidebar-primary-foreground: oklch(0.21 0.04 265.75); --sidebar-accent: oklch(0.37 0.03 259.73); --sidebar-accent-foreground: oklch(0.87 0.01 258.34); - --sidebar-border: oklch(0.45 0.03 256.80); + --sidebar-border: oklch(0.45 0.03 256.8); --sidebar-ring: oklch(0.68 0.16 276.93); --font-sans: Open Sans, sans-serif; --font-serif: Merriweather, serif; @@ -89,11 +94,16 @@ --radius: 0.5rem; --shadow-2xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05); --shadow-xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05); - --shadow-sm: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10); - --shadow: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10); - --shadow-md: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10); - --shadow-lg: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10); - --shadow-xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10); + --shadow-sm: + 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 1px 2px -2px hsl(0 0% 0% / 0.1); + --shadow: + 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 1px 2px -2px hsl(0 0% 0% / 0.1); + --shadow-md: + 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 2px 4px -2px hsl(0 0% 0% / 0.1); + --shadow-lg: + 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 4px 6px -2px hsl(0 0% 0% / 0.1); + --shadow-xl: + 0px 4px 8px -1px hsl(0 0% 0% / 0.1), 0px 8px 10px -2px hsl(0 0% 0% / 0.1); --shadow-2xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.25); } @@ -156,4 +166,57 @@ body { @apply bg-background text-foreground; } + + .dom-purify-content p { + margin-bottom: 1.25rem; /* More breathing room between paragraphs */ + } + + .dom-purify-content h2 { + margin-top: 2rem; + margin-bottom: 1rem; + font-size: 1.875rem; /* Tailwind: text-2xl */ + font-weight: 700; + border-bottom: 1px solid #e5e7eb; /* Tailwind gray-200 */ + padding-bottom: 0.25rem; + } + + .dom-purify-content h3 { + margin-top: 1.5rem; + font-size: 1.5rem; /* Tailwind: text-xl */ + font-weight: 600; + } + + .dom-purify-content ul, + .dom-purify-content ol { + padding-left: 1.25rem; + margin-bottom: 1.25rem; + } + + .dom-purify-content li { + margin-bottom: 0.5rem; + } + + .dom-purify-content img { + margin: 2rem auto; + display: block; + max-width: 100%; + max-height: 400px; + border-radius: 0.5rem; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1); + } + + .dom-purify-content hr { + margin: 3rem 0; + border: none; + height: 1px; + background-color: #e5e7eb; /* Tailwind gray-200 */ + } + + .dom-purify-content blockquote { + border-left: 4px solid #3b82f6; /* Tailwind blue-500 */ + padding-left: 1rem; + font-style: italic; + color: #6b7280; /* Tailwind gray-500 */ + margin: 1.5rem 0; + } } diff --git a/app/highlights/page.tsx b/app/highlights/page.tsx new file mode 100644 index 0000000..96e2ba9 --- /dev/null +++ b/app/highlights/page.tsx @@ -0,0 +1,255 @@ +import { + ChevronRight, +} from "lucide-react"; +import Link from "next/link"; +import { apiClient } from "@/app/api/api-client"; +import { HighlightsClient } from "@/components/Highlights/HighlightsClient"; +import type { HighlightDto, PageHighlightDto } from "@/app/api/generated"; +import type { HighlightInitialPageData } from "./types"; +import { PAGE_SIZE } from "./types"; + +interface SearchParams { + page?: string; +} + +function filterAndSortHighlights(content: HighlightDto[] = []): HighlightDto[] { + return content + .filter((highlight) => highlight.videoUrl) + .sort((a, b) => { + const dateA = a.createdAt ? new Date(a.createdAt) : new Date(0); + const dateB = b.createdAt ? new Date(b.createdAt) : new Date(0); + return dateB.getTime() - dateA.getTime(); + }); +} + +async function fetchInitialData( + page: number +): Promise { + try { + const response = await apiClient.highlights.findAll({ + page, + size: PAGE_SIZE, + }); + const data = response as PageHighlightDto; + + return { + highlights: filterAndSortHighlights(data?.content), + totalPages: data?.totalPages || 0, + totalElements: data?.totalElements || 0, + error: null, + }; + } catch (error) { + console.error("Error fetching highlights:", error); + return { + highlights: [], + totalPages: 0, + totalElements: 0, + error: "Une erreur est survenue lors du chargement des points forts.", + }; + } +} + +export default async function HighlightsPage({ + searchParams, +}: { + searchParams: Promise +}) { + const resolvedSearchParams = await searchParams + const currentPage = Number(resolvedSearchParams.page) || 0 + const initialData = await fetchInitialData(currentPage); + + return ( +
+
+ {/* Breadcrumb */} + + + {/* Latest Highlights */} + {/* */} + +
+
+ ); +} + +// function Highlights() { +// const [currentPage, setCurrentPage] = useState(0); +// const [loading, setLoading] = useState(true); +// const [highlights, setHighlights] = useState([]); +// const [totalPages, setTotalPages] = useState(0); +// const [totalElements, setTotalElements] = useState(0); +// const [error, setError] = useState(null); + +// const pageSize = 9; + +// useEffect(() => { +// fetchHighlights(currentPage); +// }, [currentPage]); + +// const fetchHighlights = async (page: number) => { +// try { +// setLoading(true); +// setError(null); + +// const response = await apiClient.highlights.findAll({ +// page: page, +// size: pageSize, +// }); +// const data = response as PageHighlightDto; + +// // Filter highlights with video URLs and sort by creation date +// const filteredHighlights: HighlightDto[] = data?.content +// ? data.content +// .filter((highlight: HighlightDto) => highlight.videoUrl) +// .sort((a: HighlightDto, b: HighlightDto) => { +// const dateA = a.createdAt ? new Date(a.createdAt) : new Date(0); +// const dateB = b.createdAt ? new Date(b.createdAt) : new Date(0); +// return dateB.getTime() - dateA.getTime(); +// }) +// : []; + +// setHighlights(filteredHighlights); +// setTotalPages(data?.totalPages || 0); +// setTotalElements(data?.totalElements || 0); +// } catch (err) { +// setError("Une erreur est survenue lors du chargement des points forts."); +// console.error("Error fetching highlights:", err); +// } finally { +// setLoading(false); +// } +// }; + +// const handlePageChange = (newPage: number) => { +// if (newPage >= 0 && newPage < totalPages) { +// setCurrentPage(newPage); +// window.scrollTo({ top: 0, behavior: "smooth" }); +// } +// }; + +// if (loading) { +// return ; +// } + +// if (error) { +// return ( +//
+//
+//

Erreur

+//

{error}

+// +//
+//
+// ); +// } + +// if (highlights.length === 0) { +// return ( +//
+//
+//
+// +// +// +//
+//

+// Aucun point fort disponible +//

+//

+// Il ny a actuellement aucun point fort avec des vidéos à afficher. +//

+//
+//
+// ); +// } + +// return ( +//
+//
+//

Points forts

+//

+// {totalElements} {totalElements === 1 ? "point fort" : "points forts"} +// {totalElements > 0 && ` • Page ${currentPage + 1} sur ${totalPages}`} +//

+//
+ +// + +// {totalPages > 1 && ( +//
+// + +//
+// {Array.from({ length: totalPages }, (_, i) => ( +// +// ))} +//
+ +// +//
+// )} +//
+// ); +// } diff --git a/app/highlights/types.ts b/app/highlights/types.ts new file mode 100644 index 0000000..472372d --- /dev/null +++ b/app/highlights/types.ts @@ -0,0 +1,10 @@ +import type { HighlightDto } from "@/app/api/generated"; + +export const PAGE_SIZE = 9 + +export interface HighlightInitialPageData { + highlights: HighlightDto[] + totalPages: number + totalElements: number + error: string | null +} \ No newline at end of file diff --git a/app/news/[id]/page.tsx b/app/news/[id]/page.tsx index 8d28a8d..73e0681 100644 --- a/app/news/[id]/page.tsx +++ b/app/news/[id]/page.tsx @@ -1,120 +1,121 @@ -export const dynamic = 'force-dynamic' +export const dynamic = "force-dynamic"; -import { apiClient } from '@/app/api/api-client' -import { formatDate } from '@/lib/utils' -import Link from 'next/link' -import { notFound } from 'next/navigation' -import DOMPurify from 'isomorphic-dompurify' -import { calculateReadingTime, formatReadingTime } from '@/lib/readingTime' -import { ChevronRight, Clock } from 'lucide-react' -import Image from 'next/image' +import { apiClient } from "@/app/api/api-client"; +import { formatDate } from "@/lib/utils"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import DOMPurify from "isomorphic-dompurify"; +import { calculateReadingTime, formatReadingTime } from "@/lib/readingTime"; +import { ChevronRight, Clock } from "lucide-react"; +import Image from "next/image"; interface UserPageProps { - params: Promise<{ id: string }> + params: Promise<{ id: string }>; } export default async function NewsPage({ params }: UserPageProps) { - const latestNews = await apiClient.newsArticles.findOne((await params).id) - if (!latestNews) { - notFound() - } + const currentNews = await apiClient.newsArticles.findOne((await params).id); // Récupérer les articles récents pour le sidebar et la section "Plus d'articles" - const allNews = await apiClient.newsArticles.findAll() + const allNews = await apiClient.newsArticles.findAll(); // Exclure l'article courant des articles récents const recentArticles = allNews - .filter((article) => article.id !== (latestNews?.id || '')) - .slice(0, 4) + .filter((article) => article.id !== (currentNews?.id || "")) + .slice(0, 4); // Articles supplémentaires pour la section du bas const moreArticles = allNews .filter( (article) => - article.id !== (latestNews?.id || '') && + article.id !== (currentNews?.id || "") && !recentArticles.map((a) => a.id).includes(article.id) ) - .slice(0, 4) + .slice(0, 4); + + if (!currentNews) { + notFound(); + } // Calculer le temps de lecture - const readingTime = calculateReadingTime(latestNews.content) - const formattedReadingTime = formatReadingTime(readingTime) + const readingTime = calculateReadingTime(currentNews.content); + const formattedReadingTime = formatReadingTime(readingTime); return ( -
-
-