Fix pnpm build

This commit is contained in:
UltraLionFr
2025-08-20 16:41:27 +02:00
parent 3de3e6f35a
commit 9730e21999
16 changed files with 134 additions and 83 deletions
+12 -14
View File
@@ -1,24 +1,23 @@
'use client'; 'use client';
import { useRouter } from 'next/navigation';
import Prism from 'prismjs'; 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.css';
import 'prismjs/plugins/line-numbers/prism-line-numbers.js'; import 'prismjs/plugins/line-numbers/prism-line-numbers.js';
import 'prismjs/themes/prism-tomorrow.css'; 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 CodeViewer from '@/components/paste/CodeViewer';
import LoadingPaste from '@/components/paste/LoadingPaste'; import LoadingPaste from '@/components/paste/LoadingPaste';
import PasswordForm from '@/components/paste/PasswordForm'; import PasswordForm from '@/components/paste/PasswordForm';
import PasteSidebar from '@/components/paste/PasteSidebar'; import PasteSidebar from '@/components/paste/PasteSidebar';
import { Paste } from '@/types/paste';
async function loadLanguage(lang: string) { async function loadLanguage(lang: string) {
try { try {
await import(`prismjs/components/prism-${lang}.js`); await import(`prismjs/components/prism-${lang}.js`);
} catch (err) { } catch {
console.warn(`[Prism] Language '${lang}' introuvable. Fallback -> plaintext`); console.warn(`[Prism] Language '${lang}' introuvable. Fallback -> plaintext`);
} }
} }
@@ -29,14 +28,13 @@ export default function PastePage({
params: Promise<{ id: string }>; params: Promise<{ id: string }>;
}) { }) {
const { id } = use(params); const { id } = use(params);
const router = useRouter();
const [paste, setPaste] = useState<any | null>(null); const [paste, setPaste] = useState<Paste | null>(null);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [language, setLanguage] = useState<string>('language-javascript'); const [language, setLanguage] = useState<string>('language-javascript');
const fetchPaste = async (pwd?: string) => { const fetchPaste = useCallback(async (pwd?: string) => {
setLoading(true); setLoading(true);
const res = await fetch(`/api/paste/${id}`, { const res = await fetch(`/api/paste/${id}`, {
method: 'POST', method: 'POST',
@@ -55,7 +53,7 @@ export default function PastePage({
return; return;
} }
const data = await res.json(); const data: Paste = await res.json();
setPaste(data); setPaste(data);
const content = data.content.toLowerCase(); const content = data.content.toLowerCase();
@@ -81,11 +79,11 @@ export default function PastePage({
setLanguage(`language-${detected}`); setLanguage(`language-${detected}`);
setLoading(false); setLoading(false);
}; }, [id]);
useEffect(() => { useEffect(() => {
fetchPaste(); fetchPaste();
}, [id]); }, [fetchPaste]);
useEffect(() => { useEffect(() => {
if (paste?.content) Prism.highlightAll(); if (paste?.content) Prism.highlightAll();
@@ -99,9 +97,9 @@ export default function PastePage({
return ( return (
<div className="relative w-screen h-screen bg-[#0e0f13] text-white flex overflow-hidden"> <div className="relative w-screen h-screen bg-[#0e0f13] text-white flex overflow-hidden">
<CodeViewer paste={paste} language={language} /> <CodeViewer paste={paste!} language={language} />
<PasteSidebar paste={paste} id={id} /> <PasteSidebar paste={paste!} id={id} />
<AuthSection /> <AuthSection />
</div> </div>
); );
} }
+20 -7
View File
@@ -4,7 +4,13 @@ import { Player } from "@lordicon/react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { ArrowLeft } from "lucide-react"; import { ArrowLeft } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { forwardRef, useEffect, useRef, useState } from "react"; import {
ForwardedRef,
forwardRef,
useEffect,
useRef,
useState,
} from "react";
// URLs dicônes Lordicon // URLs dicônes Lordicon
const icons = { const icons = {
@@ -17,13 +23,20 @@ const icons = {
// Wrapper pour Lordicon // Wrapper pour Lordicon
const LordIcon = forwardRef(function 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<Player>
) { ) {
const [iconData, setIconData] = useState<any>(null); const [iconData, setIconData] = useState<Record<string, unknown> | null>(null);
useEffect(() => { useEffect(() => {
fetch(url).then((res) => res.json()).then(setIconData); fetch(url)
.then((res) => res.json())
.then(setIconData)
.catch(() => setIconData(null));
}, [url]); }, [url]);
if (!iconData) return null; if (!iconData) return null;
@@ -40,7 +53,7 @@ function FeatureCard({
desc: string; desc: string;
icon: string; icon: string;
}) { }) {
const iconRef = useRef<any>(null); const iconRef = useRef<Player>(null);
return ( return (
<motion.div <motion.div
@@ -76,7 +89,7 @@ function ContactButton({
shadow: string; shadow: string;
color?: string; color?: string;
}) { }) {
const iconRef = useRef<any>(null); const iconRef = useRef<Player>(null);
return ( return (
<motion.a <motion.a
-30
View File
@@ -1,30 +0,0 @@
import { prisma } from '@/lib/prisma';
interface Props {
params: { id: string };
}
export default async function PastePage({ params }: Props) {
const paste = await prisma.paste.findUnique({
where: { id: params.id },
select: { title: true, content: true, createdAt: true, views: true },
});
if (!paste) return <div>Paste not found</div>;
return (
<div className="min-h-screen bg-[#0e0f13] text-white p-6">
<h1 className="text-3xl font-bold text-blue-400 mb-4">
{paste.title || 'Untitled'}
</h1>
<pre className="bg-[#11131c] p-6 rounded-xl whitespace-pre-wrap overflow-auto text-sm border border-white/10 shadow-lg">
{paste.content}
</pre>
<p className="mt-4 text-xs text-neutral-400">
Views: {paste.views} · Created at: {new Date(paste.createdAt).toLocaleString()}
</p>
</div>
);
}
+46 -8
View File
@@ -30,13 +30,34 @@ const dashboardIcons = {
paste: "https://cdn.lordicon.com/hmpomorl.json", 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() { export default function DashboardPage() {
const { data: session, status } = useSession(); const { data: session, status } = useSession();
const router = useRouter(); const router = useRouter();
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [stats, setStats] = useState<any>(null); const [stats, setStats] = useState<DashboardStats | null>(null);
const [pastes, setPastes] = useState<any[]>([]); const [pastes, setPastes] = useState<Paste[]>([]);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [totalPastes, setTotalPastes] = useState(0); const [totalPastes, setTotalPastes] = useState(0);
const [deleteId, setDeleteId] = useState<string | null>(null); const [deleteId, setDeleteId] = useState<string | null>(null);
@@ -60,16 +81,22 @@ export default function DashboardPage() {
if (status === "authenticated") { if (status === "authenticated") {
const fetchStats = async () => { const fetchStats = async () => {
const res = await fetch("/api/stats"); 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 fetchPastes = async () => {
const res = await fetch( const res = await fetch(
`/api/user-pastes?page=${page}&perPage=${PER_PAGE}&q=${encodeURIComponent(searchTerm)}` `/api/user-pastes?page=${page}&perPage=${PER_PAGE}&q=${encodeURIComponent(searchTerm)}`
); );
if (!res.ok) return setPastes([]); if (!res.ok) {
setPastes([]);
return;
}
try { try {
const data = await res.json(); const data: { pastes: Paste[]; total: number } = await res.json();
setPastes(data.pastes ?? []); setPastes(data.pastes ?? []);
setTotalPastes(data.total ?? 0); setTotalPastes(data.total ?? 0);
} catch { } catch {
@@ -92,7 +119,7 @@ export default function DashboardPage() {
<Sidebar /> <Sidebar />
<main className="flex-1 p-8 overflow-y-auto"> <main className="flex-1 p-8 overflow-y-auto">
<Header user={session?.user?.name} /> <Header user={session?.user?.name ?? "User"} />
{/* Stats */} {/* Stats */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-10"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-10">
@@ -110,7 +137,13 @@ export default function DashboardPage() {
))} ))}
</div> </div>
<SearchBar value={searchTerm} onChange={(v) => { setSearchTerm(v); setPage(1); }} /> <SearchBar
value={searchTerm}
onChange={(v) => {
setSearchTerm(v);
setPage(1);
}}
/>
{/* Pastes */} {/* Pastes */}
<h2 className="text-lg font-semibold text-white mb-4">Your Pastes</h2> <h2 className="text-lg font-semibold text-white mb-4">Your Pastes</h2>
@@ -134,7 +167,12 @@ export default function DashboardPage() {
<ConfirmDeleteModal <ConfirmDeleteModal
open={!!deleteId} open={!!deleteId}
onOpenChange={(open) => !open && setDeleteId(null)} onOpenChange={(open) => !open && setDeleteId(null)}
onConfirm={() => { if (deleteId) { handleDelete(deleteId); setDeleteId(null); } }} onConfirm={() => {
if (deleteId) {
handleDelete(deleteId);
setDeleteId(null);
}
}}
/> />
</main> </main>
</div> </div>
+1 -1
View File
@@ -30,7 +30,7 @@ export default function NotFoundPage() {
</h2> </h2>
<p className="text-base text-neutral-400 mb-10 leading-relaxed"> <p className="text-base text-neutral-400 mb-10 leading-relaxed">
The paste you're looking for might have been deleted, expired, or the The paste you&apos;re looking for might have been deleted, expired, or the
URL is incorrect. URL is incorrect.
</p> </p>
+2 -1
View File
@@ -1,12 +1,13 @@
"use client"; "use client";
import type { Player } from "@lordicon/react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useRef } from "react"; import { useRef } from "react";
import LordIcon from "./LordIcon"; import LordIcon from "./LordIcon";
export default function Header({ user }: { user?: string | null }) { export default function Header({ user }: { user?: string | null }) {
const plusIconRef = useRef<any>(null); const plusIconRef = useRef<Player | null>(null);
const router = useRouter(); const router = useRouter();
return ( return (
+7 -4
View File
@@ -1,19 +1,22 @@
"use client"; "use client";
import { Player } from "@lordicon/react"; import { Player } from "@lordicon/react";
import { forwardRef, useEffect, useState } from "react"; import { forwardRef, useEffect, useState, type ForwardedRef } from "react";
const LordIcon = forwardRef(function LordIcon( const LordIcon = forwardRef(function LordIcon(
{ url, size = 28, colorize = "#3b82f6" }: { url: string; size?: number; colorize?: string }, { url, size = 28, colorize = "#3b82f6" }: { url: string; size?: number; colorize?: string },
ref: any ref: ForwardedRef<Player>
) { ) {
const [iconData, setIconData] = useState<any>(null); const [iconData, setIconData] = useState<Record<string, unknown> | null>(null);
useEffect(() => { useEffect(() => {
fetch(url).then((res) => res.json()).then(setIconData); fetch(url)
.then((res) => res.json())
.then((data: Record<string, unknown>) => setIconData(data));
}, [url]); }, [url]);
if (!iconData) return null; if (!iconData) return null;
return <Player ref={ref} icon={iconData} size={size} colorize={colorize} />; return <Player ref={ref} icon={iconData} size={size} colorize={colorize} />;
}); });
+13 -4
View File
@@ -1,19 +1,28 @@
"use client"; "use client";
import type { Player } from "@lordicon/react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useRef } from "react"; import { useRef } from "react";
import LordIcon from "./LordIcon"; import LordIcon from "./LordIcon";
interface Paste {
id: string;
title: string | null;
content: string | null;
createdAt: string | Date;
views: number;
}
export default function PasteCard({ export default function PasteCard({
paste, paste,
onDelete, onDelete,
pasteIconUrl, pasteIconUrl,
}: { }: {
paste: any; paste: Paste;
onDelete: (id: string) => void; onDelete: (id: string) => void;
pasteIconUrl: string; pasteIconUrl: string;
}) { }) {
const pasteIconRef = useRef<any>(null); const pasteIconRef = useRef<Player | null>(null);
return ( return (
<motion.div <motion.div
@@ -27,11 +36,11 @@ export default function PasteCard({
<LordIcon ref={pasteIconRef} url={pasteIconUrl} size={28} colorize="#3b82f6" /> <LordIcon ref={pasteIconRef} url={pasteIconUrl} size={28} colorize="#3b82f6" />
</div> </div>
<div className="font-mono text-white text-sm truncate"> <div className="font-mono text-white text-sm truncate">
{paste.title || 'Untitled'} {paste.title || "Untitled"}
</div> </div>
</div> </div>
<div className="text-xs text-neutral-400 font-mono mb-3 line-clamp-3"> <div className="text-xs text-neutral-400 font-mono mb-3 line-clamp-3">
{paste.content || 'No content'} {paste.content || "No content"}
</div> </div>
<div className="flex justify-between items-center mt-auto text-xs text-neutral-500"> <div className="flex justify-between items-center mt-auto text-xs text-neutral-500">
<span>{new Date(paste.createdAt).toLocaleString()}</span> <span>{new Date(paste.createdAt).toLocaleString()}</span>
+4 -3
View File
@@ -1,5 +1,6 @@
"use client"; "use client";
import type { Player } from "@lordicon/react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useRef } from "react"; import { useRef } from "react";
import LordIcon from "./LordIcon"; import LordIcon from "./LordIcon";
@@ -10,10 +11,10 @@ export default function StatCard({
icon, icon,
}: { }: {
label: string; label: string;
value: any; value: string | number | null;
icon: string; icon: string;
}) { }) {
const iconRef = useRef<any>(null); const iconRef = useRef<Player | null>(null);
return ( return (
<motion.div <motion.div
@@ -28,7 +29,7 @@ export default function StatCard({
</div> </div>
<div> <div>
<div className="text-sm text-neutral-400">{label}</div> <div className="text-sm text-neutral-400">{label}</div>
<div className="text-xl font-bold text-white">{value}</div> <div className="text-xl font-bold text-white">{value ?? "—"}</div>
</div> </div>
</div> </div>
</motion.div> </motion.div>
+5 -4
View File
@@ -2,6 +2,7 @@
import { Info, Shield } from "lucide-react"; import { Info, Shield } from "lucide-react";
import { signIn, signOut, useSession } from "next-auth/react"; import { signIn, signOut, useSession } from "next-auth/react";
import Link from "next/link";
export default function AuthSection() { export default function AuthSection() {
const { data: session, status } = useSession(); const { data: session, status } = useSession();
@@ -13,13 +14,13 @@ export default function AuthSection() {
{!session?.user ? ( {!session?.user ? (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* Bouton About */} {/* Bouton About */}
<a <Link
href="/about" href="/about"
className="cursor-pointer flex items-center gap-2 bg-[#0d1117] hover:bg-[#1c2230] border border-white/10 text-neutral-300 px-4 py-2 rounded-lg shadow-md transition-all" className="cursor-pointer flex items-center gap-2 bg-[#0d1117] hover:bg-[#1c2230] border border-white/10 text-neutral-300 px-4 py-2 rounded-lg shadow-md transition-all"
> >
<Info size={16} /> <Info size={16} />
<span>About</span> <span>About</span>
</a> </Link>
{/* Bouton Login */} {/* Bouton Login */}
<button <button
@@ -41,12 +42,12 @@ export default function AuthSection() {
</div> </div>
<div className="flex justify-between gap-2 mt-2"> <div className="flex justify-between gap-2 mt-2">
<a <Link
href="/dashboard" href="/dashboard"
className="flex-1 text-center bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded-lg transition-colors" className="flex-1 text-center bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded-lg transition-colors"
> >
Dashboard Dashboard
</a> </Link>
<button <button
onClick={() => signOut()} onClick={() => signOut()}
className="cursor-pointer flex-1 text-center bg-red-600 hover:bg-red-500 text-white px-4 py-2 rounded-lg transition-colors" className="cursor-pointer flex-1 text-center bg-red-600 hover:bg-red-500 text-white px-4 py-2 rounded-lg transition-colors"
+8 -3
View File
@@ -11,11 +11,13 @@ import 'prismjs/plugins/line-numbers/prism-line-numbers.js';
import 'prismjs/themes/prism-tomorrow.css'; import 'prismjs/themes/prism-tomorrow.css';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { Paste } from '@/types/paste';
export default function CodeViewer({ export default function CodeViewer({
paste, paste,
language, language,
}: { }: {
paste: any; paste: Paste;
language: string; language: string;
}) { }) {
const codeRef = useRef<HTMLPreElement | null>(null); const codeRef = useRef<HTMLPreElement | null>(null);
@@ -28,11 +30,14 @@ export default function CodeViewer({
return ( return (
<div className="flex-1 h-full font-mono text-sm"> <div className="flex-1 h-full font-mono text-sm">
<pre ref={codeRef} className="h-full overflow-auto pt-6 pb-6 px-6 line-numbers"> <pre
ref={codeRef}
className="h-full overflow-auto pt-6 pb-6 px-6 line-numbers"
>
<code className={`${language} whitespace-pre-wrap break-words`}> <code className={`${language} whitespace-pre-wrap break-words`}>
{paste.content} {paste.content}
</code> </code>
</pre> </pre>
</div> </div>
); );
} }
+5 -2
View File
@@ -8,9 +8,12 @@ export default function PasswordForm({
return ( return (
<div className="h-screen w-screen bg-[#0e0f13] flex items-center justify-center text-white"> <div className="h-screen w-screen bg-[#0e0f13] flex items-center justify-center text-white">
<form <form
onSubmit={(e) => { onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
const pwd = (e.currentTarget as any).password.value; const form = e.currentTarget as HTMLFormElement & {
password: { value: string };
};
const pwd = form.password.value;
onSubmit(pwd); onSubmit(pwd);
}} }}
className="w-full max-w-sm rounded-2xl p-6 space-y-4 backdrop-blur-xl shadow-2xl bg-[linear-gradient(to_bottom_right,rgba(20,24,38,0.9),rgba(14,16,24,0.9))] ring-1 ring-white/10" className="w-full max-w-sm rounded-2xl p-6 space-y-4 backdrop-blur-xl shadow-2xl bg-[linear-gradient(to_bottom_right,rgba(20,24,38,0.9),rgba(14,16,24,0.9))] ring-1 ring-white/10"
+2 -1
View File
@@ -1,10 +1,11 @@
'use client'; 'use client';
import type { Paste } from "@/types/paste";
import { CalendarDays, Copy, Download, Eye, FileText, Plus } from "lucide-react"; import { CalendarDays, Copy, Download, Eye, FileText, Plus } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
export default function PasteSidebar({ paste, id }: { paste: any; id: string }) { export default function PasteSidebar({ paste, id }: { paste: Paste; id: string }) {
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const router = useRouter(); const router = useRouter();
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -22,6 +22,6 @@
"@/*": ["./*"] "@/*": ["./*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/api/paste/[id]/page.bak"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }
+8
View File
@@ -0,0 +1,8 @@
export interface Paste {
id: string;
title?: string | null;
content: string;
createdAt: string;
views: number;
protected?: boolean;
}