diff --git a/sigap-website/app/(pages)/(admin)/_components/navigations/nav-user.tsx b/sigap-website/app/(pages)/(admin)/_components/navigations/nav-user.tsx
index 320ac47..9b04ad8 100644
--- a/sigap-website/app/(pages)/(admin)/_components/navigations/nav-user.tsx
+++ b/sigap-website/app/(pages)/(admin)/_components/navigations/nav-user.tsx
@@ -39,9 +39,10 @@ export function NavUser({ user }: { user: IUserSchema | null }) {
const lastName = user?.profile?.last_name || "";
const userEmail = user?.email || "";
const userAvatar = user?.profile?.avatar || "";
+ const username = user?.profile?.username || "";
const getFullName = () => {
- return `${firstName} ${lastName}`.trim() || "User";
+ return `${firstName} ${lastName}`.trim() || username || "User";
};
// Generate initials for avatar fallback
@@ -58,12 +59,6 @@ export function NavUser({ user }: { user: IUserSchema | null }) {
return "U";
};
- // Handle dialog close after successful profile update
- const handleProfileUpdateSuccess = () => {
- setIsDialogOpen(false);
- // You might want to refresh the user data here
- };
-
const { handleSignOut, isPending, errors, error } = useSignOutHandler();
function LogoutButton({ handleSignOut, isPending }: { handleSignOut: () => void; isPending: boolean }) {
@@ -99,7 +94,6 @@ export function NavUser({ user }: { user: IUserSchema | null }) {
onClick={() => {
handleSignOut();
- // Tutup dialog setelah tombol Log out diklik
if (!isPending) {
setOpen(false);
}
@@ -133,13 +127,13 @@ export function NavUser({ user }: { user: IUserSchema | null }) {
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
-
+
{getInitials()}
- {getFullName()}
+ {username}
{userEmail}
@@ -154,14 +148,14 @@ export function NavUser({ user }: { user: IUserSchema | null }) {
-
+
{getInitials()}
- {getFullName()}
+ {username}
{userEmail}
diff --git a/sigap-website/app/(pages)/(admin)/_components/settings/profile-settings.tsx b/sigap-website/app/(pages)/(admin)/_components/settings/profile-settings.tsx
index 3fe9f27..5deab88 100644
--- a/sigap-website/app/(pages)/(admin)/_components/settings/profile-settings.tsx
+++ b/sigap-website/app/(pages)/(admin)/_components/settings/profile-settings.tsx
@@ -31,6 +31,8 @@ import { ScrollArea } from "@/app/_components/ui/scroll-area";
import {
updateUser,
} from "@/app/(pages)/(admin)/dashboard/user-management/action";
+import { useProfileFormHandlers } from "../../dashboard/user-management/_handlers/use-profile-form";
+import { CTexts } from "@/app/_lib/const/string";
const profileFormSchema = z.object({
username: z.string().nullable().optional(),
@@ -44,71 +46,83 @@ interface ProfileSettingsProps {
}
export function ProfileSettings({ user }: ProfileSettingsProps) {
- const [isUploading, setIsUploading] = useState(false);
- const fileInputRef = useRef
(null);
+ // const [isPending, setIsepisPending] = useState(false);
+ // const fileInputRef = useRef(null);
- // Use profile data with fallbacks
+ // // Use profile data with fallbacks
+ // const username = user?.profile?.username || "";
+ // const email = user?.email || "";
+ // const userAvatar = user?.profile?.avatar || "";
+
+ // const form = useForm({
+ // resolver: zodResolver(profileFormSchema),
+ // defaultValues: {
+ // username: username || "",
+ // avatar: userAvatar || "",
+ // },
+ // });
+
+ // const handleFileChange = async (e: React.ChangeEvent) => {
+ // const file = e.target.files?.[0];
+
+ // if (!file || !user?.id || !user?.email) return;
+
+ // try {
+ // setIsepisPending(true);
+
+ // // Upload avatar to storage
+ // // const publicUrl = await uploadAvatar(user.id, user.email, file);
+
+ // // console.log("publicUrl", publicUrl);
+
+ // // Update the form value
+ // // form.setValue("avatar", publicUrl);
+ // } catch (error) {
+ // console.error("Error uploading avatar:", error);
+ // } finally {
+ // setIsepisPending(false);
+ // }
+ // };
+
+ // const handleAvatarClick = () => {
+ // fileInputRef.current?.click();
+ // };
+
+ // async function onSubmit(data: ProfileFormValues) {
+ // try {
+ // if (!user?.id) return;
+
+ // // Update profile in database
+ // const { error } = await updateUser(user.id, {
+ // profile: {
+ // avatar: data.avatar || undefined,
+ // username: data.username || undefined,
+ // },
+ // });
+
+ // if (error) throw error;
+ // } catch (error) {
+ // console.error("Error updating profile:", error);
+ // }
+
+ const email = user?.email || "";
const username = user?.profile?.username || "";
- const userEmail = user?.email || "";
- const userAvatar = user?.profile?.avatar || "";
- const form = useForm({
- resolver: zodResolver(profileFormSchema),
- defaultValues: {
- username: username || "",
- avatar: userAvatar || "",
- },
- });
+ const {
+ form,
+ fileInputRef,
+ handleFileChange,
+ handleAvatarClick,
+ isPending,
+ onSubmit,
+ } = useProfileFormHandlers({ user });
- const handleFileChange = async (e: React.ChangeEvent) => {
- const file = e.target.files?.[0];
-
- if (!file || !user?.id || !user?.email) return;
-
- try {
- setIsUploading(true);
-
- // Upload avatar to storage
- // const publicUrl = await uploadAvatar(user.id, user.email, file);
-
- // console.log("publicUrl", publicUrl);
-
- // Update the form value
- // form.setValue("avatar", publicUrl);
- } catch (error) {
- console.error("Error uploading avatar:", error);
- } finally {
- setIsUploading(false);
- }
- };
-
- const handleAvatarClick = () => {
- fileInputRef.current?.click();
- };
-
- async function onSubmit(data: ProfileFormValues) {
- try {
- if (!user?.id) return;
-
- // Update profile in database
- const { error } = await updateUser(user.id, {
- profile: {
- avatar: data.avatar || undefined,
- username: data.username || undefined,
- },
- });
-
- if (error) throw error;
- } catch (error) {
- console.error("Error updating profile:", error);
- }
- }
return (
),
},
diff --git a/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-add-user-dialog.ts b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-add-user-dialog.tsx
similarity index 100%
rename from sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-add-user-dialog.ts
rename to sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-add-user-dialog.tsx
diff --git a/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-create-user-column.tsx b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-create-user-column.tsx
new file mode 100644
index 0000000..cabe2c2
--- /dev/null
+++ b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-create-user-column.tsx
@@ -0,0 +1,120 @@
+import { useState } from "react"
+import { useBanUserMutation, useDeleteUserMutation, useUnbanUserMutation } from "../_queries/mutations"
+import { ValidBanDuration } from "@/app/_lib/types/ban-duration"
+import { useQueryClient } from "@tanstack/react-query"
+import { toast } from "sonner"
+
+export const useCreateUserColumn = () => {
+
+ const queryClient = useQueryClient()
+
+ // Delete user state and handlers
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
+ const [userToDelete, setUserToDelete] = useState(null)
+ const { mutateAsync: deleteUser, isPending: isDeletePending } = useDeleteUserMutation()
+
+ // Ban user state and handlers
+ const [banDialogOpen, setBanDialogOpen] = useState(false)
+ const [userToBan, setUserToBan] = useState(null)
+ const { mutateAsync: banUser, isPending: isBanPending } = useBanUserMutation()
+
+ // Unban user state and handlers
+ const [unbanDialogOpen, setUnbanDialogOpen] = useState(false)
+ const [userToUnban, setUserToUnban] = useState(null)
+ const { mutateAsync: unbanUser, isPending: isUnbanPending } = useUnbanUserMutation()
+
+ const handleDeleteConfirm = async () => {
+ if (userToDelete) {
+ await deleteUser(userToDelete, {
+ onSuccess: () => {
+ if (isDeletePending) {
+ queryClient.invalidateQueries({ queryKey: ["users"] })
+
+ toast.success(`${userToDelete} has been deleted`)
+ setDeleteDialogOpen(false)
+ setUserToDelete(null)
+ }
+ },
+ onError: (error) => {
+
+ toast.error("Failed to delete user. Please try again later.")
+
+ setDeleteDialogOpen(false)
+ setUserToDelete(null)
+ }
+ })
+ }
+ }
+
+ const handleBanConfirm = async (duration: ValidBanDuration) => {
+ if (userToBan) {
+ await banUser({ id: userToBan, ban_duration: duration }, {
+ onSuccess: () => {
+ if (!isBanPending) {
+ queryClient.invalidateQueries({ queryKey: ["users"] })
+
+ toast(`${userToBan} has been banned`)
+
+ setBanDialogOpen(false)
+ setUserToBan(null)
+ }
+ },
+ onError: (error) => {
+ toast.error("Failed to ban user. Please try again later.")
+
+ setBanDialogOpen(false)
+ setUserToBan(null)
+ },
+ })
+ }
+ }
+
+ const handleUnbanConfirm = async () => {
+ if (userToUnban) {
+ await unbanUser({ id: userToUnban }, {
+ onSuccess: () => {
+ if (!isUnbanPending) {
+ queryClient.invalidateQueries({ queryKey: ["users"] })
+
+ toast(`${userToUnban} has been unbanned`)
+
+ setUnbanDialogOpen(false)
+ setUserToUnban(null)
+ }
+ },
+ onError: (error) => {
+ toast.error("Failed to unban user. Please try again later.")
+
+ setUnbanDialogOpen(false)
+ setUserToUnban(null)
+ }
+ })
+ }
+ }
+
+ return {
+ // Delete
+ deleteDialogOpen,
+ setDeleteDialogOpen,
+ userToDelete,
+ setUserToDelete,
+ handleDeleteConfirm,
+ isDeletePending,
+
+ // Ban
+ banDialogOpen,
+ setBanDialogOpen,
+ userToBan,
+ setUserToBan,
+ handleBanConfirm,
+ isBanPending,
+
+ // Unban
+ unbanDialogOpen,
+ setUnbanDialogOpen,
+ userToUnban,
+ setUserToUnban,
+ handleUnbanConfirm,
+ isUnbanPending,
+ }
+}
\ No newline at end of file
diff --git a/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-detail-sheet.ts b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-detail-sheet.tsx
similarity index 66%
rename from sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-detail-sheet.ts
rename to sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-detail-sheet.tsx
index cd8ae07..74ce225 100644
--- a/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-detail-sheet.ts
+++ b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-detail-sheet.tsx
@@ -3,7 +3,7 @@ import { toast } from "sonner";
import { useBanUserMutation, useDeleteUserMutation, useUnbanUserMutation } from "../_queries/mutations";
import { useSendMagicLinkMutation, useSendPasswordRecoveryMutation } from "@/app/(pages)/(auth)/_queries/mutations";
import { ValidBanDuration } from "@/app/_lib/types/ban-duration";
-import { handleCopyItem } from "@/app/_utils/common";
+import { copyItem } from "@/app/_utils/common";
import { useQueryClient } from "@tanstack/react-query";
export const useUserDetailSheetHandlers = ({ open, user, onUserUpdated, onOpenChange }: {
@@ -24,30 +24,54 @@ export const useUserDetailSheetHandlers = ({ open, user, onUserUpdated, onOpenCh
const handleDeleteUser = async () => {
await deleteUser(user.id, {
onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["users"] });
+ toast.success(`${user.email} has been deleted`);
+
onOpenChange(false);
}
});
};
const handleSendPasswordRecovery = async () => {
- if (!user.email) {
- toast.error("User has no email address");
- return;
- }
- await sendPasswordRecovery(user.email);
- };
+ if (user.email) {
+ await sendPasswordRecovery(user.email, {
+ onSuccess: () => {
+ toast.success(`Password recovery email sent to ${user.email}`);
+
+ onOpenChange(false);
+ },
+ onError: (error) => {
+ toast.error(error.message);
+
+ onOpenChange(false);
+ }
+ });
+ };
+ }
const handleSendMagicLink = async () => {
- if (!user.email) {
- toast.error("User has no email address");
- return;
+ if (user.email) {
+ await sendMagicLink(user.email, {
+ onSuccess: () => {
+ toast.success(`Magic link sent to ${user.email}`);
+
+ onOpenChange(false);
+ },
+ onError: (error) => {
+ toast.error(error.message);
+
+ onOpenChange(false);
+ }
+ });
}
- await sendMagicLink(user.email);
};
const handleBanUser = async (ban_duration: ValidBanDuration = "24h") => {
await banUser({ id: user.id, ban_duration: ban_duration }, {
onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["users"] });
+ toast(`${user.email} has been banned`);
+
onUserUpdated();
}
});
@@ -55,7 +79,12 @@ export const useUserDetailSheetHandlers = ({ open, user, onUserUpdated, onOpenCh
const handleUnbanUser = async () => {
await unbanUser({ id: user.id }, {
- onSuccess: onUserUpdated
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["users"] });
+ toast(`${user.email} has been unbanned`);
+
+ onUserUpdated();
+ }
});
};
@@ -81,6 +110,10 @@ export const useUserDetailSheetHandlers = ({ open, user, onUserUpdated, onOpenCh
}
};
+ const handleCopyItem = async (item: string, label: string) => {
+ if (item) copyItem(item, { label: label });
+ }
+
return {
handleDeleteUser,
handleSendPasswordRecovery,
diff --git a/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-invite-user.ts b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-invite-user.tsx
similarity index 100%
rename from sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-invite-user.ts
rename to sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-invite-user.tsx
diff --git a/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-profile-form.tsx b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-profile-form.tsx
new file mode 100644
index 0000000..8707fa0
--- /dev/null
+++ b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-profile-form.tsx
@@ -0,0 +1,195 @@
+"use client"
+
+import type React from "react"
+
+import { createClient } from "@/app/_utils/supabase/client"
+import type { IUserSchema } from "@/src/entities/models/users/users.model"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useRef, useState } from "react"
+import { useForm } from "react-hook-form"
+import { z } from "zod"
+import { useUnbanUserMutation, useUpdateUserMutation, useUploadAvatarMutation } from "../_queries/mutations"
+import { useQueryClient } from "@tanstack/react-query"
+import { toast } from "sonner"
+import { CNumbers } from "@/app/_lib/const/number"
+import { CTexts } from "@/app/_lib/const/string"
+
+// Profile update form schema
+const profileFormSchema = z.object({
+ username: z.string().nullable().optional(),
+ first_name: z.string().nullable().optional(),
+ last_name: z.string().nullable().optional(),
+ bio: z.string().nullable().optional(),
+ avatar: z.string().nullable().optional(),
+})
+
+type ProfileFormValues = z.infer
+
+interface ProfileFormProps {
+ user: IUserSchema | null
+ onSuccess?: () => void
+}
+
+export const useProfileFormHandlers = ({ user, onSuccess }: ProfileFormProps) => {
+ const queryClient = useQueryClient()
+
+ const {
+ mutateAsync: updateUser,
+ isPending,
+ error
+ } = useUpdateUserMutation()
+
+ const [avatarPreview, setAvatarPreview] = useState(user?.profile?.avatar || null)
+
+ const fileInputRef = useRef(null)
+
+ // Setup form with react-hook-form and zod validation
+ const form = useForm({
+ resolver: zodResolver(profileFormSchema),
+ defaultValues: {
+ first_name: user?.profile?.first_name || "",
+ last_name: user?.profile?.last_name || "",
+ bio: user?.profile?.bio || "",
+ avatar: user?.profile?.avatar || "",
+ },
+ })
+
+ const resetAvatarValue = () => {
+ if (fileInputRef.current) {
+ fileInputRef.current.value = ""
+ }
+ }
+
+ // Handle avatar file upload
+ const handleFileChange = async (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0]
+
+ if (!file || !user?.id) {
+
+ toast.error("No file selected")
+
+ resetAvatarValue()
+ return
+ }
+
+ // Validate file size
+ if (file.size > CNumbers.MAX_FILE_AVATAR_SIZE) {
+ toast.error(`File size must be less than ${CNumbers.MAX_FILE_AVATAR_SIZE / 1024 / 1024} MB`)
+
+ // Reset the file input
+ resetAvatarValue()
+ return
+ }
+
+ // Validate file type
+ if (!CTexts.ALLOWED_FILE_TYPES.includes(file.type)) {
+ toast.error("Invalid file type. Only PNG and JPG are allowed")
+
+ // Reset the file input
+ resetAvatarValue()
+ return
+ }
+
+ try {
+
+ // Create a preview of the selected image
+ const objectUrl = URL.createObjectURL(file)
+ setAvatarPreview(objectUrl)
+
+ // Upload to Supabase Storage
+ const fileExt = file.name.split(".").pop()
+ const fileName = `AVR-${user.email?.split("@")[0]}`
+ const filePath = `${user.id}/${fileName}`
+
+ const supabase = createClient()
+
+ const { error: uploadError, data } = await supabase.storage.from("avatars").upload(filePath, file, {
+ upsert: true,
+ contentType: file.type,
+ })
+
+ if (uploadError) {
+
+ toast.error("Error uploading avatar. Please try again.")
+
+ throw uploadError
+ }
+
+ // Get the public URL
+ const {
+ data: { publicUrl },
+ } = supabase.storage.from("avatars").getPublicUrl(filePath)
+
+ const uniquePublicUrl = `${publicUrl}?t=${Date.now()}`
+
+ await updateUser({ id: user.id, data: { profile: { avatar: uniquePublicUrl } } })
+
+ form.setValue("avatar", uniquePublicUrl)
+ resetAvatarValue()
+
+ queryClient.invalidateQueries({ queryKey: ["users"] })
+ queryClient.invalidateQueries({ queryKey: ["user", "current"] })
+ toast.success("Avatar uploaded successfully")
+ } catch (error) {
+ console.error("Error uploading avatar:", error)
+
+ // Show error toast
+ toast.error("Error uploading avatar. Please try again.")
+
+ // Revert to previous avatar if upload fails
+ setAvatarPreview(user?.profile?.avatar || null)
+
+ // Reset the file input
+ resetAvatarValue()
+ }
+ }
+
+ // Trigger file input click
+ const handleAvatarClick = () => {
+ fileInputRef.current?.click()
+ }
+
+ // Handle form submission
+ async function onSubmit(data: ProfileFormValues) {
+ try {
+ if (!user?.id) return
+
+ const supabase = createClient()
+
+ // Update profile in database
+ const { error } = await supabase
+ .from("profiles")
+ .update({
+ first_name: data.first_name,
+ last_name: data.last_name,
+ bio: data.bio,
+ avatar: data.avatar,
+ })
+ .eq("user_id", user.id)
+
+ if (error) throw error
+
+ toast.success("Profile updated successfully")
+
+ // Invalidate the user query to refresh data
+ queryClient.invalidateQueries({ queryKey: ["user", "current", user.id] })
+
+ // Call success callback
+ onSuccess?.()
+ } catch (error) {
+ console.error("Error updating profile:", error)
+ toast.error("Error updating profile")
+ }
+ }
+
+ return {
+ form,
+ handleFileChange,
+ handleAvatarClick,
+ onSubmit,
+ isPending,
+ avatarPreview,
+ fileInputRef,
+ }
+}
+
diff --git a/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-profile-sheet.ts b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-profile-sheet.tsx
similarity index 89%
rename from sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-profile-sheet.ts
rename to sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-profile-sheet.tsx
index 26a72ab..d533df6 100644
--- a/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-profile-sheet.ts
+++ b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-profile-sheet.tsx
@@ -3,6 +3,8 @@ import { IUserSchema } from "@/src/entities/models/users/users.model";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { useUpdateUserMutation } from "../_queries/mutations";
+import { useQueryClient } from "@tanstack/react-query";
+import { toast } from "sonner";
export const useUserProfileSheetHandlers = ({ open, onOpenChange, userData, onUserUpdated }: {
open: boolean;
@@ -11,6 +13,8 @@ export const useUserProfileSheetHandlers = ({ open, onOpenChange, userData, onUs
onUserUpdated: () => void;
}) => {
+ const queryClient = useQueryClient()
+
const {
mutateAsync: updateUser,
isPending,
@@ -54,10 +58,18 @@ export const useUserProfileSheetHandlers = ({ open, onOpenChange, userData, onUs
const handleUpdateUser = async () => {
await updateUser({ id: userData.id, data: form.getValues() }, {
onSuccess: () => {
+
+ queryClient.invalidateQueries({ queryKey: ["users"] })
+
+ toast.success("User updated successfully")
+
onUserUpdated();
onOpenChange(false);
},
onError: () => {
+
+ toast.error("Failed to update user")
+
onOpenChange(false);
},
});
diff --git a/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-user-management.ts b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-user-management.tsx
similarity index 100%
rename from sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-user-management.ts
rename to sigap-website/app/(pages)/(admin)/dashboard/user-management/_handlers/use-user-management.tsx
diff --git a/sigap-website/app/(pages)/(admin)/dashboard/user-management/_queries/mutations.ts b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_queries/mutations.ts
index 7514d7a..6d75f96 100644
--- a/sigap-website/app/(pages)/(admin)/dashboard/user-management/_queries/mutations.ts
+++ b/sigap-website/app/(pages)/(admin)/dashboard/user-management/_queries/mutations.ts
@@ -1,6 +1,6 @@
import { ICreateUserSchema } from "@/src/entities/models/users/create-user.model";
import { useMutation } from "@tanstack/react-query";
-import { banUser, createUser, deleteUser, inviteUser, unbanUser, updateUser } from "../action";
+import { banUser, createUser, deleteUser, inviteUser, unbanUser, updateUser, uploadAvatar } from "../action";
import { IUpdateUserSchema } from "@/src/entities/models/users/update-user.model";
import { ICredentialsInviteUserSchema } from "@/src/entities/models/users/invite-user.model";
import { IBanUserSchema, ICredentialsBanUserSchema } from "@/src/entities/models/users/ban-user.model";
@@ -49,3 +49,10 @@ export const useUnbanUserMutation = () => {
mutationFn: (credential: ICredentialsUnbanUserSchema) => unbanUser(credential),
})
}
+
+export const useUploadAvatarMutation = () => {
+ return useMutation({
+ mutationKey: ["user", "upload-avatar"],
+ mutationFn: (args: { userId: string; file: File }) => uploadAvatar(args.userId, args.file),
+ })
+}
\ No newline at end of file
diff --git a/sigap-website/app/(pages)/(admin)/dashboard/user-management/action.ts b/sigap-website/app/(pages)/(admin)/dashboard/user-management/action.ts
index be05ac3..3fa5660 100644
--- a/sigap-website/app/(pages)/(admin)/dashboard/user-management/action.ts
+++ b/sigap-website/app/(pages)/(admin)/dashboard/user-management/action.ts
@@ -498,3 +498,52 @@ export async function deleteUser(id: string) {
}
);
}
+
+
+export async function uploadAvatar(id: string, file: File) {
+ const instrumentationService = getInjection('IInstrumentationService');
+ return await instrumentationService.instrumentServerAction(
+ 'uploadAvatar',
+ { recordResponse: true },
+ async () => {
+ try {
+ const uploadAvatarController = getInjection('IUploadAvatarController');
+ const newAvatar = await uploadAvatarController(id, file);
+
+ return { success: true, newAvatar };
+
+ } catch (err) {
+ if (err instanceof InputParseError) {
+ // return {
+ // error: err.message,
+ // };
+
+ throw new InputParseError(err.message);
+ }
+
+ if (err instanceof UnauthenticatedError) {
+ // return {
+ // error: 'Must be logged in to create a user.',
+ // };
+ throw new UnauthenticatedError('Must be logged in to upload an avatar.');
+ }
+
+ if (err instanceof AuthenticationError) {
+ // return {
+ // error: 'User not found.',
+ // };
+
+ throw new AuthenticationError('There was an error with the credentials. Please try again or contact support.');
+ }
+
+ const crashReporterService = getInjection('ICrashReporterService');
+ crashReporterService.report(err);
+ // return {
+ // error:
+ // 'An error happened. The developers have been notified. Please try again later.',
+ // };
+ throw new Error('An error happened. The developers have been notified. Please try again later.');
+ }
+ }
+ );
+}
diff --git a/sigap-website/app/(pages)/layout.tsx b/sigap-website/app/(pages)/layout.tsx
index 93a0fef..e11864a 100644
--- a/sigap-website/app/(pages)/layout.tsx
+++ b/sigap-website/app/(pages)/layout.tsx
@@ -54,7 +54,7 @@ export default function RootLayout({
*/}
{children}
-
+
{/*