refactor(auth, layouts): enhance UI components for improved aesthetics and usability

- Updated SignIn and Profile components with new background styles and layout adjustments for a more modern look.
- Refined button styles in GoogleSigninButton and SigninWithPassword for better user interaction feedback.
- Enhanced InputGroup and Select components with improved styling and error handling for a more consistent user experience.
- Adjusted Sidebar and Header components for better responsiveness and visual appeal.
- Implemented spotlight effect in UserInfo dropdown for a more engaging user interface.
This commit is contained in:
AmirReza Jamali
2026-04-23 20:53:23 +03:30
parent bb1af8b31c
commit f195bba03d
50 changed files with 1245 additions and 319 deletions
+26 -15
View File
@@ -1,6 +1,7 @@
"use client";
import InputGroup from "@/components/FormElements/InputGroup";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import FileImageUploadCard from "@/components/ui/FileImageUploadCard";
import FormFooter from "@/components/ui/FormFooter";
import { REGEX } from "@/constants/regex";
import { FormErrorMessages } from "@/constants/Texts";
@@ -13,7 +14,19 @@ interface IProps {
id?: string;
}
const CreateAdminForm: FC<IProps> = ({ defaultValues, editMode, id }) => {
const { errors, handleSubmit, register, onSubmit } = useCreateAdminPresenter({
const {
errors,
handleSubmit,
register,
onSubmit,
isUploadingImage,
uploadProgress,
selectedFileName,
selectedFileSize,
previewUrl,
clearSelectedProfileImage,
profileImageRegister,
} = useCreateAdminPresenter({
editMode,
defaultValues,
id,
@@ -221,20 +234,18 @@ const CreateAdminForm: FC<IProps> = ({ defaultValues, editMode, id }) => {
)}
</div>
<div>
<InputGroup
type="file"
fileStyleVariant="style1"
label="Attach file"
register={register}
name="profile_image"
/>
{errors.profile_image && (
<p className="mt-1 text-sm text-red-500">
{errors.profile_image.message as string}
</p>
)}
</div>
<FileImageUploadCard
label="Profile image"
inputId="profile-image-upload"
fileRegister={profileImageRegister}
isLoading={isUploadingImage}
uploadProgress={uploadProgress}
previewUrl={previewUrl}
selectedFileName={selectedFileName}
selectedFileSize={selectedFileSize}
onClear={clearSelectedProfileImage}
errorMessage={errors.profile_image?.message as string | undefined}
/>
<div className="md:col-span-2">
<FormFooter />
@@ -1,37 +1,171 @@
import { useCreateAdmin, useUpdateAdmin } from "@/hooks/queries";
import { ICreateAdminCredentials } from "@/lib/api";
import { ICreateAdminCredentials, userService } from "@/lib/api";
import { validateImageFile } from "@/utils/shared";
import { useIsMutating } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { ChangeEvent, useEffect, useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form";
import { toast } from "react-toastify";
interface IProps {
editMode?: boolean;
defaultValues?: ICreateAdminCredentials;
id?: string;
}
type CreateAdminFormValues = Omit<ICreateAdminCredentials, "profile_image"> & {
profile_image?: FileList;
};
const useCreateAdminPresenter = ({
editMode = false,
defaultValues,
id,
}: IProps) => {
const isMutating = useIsMutating();
const [selectedFileName, setSelectedFileName] = useState<string>("");
const [selectedFileSize, setSelectedFileSize] = useState<number | null>(null);
const [isUploadingImage, setIsUploadingImage] = useState(false);
const [uploadProgress, setUploadProgress] = useState<number>(0);
const [uploadedProfileImageUrl, setUploadedProfileImageUrl] = useState<
string | undefined
>(defaultValues?.profile_image);
const [previewUrl, setPreviewUrl] = useState<string | null>(
defaultValues?.profile_image ?? null,
);
const {
register,
formState: { errors },
handleSubmit,
watch,
} = useForm<ICreateAdminCredentials>({
setError,
clearErrors,
setValue,
} = useForm<CreateAdminFormValues>({
mode: "onChange",
defaultValues,
defaultValues: {
...defaultValues,
profile_image: undefined,
},
});
const { mutate: createAdmin } = useCreateAdmin();
const { mutate: updateAdmin } = useUpdateAdmin(id as string);
const onSubmit: SubmitHandler<ICreateAdminCredentials> = (data) => {
const resetToDefaultProfileImage = () => {
setSelectedFileName("");
setSelectedFileSize(null);
setUploadProgress(0);
setUploadedProfileImageUrl(defaultValues?.profile_image);
setPreviewUrl(defaultValues?.profile_image ?? null);
};
const validateProfileImage = (file: File): string | null => {
return validateImageFile({ file });
};
const prepareUploadState = (file: File) => {
clearErrors("profile_image");
setSelectedFileName(file.name);
setSelectedFileSize(file.size);
setIsUploadingImage(true);
setUploadProgress(0);
};
const handleUploadSuccess = (downloadUrl: string) => {
setUploadedProfileImageUrl(downloadUrl);
setPreviewUrl(downloadUrl);
setUploadProgress(100);
};
const handleUploadFailure = (input: HTMLInputElement) => {
setUploadedProfileImageUrl(undefined);
resetToDefaultProfileImage();
setValue("profile_image", undefined);
setError("profile_image", {
type: "validate",
message: "Failed to upload profile image",
});
input.value = "";
};
const uploadProfileImage = async (file: File) => {
return userService.uploadProfileImage({
file,
onProgress: (percent) => {
setUploadProgress(percent);
},
});
};
const onProfileImageChange = async (event: ChangeEvent<HTMLInputElement>) => {
const selectedFile = event.target.files?.[0];
const inputElement = event.target;
if (!selectedFile) {
resetToDefaultProfileImage();
clearErrors("profile_image");
return;
}
const validationError = validateProfileImage(selectedFile);
if (validationError) {
setError("profile_image", {
type: "validate",
message: validationError,
});
inputElement.value = "";
return;
}
prepareUploadState(selectedFile);
try {
const downloadUrl = await uploadProfileImage(selectedFile);
handleUploadSuccess(downloadUrl);
} catch {
handleUploadFailure(inputElement);
} finally {
setIsUploadingImage(false);
}
};
const clearSelectedProfileImage = () => {
setValue("profile_image", undefined);
resetToDefaultProfileImage();
clearErrors("profile_image");
};
useEffect(() => {
return () => {
if (previewUrl?.startsWith("blob:")) {
URL.revokeObjectURL(previewUrl);
}
};
}, [previewUrl]);
const profileImageRegister = register("profile_image", {
onChange: onProfileImageChange,
});
const onSubmit: SubmitHandler<CreateAdminFormValues> = async (data) => {
const file = data.profile_image?.[0];
const profileImageUrl = uploadedProfileImageUrl ?? defaultValues?.profile_image;
if (file && !profileImageUrl) {
toast.error("Please wait for profile image upload to finish");
return;
}
const payload: ICreateAdminCredentials = {
...data,
profile_image: profileImageUrl,
};
if (editMode) {
updateAdmin({ id: id as string, credentials: data });
updateAdmin({ id: id as string, credentials: payload });
} else {
createAdmin({ ...data, role: "admin" });
createAdmin({ ...payload, role: "admin" });
}
};
const router = useRouter();
@@ -43,6 +177,13 @@ const useCreateAdminPresenter = ({
isMutating,
onSubmit,
watchForm: watch,
isUploadingImage,
uploadProgress,
selectedFileName,
selectedFileSize,
previewUrl,
clearSelectedProfileImage,
profileImageRegister,
};
};