diff --git a/prisma/migrations/20241107061836_add_user_banner/migration.sql b/prisma/migrations/20241107061836_add_user_banner/migration.sql new file mode 100644 index 00000000..62336b67 --- /dev/null +++ b/prisma/migrations/20241107061836_add_user_banner/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "banner" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1cc0363b..1e1998fa 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,6 +13,7 @@ model User { password String? avatar String? + banner String? isVerified Boolean @default(false) role Role @default(USER) jobs Job[] diff --git a/src/actions/user.profile.actions.ts b/src/actions/user.profile.actions.ts index e4e1219b..d5a1697b 100644 --- a/src/actions/user.profile.actions.ts +++ b/src/actions/user.profile.actions.ts @@ -378,6 +378,7 @@ export const getUserDetailsWithId = async (id: string) => { contactEmail: true, resume: true, avatar: true, + banner: true, aboutMe: true, project: true, resumeUpdateDate: true, @@ -422,6 +423,7 @@ export const updateUserDetails = withSession< contactEmail: data.contactEmail, aboutMe: data.aboutMe, avatar: data.avatar, + banner: data.banner, discordLink: data.discordLink, linkedinLink: data.linkedinLink, twitterLink: data.twitterLink, diff --git a/src/components/profile/ProfileHeroSection.tsx b/src/components/profile/ProfileHeroSection.tsx index 71e7b6c4..9aa871f7 100644 --- a/src/components/profile/ProfileHeroSection.tsx +++ b/src/components/profile/ProfileHeroSection.tsx @@ -11,6 +11,7 @@ import { useSession } from 'next-auth/react'; import { UserType } from '@/types/user.types'; import ProfileSocials from './ProfileSocials'; import { ProfileShareDialog } from './ProfileShare'; +import Image from 'next/image'; const ProfileHeroSection = ({ userdetails }: { userdetails: UserType }) => { const [isSheetOpen, setIsSheetOpen] = useState(false); @@ -30,7 +31,18 @@ const ProfileHeroSection = ({ userdetails }: { userdetails: UserType }) => { return ( <>
-
+ {userdetails.banner ? ( +
+ banner-img +
+ ) : ( +
+ )}
{userdetails.avatar && ( diff --git a/src/components/profile/forms/EditProfileForm.tsx b/src/components/profile/forms/EditProfileForm.tsx index ae0380f5..97e3f835 100644 --- a/src/components/profile/forms/EditProfileForm.tsx +++ b/src/components/profile/forms/EditProfileForm.tsx @@ -2,7 +2,6 @@ import React, { useRef, useState } from 'react'; import { Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, @@ -19,7 +18,6 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { uploadFileAction } from '@/actions/upload-to-cdn'; import { X } from 'lucide-react'; import { Input } from '@/components/ui/input'; -import { Textarea } from '@/components/ui/textarea'; import { Button } from '@/components/ui/button'; import { UserType } from '@/types/user.types'; import { updateUserDetails } from '@/actions/user.profile.actions'; @@ -33,18 +31,22 @@ const EditProfileForm = ({ }) => { const { toast } = useToast(); - const [file, setFile] = useState(null); - const [previewImg, setPreviewImg] = useState( + const [avatarFile, setAvatarFile] = useState(null); + const [bannerFile, setBannerFile] = useState(null); + const [previewAvatarImg, setPreviewAvatarImg] = useState( userdetails.avatar || null ); + const [previewBannerImg, setPreviewBannerImg] = useState( + userdetails.banner || null + ); const form = useForm({ resolver: zodResolver(profileSchema), defaultValues: { - aboutMe: userdetails.aboutMe || '', email: userdetails.email || '', contactEmail: userdetails.contactEmail || '', avatar: userdetails.avatar || '', + banner: userdetails.banner || '', name: userdetails.name || '', discordLink: userdetails.discordLink || '', linkedinLink: userdetails.linkedinLink || '', @@ -78,8 +80,11 @@ const EditProfileForm = ({ const onSubmit = async (data: ProfileSchemaType) => { try { - if (file) { - data.avatar = (await submitImage(file)) ?? '/main.svg'; + if (avatarFile) { + data.avatar = (await submitImage(avatarFile)) ?? '/main.svg'; + } + if (bannerFile) { + data.banner = (await submitImage(bannerFile)) ?? ''; } const response = await updateUserDetails(data); @@ -108,27 +113,50 @@ const EditProfileForm = ({ handleClose(); }; const profileImageRef = useRef(null); + const bannerImageRef = useRef(null); + + const handleClick = (inputType: string) => { + if (!inputType) return; + if ( + (inputType === 'avatarFileInput' && previewAvatarImg) || + (inputType === 'bannerFileInput' && previewBannerImg) + ) + return; - const handleClick = () => { - if (previewImg) return; - const fileInput = document.getElementById('fileInput') as HTMLInputElement; + const fileInput = document.getElementById( + `${inputType}` + ) as HTMLInputElement; if (fileInput) { fileInput.click(); } }; - const clearLogoImage = () => { - const fileInput = document.getElementById('fileInput') as HTMLInputElement; + const clearLogoImage = (inputType: string) => { + if (!inputType) return; + const fileInput = document.getElementById( + `${inputType}` + ) as HTMLInputElement; if (fileInput) { fileInput.value = ''; } - setPreviewImg(null); - setFile(null); - form.setValue('avatar', ''); + if (inputType === 'avatarFileInput') { + setPreviewAvatarImg(null); + setAvatarFile(null); + form.setValue('avatar', ''); + } else if (inputType === 'bannerFileInput') { + setPreviewBannerImg(null); + setBannerFile(null); + form.setValue('banner', ''); + } }; - const handleFileChange = async (e: React.ChangeEvent) => { + + const handleFileChange = async ( + e: React.ChangeEvent, + inputType: string + ) => { + if (!inputType) return; const selectedFile = e.target.files ? e.target.files[0] : null; if (!selectedFile) { return; @@ -143,14 +171,22 @@ const EditProfileForm = ({ } const reader = new FileReader(); reader.onload = () => { - if (profileImageRef.current) { - profileImageRef.current.src = reader.result as string; + if (inputType === 'bannerFileInput') { + if (bannerImageRef.current) { + bannerImageRef.current.src = reader.result as string; + } + setPreviewBannerImg(reader.result as string); + } else { + if (profileImageRef.current) { + profileImageRef.current.src = reader.result as string; + } + setPreviewAvatarImg(reader.result as string); } - setPreviewImg(reader.result as string); }; reader.readAsDataURL(selectedFile); if (selectedFile) { - setFile(selectedFile); + if (inputType === 'bannerFileInput') setBannerFile(selectedFile); + else setAvatarFile(selectedFile); } }; @@ -161,32 +197,84 @@ const EditProfileForm = ({ className="space-y-8 h-full flex flex-col justify-between" >
+ Banner Picture +
+
handleClick('bannerFileInput')} + > + {previewBannerImg ? ( + Banner Logo + ) : ( +
+ +
+ Upload banner image +
+
+ )} + {previewBannerImg && ( + + )} +
+ + handleFileChange(event, 'bannerFileInput')} + /> +
+ Profile Picture
handleClick('avatarFileInput')} > - {previewImg ? ( + {previewAvatarImg ? ( Company Logo ) : ( - +
+ +
+ Upload profile image +
+
)} - {previewImg && ( + {previewAvatarImg && (
handleFileChange(event, 'avatarFileInput')} />
@@ -332,25 +420,6 @@ const EditProfileForm = ({ )} /> - ( - - About Me - -