From b723479ec83aa3ba9feb4b383a449dc775ee94b4 Mon Sep 17 00:00:00 2001
From: cdricms <36056008+cdricms@users.noreply.github.com>
Date: Tue, 28 Jan 2025 13:29:33 +0100
Subject: [PATCH 1/4] Handling CRUD for members in frontend
---
backend/api/get_users.go | 7 +-
.../app/(auth)/dashboard/members/page.tsx | 10 +
frontend/components/app-sidebar.tsx | 4 +
frontend/components/member-dialog.tsx | 152 ++++++++++
frontend/components/members-table.tsx | 277 +++++++-----------
5 files changed, 280 insertions(+), 170 deletions(-)
create mode 100644 frontend/app/(auth)/dashboard/members/page.tsx
create mode 100644 frontend/components/member-dialog.tsx
diff --git a/backend/api/get_users.go b/backend/api/get_users.go
index 707807e..f577cba 100644
--- a/backend/api/get_users.go
+++ b/backend/api/get_users.go
@@ -20,9 +20,9 @@ func HandleGetUsers(w http.ResponseWriter, r *http.Request) {
})
if count == 0 {
- core.JSONError{
- Status: core.Error,
- Message: "Not users.",
+ core.JSONSuccess{
+ Status: core.Success,
+ Message: "No users.",
}.Respond(w, http.StatusNotFound)
return
}
@@ -35,7 +35,6 @@ func HandleGetUsers(w http.ResponseWriter, r *http.Request) {
return
}
- // TODO : Remove password
core.JSONSuccess{
Status: core.Success,
Message: "Users found.",
diff --git a/frontend/app/(auth)/dashboard/members/page.tsx b/frontend/app/(auth)/dashboard/members/page.tsx
new file mode 100644
index 0000000..771ed9d
--- /dev/null
+++ b/frontend/app/(auth)/dashboard/members/page.tsx
@@ -0,0 +1,10 @@
+"use server";
+import MembersTable from "@/components/members-table";
+
+export default async function Page({}) {
+ return (
+
+
+
+ );
+}
diff --git a/frontend/components/app-sidebar.tsx b/frontend/components/app-sidebar.tsx
index dd6266a..3efcdfb 100644
--- a/frontend/components/app-sidebar.tsx
+++ b/frontend/components/app-sidebar.tsx
@@ -54,6 +54,10 @@ const data = {
icon: Users,
isActive: true,
items: [
+ {
+ title: "Liste des membres",
+ url: "/dashboard/members",
+ },
{
title: "Création d'un membre",
url: "/dashboard/members/new",
diff --git a/frontend/components/member-dialog.tsx b/frontend/components/member-dialog.tsx
new file mode 100644
index 0000000..9539c55
--- /dev/null
+++ b/frontend/components/member-dialog.tsx
@@ -0,0 +1,152 @@
+import { useEffect } from "react";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import * as z from "zod";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogFooter,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+
+const memberSchema = z.object({
+ userId: z.string().optional(),
+ firstname: z.string().min(1, "Prénom est requis."),
+ lastname: z.string().min(1, "Nom de famille est requis."),
+ email: z.string().email("Adresse email invalide."),
+ password: z
+ .string()
+ .min(6, "Le mot de passe doit avoir au moins 6 caractères.")
+ .optional(),
+ phone: z.string().regex(/^\d{10}$/, "Le numéro doit avoir 10 chiffres."),
+ role: z.string().min(1, "Le rôle est requis."),
+});
+
+const updateMemberSchema = memberSchema.partial();
+
+export type Member = z.infer;
+
+interface MemberDialogProps {
+ isOpen: boolean;
+ onClose: () => void;
+ member: Member | null;
+ onSave: (member: Member) => void;
+}
+
+export default function MemberDialog({
+ isOpen,
+ onClose,
+ member,
+ onSave,
+}: MemberDialogProps) {
+ const schema = member?.userId ? updateMemberSchema : memberSchema;
+ const form = useForm({
+ resolver: zodResolver(schema),
+ defaultValues: member?.userId
+ ? member
+ : {
+ userId: "",
+ firstname: "",
+ lastname: "",
+ email: "",
+ password: "",
+ phone: "",
+ role: "",
+ },
+ });
+
+ useEffect(() => {
+ if (member) {
+ form.reset(member);
+ } else {
+ form.reset({
+ userId: "",
+ firstname: "",
+ lastname: "",
+ email: "",
+ password: "",
+ phone: "",
+ role: "",
+ });
+ }
+ }, [member, form]);
+
+ const onSubmit = (data: Member) => {
+ onSave(data);
+ onClose();
+ };
+
+ return (
+
+
+
+
+ {member
+ ? "Mis à jour du membre"
+ : "Créer un nouveau membre"}
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/components/members-table.tsx b/frontend/components/members-table.tsx
index 399ec59..01d334b 100644
--- a/frontend/components/members-table.tsx
+++ b/frontend/components/members-table.tsx
@@ -12,93 +12,28 @@ import {
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { ScrollArea } from "@/components/ui/scroll-area";
-import { UpdateMemberDialog } from "./UpdateMemberDialog";
-import { AddMemberDialog } from "./AddMemberDialog";
+import MemberDialog, { Member } from "./member-dialog";
+import * as z from "zod";
+import { request, useApi } from "@/hooks/use-api";
+import {
+ CircleX,
+ Loader2,
+ Trash2,
+ UserRoundPen,
+ UserRoundPlus,
+} from "lucide-react";
-interface Member {
- user_id: string;
- firstname: string;
- lastname: string;
- email: string;
- password: string;
- phone: string;
- role: string;
-}
-
-const initialMembers: Member[] = [
- // Add some sample data here
- {
- user_id: "1",
- firstname: "John",
- lastname: "Doe",
- email: "john@example.com",
- password: "********",
- phone: "1234567890",
- role: "User",
- },
- {
- user_id: "1",
- firstname: "John",
- lastname: "Doe",
- email: "john@example.com",
- password: "********",
- phone: "1234567890",
- role: "User",
- },
- {
- user_id: "1",
- firstname: "John",
- lastname: "Doe",
- email: "john@example.com",
- password: "********",
- phone: "1234567890",
- role: "User",
- },
- {
- user_id: "1",
- firstname: "John",
- lastname: "Doe",
- email: "john@example.com",
- password: "********",
- phone: "1234567890",
- role: "User",
- },
- {
- user_id: "1",
- firstname: "John",
- lastname: "Doe",
- email: "john@example.com",
- password: "********",
- phone: "1234567890",
- role: "User",
- },
- {
- user_id: "1",
- firstname: "John",
- lastname: "Doe",
- email: "john@example.com",
- password: "********",
- phone: "1234567890",
- role: "User",
- },
- {
- user_id: "1",
- firstname: "John",
- lastname: "Doe",
- email: "john@example.com",
- password: "********",
- phone: "1234567890",
- role: "User",
- },
- // Add more sample members...
-];
-
-export function MembersTable() {
- const [members, setMembers] = useState(initialMembers);
+export default function MembersTable() {
+ const {
+ data: members,
+ error,
+ mutate,
+ success,
+ isLoading,
+ } = useApi("/users", undefined, true, false);
const [selectMode, setSelectMode] = useState(false);
const [selectedMembers, setSelectedMembers] = useState([]);
- const [updateDialogOpen, setUpdateDialogOpen] = useState(false);
- const [addDialogOpen, setAddDialogOpen] = useState(false);
+ const [dialogOpen, setDialogOpen] = useState(false);
const [currentMember, setCurrentMember] = useState(null);
const toggleSelectMode = () => {
@@ -114,124 +49,134 @@ export function MembersTable() {
);
};
- const handleUpdate = (member: Member) => {
+ const handleOpenDialog = (member: Member | null) => {
setCurrentMember(member);
- setUpdateDialogOpen(true);
+ setDialogOpen(true);
};
- const handleDelete = (userId: string) => {
- setMembers((prev) =>
- prev.filter((member) => member.user_id !== userId),
- );
+ const handleSaveMember = async (savedMember: Member) => {
+ if (savedMember.userId) {
+ const res = await request(
+ `/users/${savedMember.userId}/update`,
+ {
+ body: savedMember,
+ requiresAuth: true,
+ method: "PATCH",
+ },
+ );
+ if (res.status === "Success") mutate();
+ // Update existing member
+ // setMembers((prev) =>
+ // prev.map((m) =>
+ // m.user_id === savedMember.user_id ? savedMember : m,
+ // ),
+ // );
+ } else {
+ const res = await request("/users/new", {
+ body: savedMember,
+ method: "POST",
+ requiresAuth: true,
+ });
+ if (res.status === "Success") mutate();
+ }
};
- const handleAdd = (newMember: Member) => {
- setMembers((prev) => [
- ...prev,
- { ...newMember, user_id: String(prev.length + 1) },
- ]);
+ const handleDelete = async (userId: string) => {
+ const res = await request(`/users/${userId}/delete`, {
+ method: "DELETE",
+ requiresAuth: true,
+ });
+ if (res.status === "Success") mutate();
};
return (
- {selectMode ? "Cancel Selection" : "Select"}
+ {selectMode ? : "Selectionner"}
- setAddDialogOpen(true)}>
- Add New Member
+ handleOpenDialog(null)}>
+
-
+
{selectMode && (
- Select
+ Selectionner
)}
- User ID
- First Name
- Last Name
+ Prénom
+ Nom
Email
- Password
- Phone
- Role
+ Téléphone
+ Rôle
Actions
- {members.map((member) => (
-
- {selectMode && (
+ {isLoading && }
+ {members &&
+ members.map((member) => (
+
+ {selectMode && (
+
+
+ toggleMemberSelection(
+ member.userId!,
+ )
+ }
+ />
+
+ )}
-
- toggleMemberSelection(
- member.user_id,
- )
- }
- />
+ {member.firstname}
- )}
- {member.user_id}
- {member.firstname}
- {member.lastname}
- {member.email}
- {member.password}
- {member.phone}
- {member.role}
-
- handleUpdate(member)}
- >
- Modify
-
-
- handleDelete(member.user_id)
- }
- >
- Delete
-
-
-
- ))}
+ {member.lastname}
+ {member.email}
+ {member.phone}
+ {member.role}
+
+
+ handleOpenDialog(member)
+ }
+ >
+
+
+
+ handleDelete(member.userId!)
+ }
+ >
+
+
+
+
+ ))}
-
setUpdateDialogOpen(false)}
+ setDialogOpen(false)}
member={currentMember}
- onUpdate={(updatedMember) => {
- setMembers((prev) =>
- prev.map((m) =>
- m.user_id === updatedMember.user_id
- ? updatedMember
- : m,
- ),
- );
- setUpdateDialogOpen(false);
- }}
- />
- setAddDialogOpen(false)}
- onAdd={handleAdd}
+ onSave={handleSaveMember}
/>
);
From 74118a5aa6e07721d66113818a291aeb4fa84568 Mon Sep 17 00:00:00 2001
From: cdricms <36056008+cdricms@users.noreply.github.com>
Date: Tue, 28 Jan 2025 13:55:19 +0100
Subject: [PATCH 2/4] Changed form in users list and removed
dashboard/users/new
---
.../app/(auth)/dashboard/members/new/page.tsx | 233 ------------------
frontend/components/app-sidebar.tsx | 4 -
frontend/components/member-dialog.tsx | 180 ++++++++++++--
frontend/components/members-table.tsx | 1 +
4 files changed, 155 insertions(+), 263 deletions(-)
delete mode 100644 frontend/app/(auth)/dashboard/members/new/page.tsx
diff --git a/frontend/app/(auth)/dashboard/members/new/page.tsx b/frontend/app/(auth)/dashboard/members/new/page.tsx
deleted file mode 100644
index 33cc8b8..0000000
--- a/frontend/app/(auth)/dashboard/members/new/page.tsx
+++ /dev/null
@@ -1,233 +0,0 @@
-"use client";
-
-import { z } from "zod";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useForm } from "react-hook-form";
-
-import {
- Form,
- FormControl,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import { Input } from "@/components/ui/input";
-import useApiMutation from "@/hooks/use-api";
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectLabel,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
-
-const formSchema = z.object({
- firstname: z.string().min(2, { message: "Prénom trop court." }),
- lastname: z.string().min(2, { message: "Nom trop court." }),
- role: z.enum(["admin", "user"], { message: "Rôle invalide." }),
- phone: z
- .string()
- .max(10, { message: "Un numéro de téléphone à 10 chiffres." })
- .optional(),
- email: z.string().email({ message: "Email invalide." })
-});
-
-export default function CreateMemberForm() {
- const {
- trigger
- } = useApiMutation(
- "/users/new",
- { onSuccess: () => console.log("Member created") },
- "POST",
- true,
- false
- )
-
- const form = useForm>({
- resolver: zodResolver(formSchema),
- defaultValues: {
- firstname: "",
- lastname: "",
- role: "user",
- phone: "",
- email: "",
- },
- });
-
- async function onSubmit(values: z.infer) {
- try {
- const res = await trigger(values)
- if (!res) throw new Error("The server hasn't responded.");
- if (res.status === "Error") throw new Error(res.message);
- } catch (error) {
- console.error("Form submission error", error);
- }
- console.log("submited");
- }
-
- return (
-
-
-
- Création de membre
-
- Remplissez les différents champs pour créer un membre.
- Un code sera envoyé par mail à l'utilisateur.
- Il pourra ensuite modifier le mot de passe de son compte avec ce code.
-
-
-
-
-
-
-
-
- );
-}
diff --git a/frontend/components/app-sidebar.tsx b/frontend/components/app-sidebar.tsx
index 3efcdfb..06db639 100644
--- a/frontend/components/app-sidebar.tsx
+++ b/frontend/components/app-sidebar.tsx
@@ -58,10 +58,6 @@ const data = {
title: "Liste des membres",
url: "/dashboard/members",
},
- {
- title: "Création d'un membre",
- url: "/dashboard/members/new",
- },
],
},
{
diff --git a/frontend/components/member-dialog.tsx b/frontend/components/member-dialog.tsx
index 9539c55..730fb4c 100644
--- a/frontend/components/member-dialog.tsx
+++ b/frontend/components/member-dialog.tsx
@@ -11,7 +11,6 @@ import {
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
import {
Form,
FormControl,
@@ -20,6 +19,15 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "./ui/select";
const memberSchema = z.object({
userId: z.string().optional(),
@@ -94,51 +102,171 @@ export default function MemberDialog({
{member
- ? "Mis à jour du membre"
+ ? "Mise à jour du membre"
: "Créer un nouveau membre"}