diff --git a/app/[id]/page.tsx b/app/[id]/page.tsx index 6298ec4..948cea3 100644 --- a/app/[id]/page.tsx +++ b/app/[id]/page.tsx @@ -1,114 +1,40 @@ 'use client'; -import { - CalendarDays, - Copy, - Download, - Eye, - FileText, - Plus, -} from 'lucide-react'; import { useRouter } from 'next/navigation'; -import { useEffect, useRef, useState } from 'react'; - import Prism from 'prismjs'; +import { use, useEffect, useState } from 'react'; + import 'prismjs/components/prism-bash'; import 'prismjs/components/prism-javascript'; import 'prismjs/components/prism-json'; import 'prismjs/components/prism-python'; import 'prismjs/components/prism-typescript'; -import 'prismjs/themes/prism-tomorrow.css'; - import 'prismjs/plugins/line-numbers/prism-line-numbers.css'; import 'prismjs/plugins/line-numbers/prism-line-numbers.js'; +import 'prismjs/themes/prism-tomorrow.css'; -/** ---------- Loader animé (spinner + skeletons) ---------- */ -function LoadingPaste() { - return ( -
- {/* Zone code (skeleton lignes) */} -
-
-
-
- Chargement du paste… -
-
- {Array.from({ length: 28 }).map((_, i) => ( -
- ))} -
-
-
+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'; - {/* Sidebar (skeleton cartes + boutons) */} -
-
-
-
-
-
-
-
+export default function PastePage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = use(params); + const router = useRouter(); -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- -
- {Array.from({ length: 4 }).map((_, i) => ( -
- ))} -
- -
-
-
- ); -} -/** -------------------------------------------------------- */ - -export default function PastePage({ params }: { params: { id: string } }) { const [paste, setPaste] = useState(null); const [error, setError] = useState(''); const [loading, setLoading] = useState(true); const [language, setLanguage] = useState('language-javascript'); - const [copied, setCopied] = useState(false); - - const codeRef = useRef(null); - const router = useRouter(); const fetchPaste = async (pwd?: string) => { setLoading(true); - const res = await fetch(`/api/paste/${params.id}`, { + const res = await fetch(`/api/paste/${id}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: pwd || '' }), @@ -144,7 +70,7 @@ export default function PastePage({ params }: { params: { id: string } }) { useEffect(() => { fetchPaste(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [id]); useEffect(() => { if (paste?.content) { @@ -152,179 +78,18 @@ export default function PastePage({ params }: { params: { id: string } }) { } }, [paste]); - // 👉 Remplace l'ancien message "Loading..." par le loader animé if (loading) return ; if (error || (paste?.protected && !paste?.content)) { - return ( -
-
{ - e.preventDefault(); - fetchPaste((e.currentTarget as any).password.value); - }} - 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 - " - > -

- 🔒 Protected Paste -

- {error &&

{error}

} - - - - -
-
- ); + return fetchPaste(pwd)} />; } return (
-
-
-          
-            {paste.content}
-          
-        
-
- -
-
-
-

- AltBin -

- -
- {paste.title && ( -

- {paste.title} -

- )} -
- -
-
- -
-
Views
-
{paste.views}
-
-
-
- -
-
Created
-
- {new Date(paste.createdAt).toLocaleDateString()} -
-
-
-
- -
- -
- - - - - - - -
- -
- Copied! -
-
+ + +
); + } \ No newline at end of file diff --git a/app/api/paste/[id]/route.ts b/app/api/paste/[id]/route.ts index 2c46997..23bf75f 100644 --- a/app/api/paste/[id]/route.ts +++ b/app/api/paste/[id]/route.ts @@ -5,12 +5,51 @@ import { NextRequest, NextResponse } from 'next/server'; export const dynamic = 'force-dynamic'; export const revalidate = 0; +// --- GET pour raw ou JSON --- +export async function GET( + request: NextRequest, + context: { params: Promise<{ id: string }> } +) { + const { id: pasteId } = await context.params; // ✅ await obligatoire + + if (!pasteId) { + return NextResponse.json({ error: 'Invalid ID' }, { status: 400 }); + } + + const paste = await prisma.paste.findUnique({ where: { id: pasteId } }); + if (!paste) { + return NextResponse.json({ error: 'Not found' }, { status: 404 }); + } + + // Si paste protégé, on bloque le GET + if (paste.password) { + return NextResponse.json({ error: 'This paste is protected' }, { status: 403 }); + } + + const searchParams = request.nextUrl.searchParams; + if (searchParams.get('raw') === '1') { + return new NextResponse(paste.content, { + status: 200, + headers: { 'Content-Type': 'text/plain; charset=utf-8' }, + }); + } + + return NextResponse.json({ + id: paste.id, + title: paste.title, + content: paste.content, + createdAt: paste.createdAt, + views: paste.views, + protected: !!paste.password, + }); +} + +// --- POST pour récupérer en validant le password --- export async function POST( request: NextRequest, - context: { params: { id: string } } + context: { params: Promise<{ id: string }> } ) { - const { params } = context; - const pasteId = params.id; + const { id: pasteId } = await context.params; // ✅ ici aussi const { password = '' } = await request.json(); @@ -65,4 +104,4 @@ export async function POST( return NextResponse.json(payload, { headers: { 'Cache-Control': 'no-store' }, }); -} +} \ No newline at end of file diff --git a/app/raw/[id]/route.ts b/app/raw/[id]/route.ts new file mode 100644 index 0000000..07e696a --- /dev/null +++ b/app/raw/[id]/route.ts @@ -0,0 +1,25 @@ +import { prisma } from "@/lib/prisma"; +import { NextResponse } from "next/server"; + +export async function GET( + request: Request, + { params }: { params: { id: string } } +) { + const { id } = params; + + if (!id) { + return new NextResponse("Invalid ID", { status: 400 }); + } + + const paste = await prisma.paste.findUnique({ where: { id } }); + if (!paste) { + return new NextResponse("Paste not found", { status: 404 }); + } + + return new NextResponse(paste.content, { + headers: { + "Content-Type": "text/plain; charset=utf-8", + "Cache-Control": "no-store", + }, + }); +} \ No newline at end of file diff --git a/app/raw/page.tsx b/app/raw/page.tsx new file mode 100644 index 0000000..75eab21 --- /dev/null +++ b/app/raw/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function RawIndexPage() { + redirect("/"); +} \ No newline at end of file diff --git a/components/paste/CodeViewer.tsx b/components/paste/CodeViewer.tsx new file mode 100644 index 0000000..561e07a --- /dev/null +++ b/components/paste/CodeViewer.tsx @@ -0,0 +1,38 @@ +'use client'; + +import Prism from 'prismjs'; +import 'prismjs/components/prism-bash'; +import 'prismjs/components/prism-javascript'; +import 'prismjs/components/prism-json'; +import 'prismjs/components/prism-python'; +import 'prismjs/components/prism-typescript'; +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 { useEffect, useRef } from 'react'; + +export default function CodeViewer({ + paste, + language, +}: { + paste: any; + language: string; +}) { + const codeRef = useRef(null); + + useEffect(() => { + if (paste?.content) { + Prism.highlightAll(); + } + }, [paste, language]); + + return ( +
+
+        
+          {paste.content}
+        
+      
+
+ ); +} diff --git a/components/paste/LoadingPaste.tsx b/components/paste/LoadingPaste.tsx new file mode 100644 index 0000000..8c6ca94 --- /dev/null +++ b/components/paste/LoadingPaste.tsx @@ -0,0 +1,62 @@ +export default function LoadingPaste() { + return ( +
+
+
+
+
+ Loading paste… +
+
+ {Array.from({ length: 28 }).map((_, i) => ( +
+ ))} +
+
+
+ +
+
+
+
+
+
+
+
+ +
+ {[...Array(2)].map((_, i) => ( +
+
+
+
+
+
+
+ ))} +
+ +
+ +
+ {[...Array(4)].map((_, i) => ( +
+ ))} +
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/components/paste/PasswordForm.tsx b/components/paste/PasswordForm.tsx new file mode 100644 index 0000000..13c1bde --- /dev/null +++ b/components/paste/PasswordForm.tsx @@ -0,0 +1,39 @@ +export default function PasswordForm({ + onSubmit, + error, +}: { + onSubmit: (pwd: string) => void; + error?: string; +}) { + return ( +
+
{ + e.preventDefault(); + const pwd = (e.currentTarget as any).password.value; + 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" + > +

+ 🔒 Protected Paste +

+ {error &&

{error}

} + + + + +
+
+ ); +} \ No newline at end of file diff --git a/components/paste/PasteSidebar.tsx b/components/paste/PasteSidebar.tsx new file mode 100644 index 0000000..214f933 --- /dev/null +++ b/components/paste/PasteSidebar.tsx @@ -0,0 +1,107 @@ +'use client'; + +import { CalendarDays, Copy, Download, Eye, FileText, Plus } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; + +export default function PasteSidebar({ paste, id }: { paste: any; id: string }) { + const [copied, setCopied] = useState(false); + const router = useRouter(); + + return ( +
+
+
+

+ AltBin +

+ +
+ {paste.title && ( +

+ {paste.title} +

+ )} +
+ + {/* Infos */} +
+
+ +
+
Views
+
{paste.views}
+
+
+
+ +
+
Created
+
+ {new Date(paste.createdAt).toLocaleDateString()} +
+
+
+
+ +
+ + {/* Actions */} +
+ + + + +
+ +
+ Copied! +
+
+ ); +} \ No newline at end of file diff --git a/prisma/altbin.db b/prisma/altbin.db index ae4f326..cbb331b 100644 Binary files a/prisma/altbin.db and b/prisma/altbin.db differ