From 781cbd94afb970c5f044fb9a675f601e27befb45 Mon Sep 17 00:00:00 2001 From: Florian Trayon <26360935+FlorianLeChat@users.noreply.github.com> Date: Tue, 9 Jan 2024 18:49:52 +0100 Subject: [PATCH] Added user file renaming through NextJS server actions --- app/[locale]/dashboard/actions.ts | 71 ++++++++ .../dashboard/components/row-actions.tsx | 171 ++++++++++++++++-- 2 files changed, 223 insertions(+), 19 deletions(-) diff --git a/app/[locale]/dashboard/actions.ts b/app/[locale]/dashboard/actions.ts index 03d4f8d..5e95cd5 100644 --- a/app/[locale]/dashboard/actions.ts +++ b/app/[locale]/dashboard/actions.ts @@ -69,6 +69,77 @@ export async function changeFileStatus( formData: FormData ) } } +// +// Renommage du nom d'un fichier. +// +export async function renameFile( formData: FormData ) +{ + // On récupère d'abord la session de l'utilisateur. + const session = await auth(); + + if ( !session ) + { + return false; + } + + // On créé ensuite un schéma de validation personnalisé pour + // les données du formulaire. + // Note : les validations Zod du nom doivent correspondre à + // celles utilisées lors du téléversement de fichiers. + const validation = z.object( { + uuid: z.string().uuid(), + name: z.string().min( 1 ).max( 100 ) + } ); + + // On tente alors de valider les données du formulaire. + const result = validation.safeParse( { + uuid: formData.get( "uuid" ), + name: formData.get( "name" ) + } ); + + if ( !result.success ) + { + return false; + } + + try + { + // On récupère après les données du fichier depuis + // la base de données. + const file = await prisma.file.findUnique( { + where: { + fileId: result.data.uuid, + userId: session.user.id + } + } ); + + if ( !file ) + { + return false; + } + + // On renomme le fichier dans la base de données avant de + // retourner une valeur de succès. + await prisma.file.update( { + where: { + fileId: result.data.uuid, + userId: session.user.id + }, + data: { + name: result.data.name + parse( file.name ).ext + } + } ); + + return true; + } + catch + { + // En cas d'erreur lors de la transaction avec la base de données, + // on retourne enfin une valeur d'échec. + return false; + } +} + // // Téléversement d'un nouveau fichier. // diff --git a/app/[locale]/dashboard/components/row-actions.tsx b/app/[locale]/dashboard/components/row-actions.tsx index dfce03b..43da9c7 100644 --- a/app/[locale]/dashboard/components/row-actions.tsx +++ b/app/[locale]/dashboard/components/row-actions.tsx @@ -16,19 +16,23 @@ import { Ban, Share2, Loader2, History, + RefreshCw, FolderLock, ArrowUpRight, ClipboardCopy, - MoreHorizontal } from "lucide-react"; + MoreHorizontal, + TextCursorInput } from "lucide-react"; import { FileAttributes } from "@/interfaces/File"; -import { useContext, useState } from "react"; +import { useContext, useRef, useState } from "react"; +import { Input } from "../../components/ui/input"; import FileHistory from "./file-history"; import ShareManager from "./share-manager"; import { useToast } from "../../components/ui/use-toast"; import { Dialog, DialogTitle, DialogHeader, + DialogFooter, DialogTrigger, DialogContent, DialogDescription } from "../../components/ui/dialog"; @@ -49,11 +53,12 @@ import { AlertDialog, AlertDialogTrigger, AlertDialogDescription } from "../../components/ui/alert-dialog"; import { Button, buttonVariants } from "../../components/ui/button"; -import { changeFileStatus, deleteFile } from "../actions"; +import { changeFileStatus, deleteFile, renameFile } from "../actions"; export default function RowActions( { row }: { row: Row } ) { // Déclaration des constantes. + const rename = useRef( null ); const { toast } = useToast(); // Déclaration des variables d'état. @@ -62,31 +67,36 @@ export default function RowActions( { row }: { row: Row } ) const [ loading, setLoading ] = useState( "" ); const data = files.filter( ( file ) => `${ file.id }` === row.id )[ 0 ]; - // Vérification de l'état de chargement. - if ( loading === row.id ) - { - return ( - - ); - } - // Affichage du rendu HTML du composant. return ( {/* Bouton d'ouverture du menu */} + { + if ( loading === row.id ) + { + // Suppression de l'ouverture du menu si l'état de + // chargement est actif. + event.preventDefault(); + } + }} className={merge( buttonVariants( { variant: "ghost" } ), "h-8 w-8 p-0" )} > - Ouvrir le menu - - + {loading === row.id ? ( + <> + Mise à jour en cours... + + + ) : ( + <> + Ouvrir le menu + + + )} {/* Actions disponibles */} @@ -139,6 +149,12 @@ export default function RowActions( { row }: { row: Row } ) // Fermeture du menu des actions. setOpen( false ); + // Vérification de l'état du fichier. + if ( data.status === "public" ) + { + return; + } + // Activation de l'état de chargement. setLoading( row.id ); @@ -230,6 +246,12 @@ export default function RowActions( { row }: { row: Row } ) // Fermeture du menu des actions. setOpen( false ); + // Vérification de l'état du fichier. + if ( data.status === "private" ) + { + return; + } + // Activation de l'état de chargement. setLoading( row.id ); @@ -354,7 +376,118 @@ export default function RowActions( { row }: { row: Row } ) - {/* Accès et suppression */} + {/* Accès, renommage et suppression */} + + + event.preventDefault()} + > + + Renommer la ressource + + + + + + + + + + Quel sera le nouveau nom de la ressource ? + + + + + Cette action est irréversible.{" "} + Cela ne modifiera pas le lien d‘accès, ni + son extension et ni les partages actuellement + associés avec d‘autres utilisateurs. + + + + + { + data.name = event.currentTarget.value; + }} + onKeyDown={( event ) => + { + if ( event.key === "Enter" ) + { + rename.current?.click(); + } + }} + spellCheck="false" + placeholder="john-doe" + autoComplete="off" + defaultValue={data.name} + autoCapitalize="off" + /> + + + + + + +