diff --git a/app/[id]/page.tsx b/app/[id]/page.tsx index b3a3e9f..fc7c4f7 100644 --- a/app/[id]/page.tsx +++ b/app/[id]/page.tsx @@ -1,24 +1,23 @@ 'use client'; -import { useRouter } from 'next/navigation'; import Prism from 'prismjs'; -import { use, useEffect, useState } from 'react'; +import { use, useCallback, useEffect, useState } from 'react'; import 'prismjs/plugins/line-numbers/prism-line-numbers.css'; import 'prismjs/plugins/line-numbers/prism-line-numbers.js'; import 'prismjs/themes/prism-tomorrow.css'; -import AuthSection from "@/components/paste-form/AuthSection"; +import AuthSection from '@/components/paste-form/AuthSection'; import CodeViewer from '@/components/paste/CodeViewer'; import LoadingPaste from '@/components/paste/LoadingPaste'; import PasswordForm from '@/components/paste/PasswordForm'; import PasteSidebar from '@/components/paste/PasteSidebar'; - +import { Paste } from '@/types/paste'; async function loadLanguage(lang: string) { try { await import(`prismjs/components/prism-${lang}.js`); - } catch (err) { + } catch { console.warn(`[Prism] Language '${lang}' introuvable. Fallback -> plaintext`); } } @@ -29,14 +28,13 @@ export default function PastePage({ params: Promise<{ id: string }>; }) { const { id } = use(params); - const router = useRouter(); - const [paste, setPaste] = useState(null); + const [paste, setPaste] = useState(null); const [error, setError] = useState(''); const [loading, setLoading] = useState(true); const [language, setLanguage] = useState('language-javascript'); - const fetchPaste = async (pwd?: string) => { + const fetchPaste = useCallback(async (pwd?: string) => { setLoading(true); const res = await fetch(`/api/paste/${id}`, { method: 'POST', @@ -55,7 +53,7 @@ export default function PastePage({ return; } - const data = await res.json(); + const data: Paste = await res.json(); setPaste(data); const content = data.content.toLowerCase(); @@ -81,11 +79,11 @@ export default function PastePage({ setLanguage(`language-${detected}`); setLoading(false); - }; + }, [id]); useEffect(() => { fetchPaste(); - }, [id]); + }, [fetchPaste]); useEffect(() => { if (paste?.content) Prism.highlightAll(); @@ -99,9 +97,9 @@ export default function PastePage({ return (
- - + +
); -} +} \ No newline at end of file diff --git a/app/about/page.tsx b/app/about/page.tsx index 4eced33..5cecd7b 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -4,7 +4,13 @@ import { Player } from "@lordicon/react"; import { motion } from "framer-motion"; import { ArrowLeft } from "lucide-react"; import { useRouter } from "next/navigation"; -import { forwardRef, useEffect, useRef, useState } from "react"; +import { + ForwardedRef, + forwardRef, + useEffect, + useRef, + useState, +} from "react"; // URLs d’icônes Lordicon const icons = { @@ -17,13 +23,20 @@ const icons = { // Wrapper pour Lordicon const LordIcon = forwardRef(function LordIcon( - { url, size = 30, colorize = "#3b82f6" }: { url: string; size?: number; colorize?: string }, - ref: any + { + url, + size = 30, + colorize = "#3b82f6", + }: { url: string; size?: number; colorize?: string }, + ref: ForwardedRef ) { - const [iconData, setIconData] = useState(null); + const [iconData, setIconData] = useState | null>(null); useEffect(() => { - fetch(url).then((res) => res.json()).then(setIconData); + fetch(url) + .then((res) => res.json()) + .then(setIconData) + .catch(() => setIconData(null)); }, [url]); if (!iconData) return null; @@ -40,7 +53,7 @@ function FeatureCard({ desc: string; icon: string; }) { - const iconRef = useRef(null); + const iconRef = useRef(null); return ( (null); + const iconRef = useRef(null); return ( Paste not found; - - return ( -
-

- {paste.title || 'Untitled'} -

- -
-        {paste.content}
-      
- -

- Views: {paste.views} · Created at: {new Date(paste.createdAt).toLocaleString()} -

-
- ); -} diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index e45ae71..8e833b4 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -30,13 +30,34 @@ const dashboardIcons = { paste: "https://cdn.lordicon.com/hmpomorl.json", }; +// ---- Types ---- +interface DashboardStats { + totalPastes: number; + totalViews: number; + recentPastes: number; + apiUsage: number; + storageUsed: number; + avgViews: number; + mostViewed: number; + avgSize: number; +} + +interface Paste { + id: string; + title: string; + content: string; + createdAt: string; + views: number; + maxViews: number | null; +} + export default function DashboardPage() { const { data: session, status } = useSession(); const router = useRouter(); const [searchTerm, setSearchTerm] = useState(""); - const [stats, setStats] = useState(null); - const [pastes, setPastes] = useState([]); + const [stats, setStats] = useState(null); + const [pastes, setPastes] = useState([]); const [page, setPage] = useState(1); const [totalPastes, setTotalPastes] = useState(0); const [deleteId, setDeleteId] = useState(null); @@ -60,16 +81,22 @@ export default function DashboardPage() { if (status === "authenticated") { const fetchStats = async () => { const res = await fetch("/api/stats"); - if (res.ok) setStats(await res.json()); + if (res.ok) { + const data: DashboardStats = await res.json(); + setStats(data); + } }; const fetchPastes = async () => { const res = await fetch( `/api/user-pastes?page=${page}&perPage=${PER_PAGE}&q=${encodeURIComponent(searchTerm)}` ); - if (!res.ok) return setPastes([]); + if (!res.ok) { + setPastes([]); + return; + } try { - const data = await res.json(); + const data: { pastes: Paste[]; total: number } = await res.json(); setPastes(data.pastes ?? []); setTotalPastes(data.total ?? 0); } catch { @@ -92,7 +119,7 @@ export default function DashboardPage() {
-
+
{/* Stats */}
@@ -110,7 +137,13 @@ export default function DashboardPage() { ))}
- { setSearchTerm(v); setPage(1); }} /> + { + setSearchTerm(v); + setPage(1); + }} + /> {/* Pastes */}

