mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
Feature: Remplacement de tous les prompts par des modaux + Fix RSS refresh
Modaux : - Création ConfirmModal pour confirmations avec variants (default/destructive) - Création InputModal pour saisie de texte/URL/mot de passe - Création AddUserModal pour création d'utilisateur avec admin checkbox - Remplacement de tous les prompt() et confirm() par des modaux modernes - Modaux pour : suppression note, ajout todo, ajout RSS, ajout utilisateur, suppression utilisateur, changement mot de passe, ajout todo dans note Fix RSS : - Ajout route POST /api/rss/refresh (alias de /api/rss/fetch) - Fix uploadImage retour avec result.image || result UX améliorée : - Plus de popups navigateur natifs - Interface cohérente et moderne - Validation dans les modaux - Support touche Entrée dans les InputModals - Auto-focus sur le champ de saisie
This commit is contained in:
6 files changed
+409
-81
No files matched your search
@@ -130,6 +130,26 @@ router.post('/fetch', requireAdmin, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/rss/refresh
|
||||
* Alias pour fetch (pour compatibilité)
|
||||
*/
|
||||
router.post('/refresh', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const rssScheduler = require('../services/rss-scheduler');
|
||||
await rssScheduler.manualFetch();
|
||||
|
||||
// Invalider le cache
|
||||
articlesCache = null;
|
||||
articlesCacheTime = 0;
|
||||
|
||||
res.json({ message: 'Mise à jour des flux RSS terminée avec succès' });
|
||||
} catch (error) {
|
||||
logger.error('Erreur lors du refresh des flux RSS:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
// Cache pour les articles RSS (30 secondes)
|
||||
let articlesCache = null;
|
||||
let articlesCacheTime = 0;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { useState, useEffect } from "react";
|
||||
import { UserPlus } from "lucide-react";
|
||||
|
||||
interface AddUserModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onConfirm: (username: string, password: string, isAdmin: boolean) => void;
|
||||
}
|
||||
|
||||
export function AddUserModal({ open, onOpenChange, onConfirm }: AddUserModalProps) {
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setUsername("");
|
||||
setPassword("");
|
||||
setIsAdmin(false);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (username.trim() && password.trim()) {
|
||||
onConfirm(username.trim(), password.trim(), isAdmin);
|
||||
onOpenChange(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isValid = username.trim().length > 0 && password.trim().length > 0;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<UserPlus className="h-5 w-5" />
|
||||
Ajouter un utilisateur
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Créez un nouveau compte utilisateur pour l'application
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="username">Nom d'utilisateur</Label>
|
||||
<Input
|
||||
id="username"
|
||||
placeholder="Nom d'utilisateur"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="password">Mot de passe</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="Mot de passe"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="is-admin"
|
||||
checked={isAdmin}
|
||||
onCheckedChange={(checked) => setIsAdmin(checked as boolean)}
|
||||
/>
|
||||
<Label
|
||||
htmlFor="is-admin"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Administrateur
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button onClick={handleConfirm} disabled={!isValid}>
|
||||
Créer l'utilisateur
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
|
||||
interface ConfirmModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
title: string;
|
||||
description: string;
|
||||
onConfirm: () => void;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
variant?: "default" | "destructive";
|
||||
}
|
||||
|
||||
export function ConfirmModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
title,
|
||||
description,
|
||||
onConfirm,
|
||||
confirmText = "Confirmer",
|
||||
cancelText = "Annuler",
|
||||
variant = "default"
|
||||
}: ConfirmModalProps) {
|
||||
const handleConfirm = () => {
|
||||
onConfirm();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
{variant === "destructive" && <AlertTriangle className="h-5 w-5 text-destructive" />}
|
||||
{title}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{description}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
{cancelText}
|
||||
</Button>
|
||||
<Button
|
||||
variant={variant}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
{confirmText}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
interface InputModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
title: string;
|
||||
description?: string;
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
defaultValue?: string;
|
||||
onConfirm: (value: string) => void;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
type?: "text" | "password" | "url" | "email";
|
||||
}
|
||||
|
||||
export function InputModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
title,
|
||||
description,
|
||||
label,
|
||||
placeholder,
|
||||
defaultValue = "",
|
||||
onConfirm,
|
||||
confirmText = "Confirmer",
|
||||
cancelText = "Annuler",
|
||||
type = "text"
|
||||
}: InputModalProps) {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setValue(defaultValue);
|
||||
}
|
||||
}, [open, defaultValue]);
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (value.trim()) {
|
||||
onConfirm(value.trim());
|
||||
onOpenChange(false);
|
||||
setValue("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter" && value.trim()) {
|
||||
e.preventDefault();
|
||||
handleConfirm();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
{description && <DialogDescription>{description}</DialogDescription>}
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="input-field">{label}</Label>
|
||||
<Input
|
||||
id="input-field"
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
{cancelText}
|
||||
</Button>
|
||||
<Button onClick={handleConfirm} disabled={!value.trim()}>
|
||||
{confirmText}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
+143
-77
@@ -24,6 +24,9 @@ import SettingsService, { Settings as AppSettings } from "@/services/SettingsSer
|
||||
import CalendarService, { CalendarEvent } from "@/services/CalendarService";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { showError, showSuccess } from "@/utils/toast";
|
||||
import { ConfirmModal } from "@/components/modals/ConfirmModal";
|
||||
import { InputModal } from "@/components/modals/InputModal";
|
||||
import { AddUserModal } from "@/components/modals/AddUserModal";
|
||||
|
||||
interface UserType {
|
||||
id: number;
|
||||
@@ -47,6 +50,15 @@ const Index = () => {
|
||||
const [showArchived, setShowArchived] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Modal states
|
||||
const [deleteNoteModal, setDeleteNoteModal] = useState(false);
|
||||
const [addTodoModal, setAddTodoModal] = useState(false);
|
||||
const [addRssFeedModal, setAddRssFeedModal] = useState(false);
|
||||
const [addUserModal, setAddUserModal] = useState(false);
|
||||
const [deleteUserModal, setDeleteUserModal] = useState<{open: boolean, userId?: number}>({open: false});
|
||||
const [changePasswordModal, setChangePasswordModal] = useState<{open: boolean, userId?: number}>({open: false});
|
||||
const [addNoteTodoModal, setAddNoteTodoModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const userFromAuth = AuthService.getUser();
|
||||
if (userFromAuth) {
|
||||
@@ -195,37 +207,32 @@ const Index = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteNote = async () => {
|
||||
const confirmDeleteNote = async () => {
|
||||
if (!openNote?.id) return;
|
||||
|
||||
if (window.confirm("Êtes-vous sûr de vouloir supprimer cette note ?")) {
|
||||
try {
|
||||
const success = await NotesService.deleteNote(openNote.id);
|
||||
try {
|
||||
const success = await NotesService.deleteNote(openNote.id);
|
||||
|
||||
if (success) {
|
||||
const updatedNotes = notes.filter(note => note.id !== openNote.id);
|
||||
setNotes(updatedNotes);
|
||||
setOpenNote(null);
|
||||
showSuccess("Note supprimée");
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Erreur lors de la suppression");
|
||||
if (success) {
|
||||
const updatedNotes = notes.filter(note => note.id !== openNote.id);
|
||||
setNotes(updatedNotes);
|
||||
setOpenNote(null);
|
||||
showSuccess("Note supprimée");
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Erreur lors de la suppression");
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddTodo = async () => {
|
||||
const text = prompt("Nouvelle tâche:");
|
||||
if (text) {
|
||||
try {
|
||||
const newTodo = await TodosService.createTodo(text);
|
||||
if (newTodo) {
|
||||
setTodos([newTodo, ...todos]);
|
||||
showSuccess("Tâche ajoutée");
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Erreur lors de l'ajout de la tâche");
|
||||
const confirmAddTodo = async (text: string) => {
|
||||
try {
|
||||
const newTodo = await TodosService.createTodo(text);
|
||||
if (newTodo) {
|
||||
setTodos([newTodo, ...todos]);
|
||||
showSuccess("Tâche ajoutée");
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Erreur lors de l'ajout de la tâche");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -248,19 +255,16 @@ const Index = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddRssFeed = async () => {
|
||||
const url = prompt("URL du flux RSS:");
|
||||
if (url) {
|
||||
try {
|
||||
const newFeed = await RssService.addFeed(url);
|
||||
if (newFeed) {
|
||||
setRssFeeds([...rssFeeds, newFeed]);
|
||||
showSuccess("Flux RSS ajouté");
|
||||
loadRssArticles();
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Erreur lors de l'ajout du flux");
|
||||
const confirmAddRssFeed = async (url: string) => {
|
||||
try {
|
||||
const newFeed = await RssService.addFeed(url);
|
||||
if (newFeed) {
|
||||
setRssFeeds([...rssFeeds, newFeed]);
|
||||
showSuccess("Flux RSS ajouté");
|
||||
loadRssArticles();
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Erreur lors de l'ajout du flux");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -285,13 +289,7 @@ const Index = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddUser = async () => {
|
||||
const username = prompt("Nom d'utilisateur:");
|
||||
if (!username) return;
|
||||
const password = prompt("Mot de passe:");
|
||||
if (!password) return;
|
||||
const isAdmin = window.confirm("Donner les droits administrateur ?");
|
||||
|
||||
const confirmAddUser = async (username: string, password: string, isAdmin: boolean) => {
|
||||
try {
|
||||
const newUser = await AdminService.createUser(username, password, isAdmin);
|
||||
if (newUser) {
|
||||
@@ -303,31 +301,30 @@ const Index = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteUser = async (userId: number) => {
|
||||
if (window.confirm("Êtes-vous sûr de vouloir supprimer cet utilisateur ?")) {
|
||||
try {
|
||||
const success = await AdminService.deleteUser(userId);
|
||||
if (success) {
|
||||
loadUsers();
|
||||
showSuccess("Utilisateur supprimé");
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Erreur lors de la suppression");
|
||||
const confirmDeleteUser = async () => {
|
||||
if (!deleteUserModal.userId) return;
|
||||
|
||||
try {
|
||||
const success = await AdminService.deleteUser(deleteUserModal.userId);
|
||||
if (success) {
|
||||
loadUsers();
|
||||
showSuccess("Utilisateur supprimé");
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Erreur lors de la suppression");
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangePassword = async (userId: number) => {
|
||||
const newPassword = prompt("Nouveau mot de passe:");
|
||||
if (newPassword) {
|
||||
try {
|
||||
const success = await AdminService.updateUserPassword(userId, newPassword);
|
||||
if (success) {
|
||||
showSuccess("Mot de passe modifié");
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Erreur lors de la modification");
|
||||
const confirmChangePassword = async (newPassword: string) => {
|
||||
if (!changePasswordModal.userId) return;
|
||||
|
||||
try {
|
||||
const success = await AdminService.updateUserPassword(changePasswordModal.userId, newPassword);
|
||||
if (success) {
|
||||
showSuccess("Mot de passe modifié");
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Erreur lors de la modification");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -548,7 +545,7 @@ const Index = () => {
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleDeleteNote}
|
||||
onClick={() => setDeleteNoteModal(true)}
|
||||
title="Supprimer"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
@@ -596,13 +593,7 @@ const Index = () => {
|
||||
|
||||
<TabsContent value="todos" className="mt-4 space-y-4">
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const text = prompt("Nouvelle tâche:");
|
||||
if (text) {
|
||||
const updatedTodos = [...(openNote.todos || []), { text, completed: false }];
|
||||
await handleUpdateNote({ todos: updatedTodos });
|
||||
}
|
||||
}}
|
||||
onClick={() => setAddNoteTodoModal(true)}
|
||||
className="w-full"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
@@ -780,7 +771,7 @@ const Index = () => {
|
||||
<CheckSquare className="h-6 w-6" />
|
||||
Tâches
|
||||
</CardTitle>
|
||||
<Button size="lg" variant="outline" onClick={handleAddTodo} className="gap-2">
|
||||
<Button size="lg" variant="outline" onClick={() => setAddTodoModal(true)} className="gap-2">
|
||||
<Plus className="h-5 w-5" />
|
||||
Ajouter
|
||||
</Button>
|
||||
@@ -867,7 +858,7 @@ const Index = () => {
|
||||
<RefreshCw className="h-5 w-5" />
|
||||
Actualiser
|
||||
</Button>
|
||||
<Button size="lg" variant="outline" onClick={handleAddRssFeed} className="gap-2">
|
||||
<Button size="lg" variant="outline" onClick={() => setAddRssFeedModal(true)} className="gap-2">
|
||||
<Plus className="h-5 w-5" />
|
||||
Ajouter
|
||||
</Button>
|
||||
@@ -903,7 +894,7 @@ const Index = () => {
|
||||
<Button
|
||||
variant="link"
|
||||
className="mt-2"
|
||||
onClick={handleAddRssFeed}
|
||||
onClick={() => setAddRssFeedModal(true)}
|
||||
>
|
||||
Ajouter un flux RSS
|
||||
</Button>
|
||||
@@ -1008,7 +999,7 @@ const Index = () => {
|
||||
<TabsContent value="users" className="mt-4 space-y-4">
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
onClick={handleAddUser}
|
||||
onClick={() => setAddUserModal(true)}
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
Ajouter un utilisateur
|
||||
@@ -1033,7 +1024,7 @@ const Index = () => {
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleChangePassword(u.id)}
|
||||
onClick={() => setChangePasswordModal({open: true, userId: u.id})}
|
||||
>
|
||||
<Key className="h-4 w-4 mr-2" />
|
||||
Mot de passe
|
||||
@@ -1042,7 +1033,7 @@ const Index = () => {
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteUser(u.id)}
|
||||
onClick={() => setDeleteUserModal({open: true, userId: u.id})}
|
||||
>
|
||||
Supprimer
|
||||
</Button>
|
||||
@@ -1057,7 +1048,7 @@ const Index = () => {
|
||||
<TabsContent value="rss" className="mt-4 space-y-4">
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
onClick={handleAddRssFeed}
|
||||
onClick={() => setAddRssFeedModal(true)}
|
||||
>
|
||||
<Rss className="h-4 w-4" />
|
||||
Ajouter un flux RSS
|
||||
@@ -1228,6 +1219,81 @@ const Index = () => {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Modals */}
|
||||
<ConfirmModal
|
||||
open={deleteNoteModal}
|
||||
onOpenChange={setDeleteNoteModal}
|
||||
title="Supprimer la note"
|
||||
description="Êtes-vous sûr de vouloir supprimer cette note ? Cette action est irréversible."
|
||||
onConfirm={confirmDeleteNote}
|
||||
confirmText="Supprimer"
|
||||
cancelText="Annuler"
|
||||
variant="destructive"
|
||||
/>
|
||||
|
||||
<InputModal
|
||||
open={addTodoModal}
|
||||
onOpenChange={setAddTodoModal}
|
||||
title="Nouvelle tâche"
|
||||
label="Tâche"
|
||||
placeholder="Entrez la tâche..."
|
||||
onConfirm={confirmAddTodo}
|
||||
confirmText="Ajouter"
|
||||
/>
|
||||
|
||||
<InputModal
|
||||
open={addRssFeedModal}
|
||||
onOpenChange={setAddRssFeedModal}
|
||||
title="Ajouter un flux RSS"
|
||||
label="URL du flux RSS"
|
||||
placeholder="https://example.com/feed.xml"
|
||||
onConfirm={confirmAddRssFeed}
|
||||
confirmText="Ajouter"
|
||||
type="url"
|
||||
/>
|
||||
|
||||
<AddUserModal
|
||||
open={addUserModal}
|
||||
onOpenChange={setAddUserModal}
|
||||
onConfirm={confirmAddUser}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
open={deleteUserModal.open}
|
||||
onOpenChange={(open) => setDeleteUserModal({open})}
|
||||
title="Supprimer l'utilisateur"
|
||||
description="Êtes-vous sûr de vouloir supprimer cet utilisateur ? Cette action est irréversible."
|
||||
onConfirm={confirmDeleteUser}
|
||||
confirmText="Supprimer"
|
||||
variant="destructive"
|
||||
/>
|
||||
|
||||
<InputModal
|
||||
open={changePasswordModal.open}
|
||||
onOpenChange={(open) => setChangePasswordModal({open})}
|
||||
title="Changer le mot de passe"
|
||||
label="Nouveau mot de passe"
|
||||
placeholder="Entrez le nouveau mot de passe..."
|
||||
onConfirm={confirmChangePassword}
|
||||
confirmText="Modifier"
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<InputModal
|
||||
open={addNoteTodoModal}
|
||||
onOpenChange={setAddNoteTodoModal}
|
||||
title="Nouvelle tâche"
|
||||
label="Tâche"
|
||||
placeholder="Entrez la tâche..."
|
||||
onConfirm={async (text) => {
|
||||
if (openNote) {
|
||||
const updatedTodos = [...(openNote.todos || []), { text, completed: false }];
|
||||
await handleUpdateNote({ todos: updatedTodos });
|
||||
}
|
||||
}}
|
||||
confirmText="Ajouter"
|
||||
/>
|
||||
|
||||
<div className="fixed bottom-4 right-4">
|
||||
<MadeWithDyad />
|
||||
</div>
|
||||
|
||||
@@ -130,7 +130,7 @@ class NotesService {
|
||||
async uploadImage(noteId: number, file: File): Promise<Image | null> {
|
||||
const formData = new FormData();
|
||||
formData.append("image", file);
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/notes/${noteId}/images`, {
|
||||
method: "POST",
|
||||
@@ -139,12 +139,13 @@ class NotesService {
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Erreur lors de l'upload de l'image");
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
|
||||
const result = await response.json();
|
||||
return result.image || result;
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : "Erreur serveur");
|
||||
return null;
|
||||
|
||||
Reference in new issue
Block a user