mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
[dyad] wrote 5 file(s)
This commit is contained in:
1 parent
1ce71f37c2
commit
209c48ee6b
4 files changed
+90
-53
No files matched your search
+7
-2
@@ -27,8 +27,13 @@ const App = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Vérifier le token au démarrage
|
// Vérifier le token au démarrage
|
||||||
const checkAuth = async () => {
|
const checkAuth = async () => {
|
||||||
const isValid = await AuthService.checkTokenValidity();
|
try {
|
||||||
setIsLoading(false);
|
await AuthService.checkTokenValidity();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking token validity:", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
checkAuth();
|
checkAuth();
|
||||||
|
|||||||
+61
-45
@@ -13,36 +13,44 @@ import AdminService from "@/services/AdminService";
|
|||||||
import NotesService, { Note } from "@/services/NotesService";
|
import NotesService, { Note } from "@/services/NotesService";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
interface UserType {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
is_admin: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const Index = () => {
|
const Index = () => {
|
||||||
const [user, setUser] = useState<any>(null);
|
const [user, setUser] = useState<UserType | null>(null);
|
||||||
const [showAdmin, setShowAdmin] = useState(false);
|
const [showAdmin, setShowAdmin] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState("all");
|
const [activeTab, setActiveTab] = useState("all");
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [notes, setNotes] = useState<Note[]>([]);
|
const [notes, setNotes] = useState<Note[]>([]);
|
||||||
const [currentNote, setCurrentNote] = useState<Note | null>(null);
|
const [currentNote, setCurrentNote] = useState<Note | null>(null);
|
||||||
const [users, setUsers] = useState<any[]>([]);
|
const [users, setUsers] = useState<UserType[]>([]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const user = AuthService.getUser();
|
const userFromAuth = AuthService.getUser();
|
||||||
if (user) {
|
if (userFromAuth) {
|
||||||
setUser(user);
|
setUser(userFromAuth);
|
||||||
loadNotes();
|
loadNotes();
|
||||||
|
} else {
|
||||||
|
navigate("/login");
|
||||||
}
|
}
|
||||||
}, []);
|
}, [navigate]);
|
||||||
|
|
||||||
const loadNotes = async () => {
|
const loadNotes = async () => {
|
||||||
const notes = await NotesService.getNotes();
|
const fetchedNotes = await NotesService.getNotes();
|
||||||
setNotes(notes);
|
setNotes(fetchedNotes || []);
|
||||||
if (notes.length > 0) {
|
if (fetchedNotes && fetchedNotes.length > 0) {
|
||||||
setCurrentNote(notes[0]);
|
setCurrentNote(fetchedNotes[0]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadUsers = async () => {
|
const loadUsers = async () => {
|
||||||
if (user?.is_admin) {
|
if (user?.is_admin) {
|
||||||
const users = await AdminService.getUsers();
|
const fetchedUsers = await AdminService.getUsers();
|
||||||
setUsers(users);
|
setUsers(fetchedUsers || []);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,7 +70,13 @@ const Index = () => {
|
|||||||
const handleUpdateNote = async (updatedFields: Partial<Note>) => {
|
const handleUpdateNote = async (updatedFields: Partial<Note>) => {
|
||||||
if (!currentNote) return;
|
if (!currentNote) return;
|
||||||
|
|
||||||
const updatedNote = { ...currentNote, ...updatedFields };
|
const updatedNote = {
|
||||||
|
...currentNote,
|
||||||
|
...updatedFields,
|
||||||
|
todos: currentNote.todos || [],
|
||||||
|
images: currentNote.images || []
|
||||||
|
};
|
||||||
|
|
||||||
const success = await NotesService.updateNote(updatedNote);
|
const success = await NotesService.updateNote(updatedNote);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -137,7 +151,7 @@ const Index = () => {
|
|||||||
(activeTab === "all" || (activeTab === "archived" && note.archived)) &&
|
(activeTab === "all" || (activeTab === "archived" && note.archived)) &&
|
||||||
(searchQuery === "" ||
|
(searchQuery === "" ||
|
||||||
note.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
note.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
note.content.toLowerCase().includes(searchQuery.toLowerCase()))
|
(note.content && note.content.toLowerCase().includes(searchQuery.toLowerCase())))
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -165,33 +179,35 @@ const Index = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<DropdownMenu>
|
{user && (
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenu>
|
||||||
<Button variant="ghost" className="flex items-center gap-2">
|
<DropdownMenuTrigger asChild>
|
||||||
<Avatar className="h-8 w-8">
|
<Button variant="ghost" className="flex items-center gap-2">
|
||||||
<AvatarFallback className="bg-primary/10 text-primary">
|
<Avatar className="h-8 w-8">
|
||||||
{user?.username?.charAt(0).toUpperCase() || "U"}
|
<AvatarFallback className="bg-primary/10 text-primary">
|
||||||
</AvatarFallback>
|
{user.username?.charAt(0).toUpperCase() || "U"}
|
||||||
</Avatar>
|
</AvatarFallback>
|
||||||
<span className="font-medium">{user?.username}</span>
|
</Avatar>
|
||||||
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
<span className="font-medium">{user.username}</span>
|
||||||
</Button>
|
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
||||||
</DropdownMenuTrigger>
|
</Button>
|
||||||
<DropdownMenuContent align="end" className="w-56">
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuLabel>Mon compte</DropdownMenuLabel>
|
<DropdownMenuContent align="end" className="w-56">
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuLabel>Mon compte</DropdownMenuLabel>
|
||||||
{user?.is_admin && (
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem onClick={() => setShowAdmin(true)}>
|
{user.is_admin && (
|
||||||
<Settings className="mr-2 h-4 w-4" />
|
<DropdownMenuItem onClick={() => setShowAdmin(true)}>
|
||||||
<span>Administration</span>
|
<Settings className="mr-2 h-4 w-4" />
|
||||||
|
<span>Administration</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
<DropdownMenuItem onClick={handleLogout}>
|
||||||
|
<LogOut className="mr-2 h-4 w-4" />
|
||||||
|
<span>Déconnexion</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
</DropdownMenuContent>
|
||||||
<DropdownMenuItem onClick={handleLogout}>
|
</DropdownMenu>
|
||||||
<LogOut className="mr-2 h-4 w-4" />
|
)}
|
||||||
<span>Déconnexion</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -259,7 +275,7 @@ const Index = () => {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Titre de la note"
|
placeholder="Titre de la note"
|
||||||
className="text-xl font-semibold border-none shadow-none text-2xl h-auto px-0 focus-visible:ring-0"
|
className="text-xl font-semibold border-none shadow-none text-2xl h-auto px-0 focus-visible:ring-0"
|
||||||
value={currentNote.title}
|
value={currentNote.title || ""}
|
||||||
onChange={(e) => handleUpdateNote({ title: e.target.value })}
|
onChange={(e) => handleUpdateNote({ title: e.target.value })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -295,7 +311,7 @@ const Index = () => {
|
|||||||
className="min-h-[500px] outline-none"
|
className="min-h-[500px] outline-none"
|
||||||
contentEditable
|
contentEditable
|
||||||
suppressContentEditableWarning
|
suppressContentEditableWarning
|
||||||
dangerouslySetInnerHTML={{ __html: currentNote.content }}
|
dangerouslySetInnerHTML={{ __html: currentNote.content || "" }}
|
||||||
onBlur={(e) => handleUpdateNote({ content: e.currentTarget.innerHTML })}
|
onBlur={(e) => handleUpdateNote({ content: e.currentTarget.innerHTML })}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -331,7 +347,7 @@ const Index = () => {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={todo.completed}
|
checked={todo.completed}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
const updatedTodos = [...currentNote.todos];
|
const updatedTodos = [...(currentNote.todos || [])];
|
||||||
updatedTodos[index].completed = !updatedTodos[index].completed;
|
updatedTodos[index].completed = !updatedTodos[index].completed;
|
||||||
handleUpdateNote({ todos: updatedTodos });
|
handleUpdateNote({ todos: updatedTodos });
|
||||||
}}
|
}}
|
||||||
@@ -345,7 +361,7 @@ const Index = () => {
|
|||||||
size="icon"
|
size="icon"
|
||||||
className="ml-auto h-6 w-6"
|
className="ml-auto h-6 w-6"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const updatedTodos = [...currentNote.todos];
|
const updatedTodos = [...(currentNote.todos || [])];
|
||||||
updatedTodos.splice(index, 1);
|
updatedTodos.splice(index, 1);
|
||||||
handleUpdateNote({ todos: updatedTodos });
|
handleUpdateNote({ todos: updatedTodos });
|
||||||
}}
|
}}
|
||||||
@@ -412,7 +428,7 @@ const Index = () => {
|
|||||||
if (image.id && currentNote.id) {
|
if (image.id && currentNote.id) {
|
||||||
const success = await NotesService.deleteImage(currentNote.id, image.id);
|
const success = await NotesService.deleteImage(currentNote.id, image.id);
|
||||||
if (success) {
|
if (success) {
|
||||||
const updatedImages = currentNote.images.filter((_, i) => i !== index);
|
const updatedImages = (currentNote.images || []).filter((_, i) => i !== index);
|
||||||
handleUpdateNote({ images: updatedImages });
|
handleUpdateNote({ images: updatedImages });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class AuthService {
|
|||||||
|
|
||||||
getHeaders(): Record<string, string> {
|
getHeaders(): Record<string, string> {
|
||||||
return {
|
return {
|
||||||
"Authorization": `Bearer ${this.token}`,
|
"Authorization": `Bearer ${this.token || ""}`,
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -51,8 +51,8 @@ class AuthService {
|
|||||||
|
|
||||||
this.token = data.token;
|
this.token = data.token;
|
||||||
this.user = data.user;
|
this.user = data.user;
|
||||||
localStorage.setItem("token", this.token);
|
localStorage.setItem("token", this.token || "");
|
||||||
localStorage.setItem("user", JSON.stringify(this.user));
|
localStorage.setItem("user", JSON.stringify(this.user || {}));
|
||||||
|
|
||||||
showSuccess("Connexion réussie");
|
showSuccess("Connexion réussie");
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -35,7 +35,14 @@ class NotesService {
|
|||||||
throw new Error("Erreur lors de la récupération des notes");
|
throw new Error("Erreur lors de la récupération des notes");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await response.json();
|
const notes = await response.json();
|
||||||
|
|
||||||
|
// Ensure todos and images are always arrays
|
||||||
|
return notes.map((note: Note) => ({
|
||||||
|
...note,
|
||||||
|
todos: note.todos || [],
|
||||||
|
images: note.images || []
|
||||||
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error instanceof Error ? error.message : "Erreur serveur");
|
showError(error instanceof Error ? error.message : "Erreur serveur");
|
||||||
return [];
|
return [];
|
||||||
@@ -55,7 +62,12 @@ class NotesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showSuccess("Note créée avec succès");
|
showSuccess("Note créée avec succès");
|
||||||
return await response.json();
|
const note = await response.json();
|
||||||
|
return {
|
||||||
|
...note,
|
||||||
|
todos: note.todos || [],
|
||||||
|
images: note.images || []
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error instanceof Error ? error.message : "Erreur serveur");
|
showError(error instanceof Error ? error.message : "Erreur serveur");
|
||||||
return null;
|
return null;
|
||||||
@@ -69,7 +81,11 @@ class NotesService {
|
|||||||
const response = await fetch(`/api/notes/${note.id}`, {
|
const response = await fetch(`/api/notes/${note.id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: AuthService.getHeaders(),
|
headers: AuthService.getHeaders(),
|
||||||
body: JSON.stringify(note)
|
body: JSON.stringify({
|
||||||
|
...note,
|
||||||
|
todos: note.todos || [],
|
||||||
|
images: note.images || []
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
Reference in new issue
Block a user