From f6170fa581df55575f62466aba63d203760e0df3 Mon Sep 17 00:00:00 2001 From: UltraLionFr Date: Wed, 20 Aug 2025 15:16:00 +0200 Subject: [PATCH] Update code --- app/[id]/page.tsx | 279 +++--------------------------- app/api/paste/[id]/route.ts | 47 ++++- app/raw/[id]/route.ts | 25 +++ app/raw/page.tsx | 5 + components/paste/CodeViewer.tsx | 38 ++++ components/paste/LoadingPaste.tsx | 62 +++++++ components/paste/PasswordForm.tsx | 39 +++++ components/paste/PasteSidebar.tsx | 107 ++++++++++++ prisma/altbin.db | Bin 57344 -> 65536 bytes 9 files changed, 341 insertions(+), 261 deletions(-) create mode 100644 app/raw/[id]/route.ts create mode 100644 app/raw/page.tsx create mode 100644 components/paste/CodeViewer.tsx create mode 100644 components/paste/LoadingPaste.tsx create mode 100644 components/paste/PasswordForm.tsx create mode 100644 components/paste/PasteSidebar.tsx 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 ae4f3262b22b4c69f3d0daeba2b191e91a43b88e..cbb331b54ad590a1ef7d55e183d96b1ee94ae875 100644 GIT binary patch delta 7868 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 literal 57344 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