diff --git a/src/components/delete-warning/index.jsx b/src/components/delete-warning/index.jsx new file mode 100644 index 0000000..6e2635e --- /dev/null +++ b/src/components/delete-warning/index.jsx @@ -0,0 +1,46 @@ +import { useState } from "react"; + +import { deleteDocData } from "@/lib/firebase/firestoreFunctions"; + +import Button from "../button/Button"; + +export default function DeleteWarning({ + setDeleteWarningItem, + deleteWarningItem, + setItems, + items, +}) { + const [isLoading, setIsLoading] = useState(false); + const handleDelete = async () => { + setIsLoading(true); + await deleteDocData("items", deleteWarningItem); + setItems(items.filter((item) => item.id !== deleteWarningItem)); + setDeleteWarningItem(false); + setIsLoading(false); + }; + return ( +
+
+
+

Do you want to delete this item?

+
+ + +
+
+
+ ); +} diff --git a/src/components/file-input/FileInput.jsx b/src/components/file-input/FileInput.jsx index 956b897..f1a5559 100644 --- a/src/components/file-input/FileInput.jsx +++ b/src/components/file-input/FileInput.jsx @@ -13,7 +13,6 @@ const FileInput = ({ files, setFiles, register, errors, clearErrors }) => { setFiles(Array.from(e.target.files)); setInput(true); }; - return (
{ > { accept='image/*' multiple /> - {input ? ( + {input || files ? (
{files && files.map((file) => ( ))}
diff --git a/src/components/herosection/HeroSection.jsx b/src/components/herosection/HeroSection.jsx index 0d3abd2..cb09fb1 100644 --- a/src/components/herosection/HeroSection.jsx +++ b/src/components/herosection/HeroSection.jsx @@ -1,6 +1,6 @@ import Image from "next/image"; -const HeroSection = () => { +const HeroSection = ({ description }) => { return (
@@ -11,15 +11,8 @@ const HeroSection = () => { height={500} alt='hero image' /> -
-

- Lorem ipsum dolor sit amet consectetur, adipisicing - elit. Ullam, quia ad alias non saepe aliquid, totam, - aliqu perspiciatis optio doloribus debitis vero - voluptates esse. Voluptatum nihil nam fuga minima! quis - nostr exercitation ullamco laboris nisi ut aliquip ex ea - commodo consequat. -

+
+

{description}

- -
-
- ); -}; - -export default AddItem; diff --git a/src/components/personalinfo/PersonalInfo.jsx b/src/components/personalinfo/PersonalInfo.jsx deleted file mode 100644 index bc504da..0000000 --- a/src/components/personalinfo/PersonalInfo.jsx +++ /dev/null @@ -1,35 +0,0 @@ -const PersonalInfo = () => { - return ( -
-
- - - - -
- Name Surname -

Location

-
-
- -
-
- Name Surname - Location - Phone: 01122334455 - Email: mail@mail.com - Language: EN -
-
-
- ); -}; - -export default PersonalInfo; diff --git a/src/components/profile/AvatarUploadImage.jsx b/src/components/profile/AvatarUploadImage.jsx new file mode 100644 index 0000000..cb99a81 --- /dev/null +++ b/src/components/profile/AvatarUploadImage.jsx @@ -0,0 +1,32 @@ +/* eslint-disable @next/next/no-img-element */ +import { AiFillCamera } from "react-icons/ai"; + +function AvatarUploadImage({ image, setFile, file }) { + return ( + <> +
+ { + setFile(e.target.files[0]); + }} + type='file' + className='absolute top-0 left-0 right-0 bottom-0 rounded-full opacity-0 z-30 cursor-pointer' + /> +
+
+ +
+ avatar +
+

+ Change your profile picture +

+ + ); +} + +export default AvatarUploadImage; diff --git a/src/components/profile/EditProfile.jsx b/src/components/profile/EditProfile.jsx new file mode 100644 index 0000000..d11ab97 --- /dev/null +++ b/src/components/profile/EditProfile.jsx @@ -0,0 +1,168 @@ +import clsx from "clsx"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { AiOutlineClose } from "react-icons/ai"; + +import { getDocData, updateDocData } from "@/lib/firebase/firestoreFunctions"; +import UseUploadImage from "@/lib/hooks/useUploadImage"; + +import Button from "@/components/button/Button"; +import Input from "@/components/input"; + +import { useAuth } from "@/context/AuthContext"; + +import AvatarUploadImage from "./AvatarUploadImage"; + +const inputs = [ + { + name: "name", + type: "text", + labelText: "Your Name", + placeholder: "Your Name", + requiredMessage: "Enter a name please", + validation: { + maxLength: { + value: 30, + message: "30 character max", + }, + }, + }, + { + name: "publicEmail", + type: "email", + labelText: "Your Public Email", + placeholder: "Enter Email", + requiredMessage: false, + validation: {}, + }, + { + name: "publicPhoneNumber", + type: "number", + labelText: "Phone Number", + placeholder: "Phone Number", + requiredMessage: false, + validation: { + maxLength: { + value: 11, + message: "Enter a valid number", + }, + minLength: { + value: 9, + message: "Enter a valid number", + }, + }, + }, + { + name: "bio", + type: "text", + labelText: "Bio", + placeholder: "Bio", + requiredMessage: false, + validation: { + maxLength: { + value: 50, + message: "Max 50 characters", + }, + }, + }, + { + name: "location", + type: "text", + labelText: "Location", + placeholder: "Location", + requiredMessage: false, + validation: {}, + }, +]; + +function EditProfile({ + setEditProfile, + setUserData, + name, + avatarUrl, + bio, + publicEmail, + publicPhoneNumber, + location, +}) { + const { currentUser } = useAuth(); + const [uploadFile] = UseUploadImage(); + const [file, setFile] = useState(); + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + name, + avatarUrl, + bio, + publicEmail, + publicPhoneNumber, + location, + }, + }); + + const updateProfile = async (data) => { + if (file) { + const downloadUrl = await uploadFile(file); + data.avatarUrl = downloadUrl; + } + updateDocData("users", currentUser.uid, { + ...data, + }); + setUserData({ ...data }); + setEditProfile(false); + }; + + return ( +
+
+
+
+

Edit profile

+ setEditProfile(false)} + className='text-3xl text-black cursor-pointer' + /> +
+
+ + + {inputs.map((input) => ( + + ))} + + +
+
+ ); +} + +export default EditProfile; diff --git a/src/components/profile/Profile.jsx b/src/components/profile/Profile.jsx new file mode 100644 index 0000000..ca0b4fa --- /dev/null +++ b/src/components/profile/Profile.jsx @@ -0,0 +1,102 @@ +/* eslint-disable @next/next/no-img-element */ +import { useEffect, useState } from "react"; +import { AiOutlineEdit, AiOutlineMail, AiOutlinePhone } from "react-icons/ai"; +import { BsBasketFill } from "react-icons/bs"; +import { GoLocation } from "react-icons/go"; + +import { getDocData } from "@/lib/firebase/firestoreFunctions"; + +import Button from "@/components/button/Button"; + +import { useAuth } from "@/context/AuthContext"; + +import EditProfile from "./EditProfile"; + +const Profile = () => { + const { currentUser } = useAuth(); + + const [editProfile, setEditProfile] = useState(false); + const [userData, setUserData] = useState({}); + + useEffect(() => { + getDocData("users", currentUser.uid).then((data) => { + setUserData(data); + }); + }, []); + + const { name, avatarUrl, bio, publicEmail, publicPhoneNumber, location } = + userData; + return ( + <> + {editProfile && ( + + )} +
+

My account

+
+
+
+ profile +
+
+
+
+
+

+ {`${(bio && bio) || "Add Bio"}`} +

+

+ {name} +

+
+
+
+ +
+ {/*
+ + {5 + " "} + items +
*/} +
+ + {publicEmail} +
+
+ + + {publicPhoneNumber} +
+
+ + {location} +
+
+
+
+
+
+ + ); +}; + +export default Profile; diff --git a/src/layout/Layout.jsx b/src/layout/Layout.jsx index 00ef751..e5039ae 100644 --- a/src/layout/Layout.jsx +++ b/src/layout/Layout.jsx @@ -3,12 +3,12 @@ import Navbar from "@/components/navbar/Navbar"; export default function Layout({ children }) { return ( - <> -
+
+
{children}
- +
); } diff --git a/src/lib/firebase/firestoreFunctions.js b/src/lib/firebase/firestoreFunctions.js index d1113d3..97f0c5f 100644 --- a/src/lib/firebase/firestoreFunctions.js +++ b/src/lib/firebase/firestoreFunctions.js @@ -1,6 +1,7 @@ import { addDoc, collection, + deleteDoc, doc, getDoc, getDocs, @@ -67,7 +68,9 @@ export const getItemsByUser = async (userId) => { const querySnapshot = await getDocs(q); const items = []; querySnapshot.forEach((doc) => { - items.push(doc.data()); + let data = doc.data(); + data.id = doc.id; + items.push(data); }); return items; }; @@ -86,3 +89,9 @@ export const getItemByCategory = async (category) => { }); return items; }; + +// DELETE DOC + +export const deleteDocData = async (collection, docId) => { + return await deleteDoc(doc(db, collection, docId)); +}; diff --git a/src/pages/account/index.jsx b/src/pages/account/index.jsx new file mode 100644 index 0000000..f943a58 --- /dev/null +++ b/src/pages/account/index.jsx @@ -0,0 +1,61 @@ +import { useEffect, useState } from "react"; +import { AiOutlineDelete, AiOutlineEdit } from "react-icons/ai"; + +import { getItemsByUser } from "@/lib/firebase/firestoreFunctions"; + +import DeleteWarning from "@/components/delete-warning"; +import ItemCard from "@/components/itemcard/ItemCard"; +import Profile from "@/components/profile/Profile"; + +import { useAuth } from "@/context/AuthContext"; + +export default function MyAccount() { + const { currentUser } = useAuth(); + const [items, setItems] = useState([]); + const [deleteWarningItem, setDeleteWarningItem] = useState(); + + useEffect(() => { + getItemsByUser(currentUser.uid).then((data) => { + setItems(data); + }); + }, [currentUser]); + return ( + <> + {deleteWarningItem && ( + + )} + + +
+

+ My Items: +

+ +
+ {items.map((item) => ( +
+
+ + setDeleteWarningItem(item.id) + } + className='text-red text-3xl cursor-pointer' + /> + +
+ +
+ ))} +
+
+ + ); +} diff --git a/src/pages/add-item/index.jsx b/src/pages/add-item/index.jsx index d1e5724..cb1ca86 100644 --- a/src/pages/add-item/index.jsx +++ b/src/pages/add-item/index.jsx @@ -1,10 +1,11 @@ import { clsx } from "clsx"; import { serverTimestamp } from "firebase/firestore"; +import { useRouter } from "next/router"; import { appWithTranslation } from "next-i18next"; import { useState } from "react"; import { useForm } from "react-hook-form"; -import { setDoc } from "@/lib/firebase/firestoreFunctions"; +import { setDoc, updateDocData } from "@/lib/firebase/firestoreFunctions"; import UseUploadImage from "@/lib/hooks/useUploadImage"; import Button from "@/components/button/Button"; @@ -14,15 +15,32 @@ import { useAuth } from "@/context/AuthContext"; function AddItemPage() { const { currentUser } = useAuth(); - const [files, setFiles] = useState([]); + const router = useRouter(); + const [files, setFiles] = useState(); const [uploadFile] = UseUploadImage(); + const itemQueryData = router.query; + const updateItemPageMode = !itemQueryData.isEmpty; + let inputDefaultValues = {}; + if (updateItemPageMode) { + inputDefaultValues = { + itemName: itemQueryData.title, + category: itemQueryData.categories, + description: itemQueryData.description, + location: itemQueryData.location, + }; + if (typeof itemQueryData.imagesList === "string") { + itemQueryData.imagesList = [itemQueryData.imagesList]; + } + } const { register, handleSubmit, formState: { errors, isSubmitting }, reset, clearErrors, - } = useForm(); + } = useForm({ + defaultValues: inputDefaultValues, + }); const submitItem = async (data) => { const uploadPromises = files.map(async (file) => { @@ -48,15 +66,38 @@ function AddItemPage() { await setDoc(itemData, "items"); reset(); }; + const updateItem = async (data) => { + let downloadUrls; + if (files) { + const uploadPromises = files.map(async (file) => { + return await uploadFile(file); + }); + downloadUrls = await Promise.all(uploadPromises); + } + const itemData = { + title: data.itemName, + categories: data.category, + description: data.description, + location: data.location, + imagesList: downloadUrls ? downloadUrls : itemQueryData.imagesList, + updatedAt: serverTimestamp(), + }; + await updateDocData("items", itemQueryData.id, itemData); + router.push(`/item/${itemQueryData.id}`); + }; return (

- Add new Item + {updateItemPageMode ? "Update Item" : "Add new Item"}