From 612459a4ecbe6e12ea09e52247342485da9f4ee6 Mon Sep 17 00:00:00 2001 From: Anthony G <1@anthony.sh> Date: Mon, 10 Aug 2026 14:51:20 +0200 Subject: [PATCH] feat: add submission form and status page --- src/app/layout.tsx | 13 ++++ src/app/page.tsx | 10 +++ src/app/status/[uuid]/page.tsx | 15 +++++ src/components/StatusView.tsx | 101 +++++++++++++++++++++++++++++++ src/components/SubmitForm.tsx | 107 +++++++++++++++++++++++++++++++++ 5 files changed, 246 insertions(+) create mode 100644 src/app/layout.tsx create mode 100644 src/app/page.tsx create mode 100644 src/app/status/[uuid]/page.tsx create mode 100644 src/components/StatusView.tsx create mode 100644 src/components/SubmitForm.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..ac7e01f --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,13 @@ +import type { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Ombrora — Telechargeur de videos', +} + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..12a0357 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,10 @@ +import { SubmitForm } from '@/components/SubmitForm' + +export default function Home() { + return ( +
+

Ombrora

+ +
+ ) +} diff --git a/src/app/status/[uuid]/page.tsx b/src/app/status/[uuid]/page.tsx new file mode 100644 index 0000000..92287cb --- /dev/null +++ b/src/app/status/[uuid]/page.tsx @@ -0,0 +1,15 @@ +import { StatusView } from '@/components/StatusView' + +export default async function StatusPage({ + params, +}: { + params: Promise<{ uuid: string }> +}) { + const { uuid } = await params + return ( +
+

Statut du telechargement

+ +
+ ) +} diff --git a/src/components/StatusView.tsx b/src/components/StatusView.tsx new file mode 100644 index 0000000..b6e4493 --- /dev/null +++ b/src/components/StatusView.tsx @@ -0,0 +1,101 @@ +'use client' + +import { useEffect, useState } from 'react' + +type DownloadData = { + uuid: string + status: 'PENDING' | 'PROCESSING' | 'DONE' | 'FAILED' | 'FILE_DELETED' + format: string + quality: string + fileName: string | null + fileSize: string | null + errorMsg: string | null + downloadToken: string | null + tokenExpiresAt: string | null +} + +export function StatusView({ uuid }: { uuid: string }) { + const [data, setData] = useState(null) + const [notFound, setNotFound] = useState(false) + + useEffect(() => { + let active = true + + async function poll() { + const res = await fetch(`/api/downloads/${uuid}`) + if (!active) return + + if (res.status === 404) { + setNotFound(true) + return + } + + const body = (await res.json()) as DownloadData + setData(body) + + if (body.status === 'PENDING' || body.status === 'PROCESSING') { + setTimeout(poll, 5_000) + } + } + + poll() + return () => { active = false } + }, [uuid]) + + if (notFound) return

Telechargement introuvable.

+ if (!data) return

Chargement...

+ + if (data.status === 'PENDING' || data.status === 'PROCESSING') { + return ( +
+

Statut : {data.status === 'PENDING' ? 'En attente' : 'En cours...'}

+

Cette page se rafraichit automatiquement toutes les 5 secondes.

+
+ ) + } + + if (data.status === 'DONE' && data.downloadToken) { + const sizeMb = data.fileSize + ? `(${(Number(data.fileSize) / 1_048_576).toFixed(1)} Mo)` + : '' + return ( +
+

Telechargement pret {sizeMb}

+ + Telecharger {data.fileName} + + {data.tokenExpiresAt && ( +

+ Lien valable jusqu'au{' '} + {new Date(data.tokenExpiresAt).toLocaleString('fr-FR')} +

+ )} +
+ ) + } + + if (data.status === 'FAILED') { + return ( +
+

Echec du telechargement.

+
+          {data.errorMsg}
+        
+
+ ) + } + + if (data.status === 'FILE_DELETED') { + return ( +

+ Le fichier a expire et a ete supprime. Le telechargement n'est plus + disponible. +

+ ) + } + + return

Statut inconnu : {data.status}

+} diff --git a/src/components/SubmitForm.tsx b/src/components/SubmitForm.tsx new file mode 100644 index 0000000..8d03798 --- /dev/null +++ b/src/components/SubmitForm.tsx @@ -0,0 +1,107 @@ +'use client' + +import { useState, FormEvent } from 'react' +import { useRouter } from 'next/navigation' + +const FORMATS = ['mp4', 'mp3', 'webm', 'mkv'] +const QUALITIES = ['best', '1080p', '720p', '480p', '360p'] + +export function SubmitForm() { + const router = useRouter() + const [url, setUrl] = useState('') + const [format, setFormat] = useState('mp4') + const [quality, setQuality] = useState('best') + const [subtitles, setSubtitles] = useState(false) + const [extraArgs, setExtraArgs] = useState('') + const [error, setError] = useState(null) + const [loading, setLoading] = useState(false) + + async function handleSubmit(e: FormEvent) { + e.preventDefault() + setError(null) + setLoading(true) + + const res = await fetch('/api/downloads', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + url, + format, + quality, + subtitles, + extraArgs: extraArgs.trim() || null, + }), + }) + + setLoading(false) + + if (res.status === 429) { + setError('Trop de soumissions. Reessayez dans une heure.') + return + } + if (!res.ok) { + const body = await res.json().catch(() => ({})) + setError((body as { error?: string }).error ?? 'Erreur lors de la soumission.') + return + } + + const { uuid } = (await res.json()) as { uuid: string } + router.push(`/status/${uuid}`) + } + + return ( +
+ + + + + + + + + + + {error &&

{error}

} + + +
+ ) +}