Your Pastes

@@ -134,7 +167,12 @@ export default function DashboardPage() { !open && setDeleteId(null)} - onConfirm={() => { if (deleteId) { handleDelete(deleteId); setDeleteId(null); } }} + onConfirm={() => { + if (deleteId) { + handleDelete(deleteId); + setDeleteId(null); + } + }} />
diff --git a/app/not-found.tsx b/app/not-found.tsx index a2d0303..c5da019 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -30,7 +30,7 @@ export default function NotFoundPage() {

- The paste you're looking for might have been deleted, expired, or the + The paste you're looking for might have been deleted, expired, or the URL is incorrect.

diff --git a/components/dashboard/Header.tsx b/components/dashboard/Header.tsx index 7d61cb7..41123b6 100644 --- a/components/dashboard/Header.tsx +++ b/components/dashboard/Header.tsx @@ -1,12 +1,13 @@ "use client"; +import type { Player } from "@lordicon/react"; import { motion } from "framer-motion"; import { useRouter } from "next/navigation"; import { useRef } from "react"; import LordIcon from "./LordIcon"; export default function Header({ user }: { user?: string | null }) { - const plusIconRef = useRef(null); + const plusIconRef = useRef(null); const router = useRouter(); return ( diff --git a/components/dashboard/LordIcon.tsx b/components/dashboard/LordIcon.tsx index 9553efc..b5609fc 100644 --- a/components/dashboard/LordIcon.tsx +++ b/components/dashboard/LordIcon.tsx @@ -1,19 +1,22 @@ "use client"; import { Player } from "@lordicon/react"; -import { forwardRef, useEffect, useState } from "react"; +import { forwardRef, useEffect, useState, type ForwardedRef } from "react"; const LordIcon = forwardRef(function LordIcon( { url, size = 28, colorize = "#3b82f6" }: { url: string; size?: number; colorize?: string }, - ref: any + ref: ForwardedRef ) { - const [iconData, setIconData] = useState(null); + const [iconData, setIconData] = useState | null>(null); useEffect(() => { - fetch(url).then((res) => res.json()).then(setIconData); + fetch(url) + .then((res) => res.json()) + .then((data: Record) => setIconData(data)); }, [url]); if (!iconData) return null; + return ; }); diff --git a/components/dashboard/PasteCard.tsx b/components/dashboard/PasteCard.tsx index 20deecd..7b5a5ae 100644 --- a/components/dashboard/PasteCard.tsx +++ b/components/dashboard/PasteCard.tsx @@ -1,19 +1,28 @@ "use client"; +import type { Player } from "@lordicon/react"; import { motion } from "framer-motion"; import { useRef } from "react"; import LordIcon from "./LordIcon"; +interface Paste { + id: string; + title: string | null; + content: string | null; + createdAt: string | Date; + views: number; +} + export default function PasteCard({ paste, onDelete, pasteIconUrl, }: { - paste: any; + paste: Paste; onDelete: (id: string) => void; pasteIconUrl: string; }) { - const pasteIconRef = useRef(null); + const pasteIconRef = useRef(null); return (
- {paste.title || 'Untitled'} + {paste.title || "Untitled"}
- {paste.content || 'No content'} + {paste.content || "No content"}
{new Date(paste.createdAt).toLocaleString()} diff --git a/components/dashboard/StatCard.tsx b/components/dashboard/StatCard.tsx index f156fc1..2d094d3 100644 --- a/components/dashboard/StatCard.tsx +++ b/components/dashboard/StatCard.tsx @@ -1,5 +1,6 @@ "use client"; +import type { Player } from "@lordicon/react"; import { motion } from "framer-motion"; import { useRef } from "react"; import LordIcon from "./LordIcon"; @@ -10,10 +11,10 @@ export default function StatCard({ icon, }: { label: string; - value: any; + value: string | number | null; icon: string; }) { - const iconRef = useRef(null); + const iconRef = useRef(null); return (
{label}
-
{value}
+
{value ?? "—"}
diff --git a/components/paste-form/AuthSection.tsx b/components/paste-form/AuthSection.tsx index 249e433..6673845 100644 --- a/components/paste-form/AuthSection.tsx +++ b/components/paste-form/AuthSection.tsx @@ -2,6 +2,7 @@ import { Info, Shield } from "lucide-react"; import { signIn, signOut, useSession } from "next-auth/react"; +import Link from "next/link"; export default function AuthSection() { const { data: session, status } = useSession(); @@ -13,13 +14,13 @@ export default function AuthSection() { {!session?.user ? (
{/* Bouton About */} - About - + {/* Bouton Login */}