diff --git a/.eslintrc.js b/.eslintrc.js index 9030412..056c53d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -66,6 +66,7 @@ module.exports = { ], }, ], + "@next/next/no-img-element": "off", //#endregion //*======== Import Sort =========== }, globals: { diff --git a/README.md b/README.md index ff5087e..e430ff0 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,55 @@ -# Capstone project information +# Welcome to Let's Share -## Project information +## Project Description -- Project name: [Project name] -- Project description: [Project description] -- Technology used: Nextjs, TailwindCSS, i18next, Husky, Prettier, Eslint, Commitlinter (Conventional Commits) -- Team members: Member1, Member2, etc, -- Bootcamp: GIZ YE 2022 +Join Let's Share, where sharing is caring! Experience the magic of seamless connections as surplus food, stylish clothes, and cozy furniture find new homes through our user-friendly interface. Let's Share isn't just a platform; it's a front-end showcase, weaving together technology and warmth. Transform your daily exchanges into shared moments. Join us – where front-end innovation meets community connection! -## Installation +## Key Features -```shell -npm install -g commitizen // Installs commitizen -npx husky install // Installs Husky -chmod ug+x .husky/* // Gives husky executable extension -npm install // Installs the node modules -``` +Homepage +Sign in page +Sign up page +List of products page +Profile page +About us page +Blog page +Single blog page +Filter Products: Easily filter products by name, category, and price. +User Authentication: Create accounts, log in, and log out. +Product Details: View detailed descriptions of products. +Responsive Design: Ensure accessibility on various devices. -## Development process +## Technology Stack -- When commiting you will have to use `git cz` and then go through the process. Look at the first commit I made to know what that means. -- The translation process is done using the i18next library. There's an example in the index.js file of how this is done. Check this github project for more info on how to use the i18next library: [i18next](https://github.com/i18next/next-i18next) -- When writing commits, commitlinter library is used to make sure that your commits are consistent with the conventional commits. To learn more about it refer to the guidelines in Canvas or the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) site. -- The `layout` folder contains the components that will be used in the layout of pages, so components like the header and footer will be placed there. +Frontend: Next.js +Database: Firestore +Authentication: Firebase +Styling: CSS, Tailwind CSS -Good luck and happy coding :D +## Project Goals + +Share and reuse products like clothes and furniture amongst the community + +## Team Members + +Ismail Benlaredj : https://github.com/ismail-benlaredj +Darine Tag : https://github.com/darinetag +Hadj Said Bouras : https://github.com/Hadj-Said-Bouras +Mohamed Cheraitia : https://github.com/mohamed-cheraitia +Laid Benglia : https://github.com/LaidBengli +Sara Bagache : https://github.com/SaraBegache + +## Getting Started + +To run this project locally, follow these steps: + +creat a next.js file +Clone the repository: git clone https://github.com/202306-NEA-DZ-FEW/team-f +Navigate to the project directory: cd recoded-capstone +Install dependencies: npm install +Start the development server: npm run dev +Open your browser and visit http://localhost:3000 + +## Bootcamp + +Bootcamp: 202306-NEA-DZ-FEW diff --git a/public/images/Shop.png b/public/images/Shop.png new file mode 100644 index 0000000..958a0e0 Binary files /dev/null and b/public/images/Shop.png differ diff --git a/public/images/category_img/category_academic.webp b/public/images/category_img/category_academic.webp new file mode 100644 index 0000000..885deab Binary files /dev/null and b/public/images/category_img/category_academic.webp differ diff --git a/public/images/category_img/category_beauty.webp b/public/images/category_img/category_beauty.webp new file mode 100644 index 0000000..2bc35d6 Binary files /dev/null and b/public/images/category_img/category_beauty.webp differ diff --git a/public/images/category_img/category_clothes.webp b/public/images/category_img/category_clothes.webp new file mode 100644 index 0000000..a375f79 Binary files /dev/null and b/public/images/category_img/category_clothes.webp differ diff --git a/public/images/category_img/category_dorm.webp b/public/images/category_img/category_dorm.webp new file mode 100644 index 0000000..011864b Binary files /dev/null and b/public/images/category_img/category_dorm.webp differ diff --git a/public/images/category_img/category_electronics.webp b/public/images/category_img/category_electronics.webp new file mode 100644 index 0000000..32727df Binary files /dev/null and b/public/images/category_img/category_electronics.webp differ diff --git a/public/images/category_img/category_entertainment.webp b/public/images/category_img/category_entertainment.webp new file mode 100644 index 0000000..e5645b7 Binary files /dev/null and b/public/images/category_img/category_entertainment.webp differ diff --git a/public/images/category_img/category_other.webp b/public/images/category_img/category_other.webp new file mode 100644 index 0000000..da49503 Binary files /dev/null and b/public/images/category_img/category_other.webp differ diff --git a/public/secondherosec-icons/sec4.png b/public/secondherosec-icons/sec4.png new file mode 100644 index 0000000..df1833d Binary files /dev/null and b/public/secondherosec-icons/sec4.png differ diff --git a/public/secondherosec-icons/sec5.png b/public/secondherosec-icons/sec5.png new file mode 100644 index 0000000..b1d5b19 Binary files /dev/null and b/public/secondherosec-icons/sec5.png differ diff --git a/public/secondherosec-icons/sec6.png b/public/secondherosec-icons/sec6.png new file mode 100644 index 0000000..1eb7b7e Binary files /dev/null and b/public/secondherosec-icons/sec6.png differ diff --git a/public/secondherosec-icons/sec7.png b/public/secondherosec-icons/sec7.png new file mode 100644 index 0000000..ed5fd90 Binary files /dev/null and b/public/secondherosec-icons/sec7.png differ diff --git a/src/components/blogcard/BlogCard.jsx b/src/components/blogcard/BlogCard.jsx index b932f0b..b906d11 100644 --- a/src/components/blogcard/BlogCard.jsx +++ b/src/components/blogcard/BlogCard.jsx @@ -1,3 +1,4 @@ +import Image from "next/image"; const BlogCard = ({ blog }) => { return (
@@ -7,7 +8,7 @@ const BlogCard = ({ blog }) => { {blog.date}

- {blog.title} { +import Link from "next/link"; + +const CategoryCard = ({ imgSrc, title, href }) => { return ( -
- {category.name} -
-
{category.name}
+ +
+
+ {title} +
+

+ {title} +

-
+ ); }; 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}

{/*

Log in with:

diff --git a/src/components/personalinfo/AddItem.jsx b/src/components/personalinfo/AddItem.jsx deleted file mode 100644 index 52030a6..0000000 --- a/src/components/personalinfo/AddItem.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import Image from "next/image"; - -const AddItem = () => { - return ( -
- My items -
- product -

Product title

- - -
-
- ); -}; - -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/components/secondherosection/SecondHeroSec.jsx b/src/components/secondherosection/SecondHeroSec.jsx new file mode 100644 index 0000000..729c57a --- /dev/null +++ b/src/components/secondherosection/SecondHeroSec.jsx @@ -0,0 +1,28 @@ +import Image from "next/image"; + +const SecondHeroSection = ({ children }) => { + return ( +
+ {/* Main Content */} +
+ {/* Hero Image */} + hero image + + {/* Card Section */} +
+
+ {children} +
+
+
+
+ ); +}; + +export default SecondHeroSection; diff --git a/src/components/signup/SignUpForm.jsx b/src/components/signup/SignUpForm.jsx index 8ae0f51..25862c0 100644 --- a/src/components/signup/SignUpForm.jsx +++ b/src/components/signup/SignUpForm.jsx @@ -1,79 +1,243 @@ +import React, { useState } from "react"; +import { useForm } from "react-hook-form"; + +import { useAuth } from "@/context/AuthContext"; + const SignUpForm = () => { + const { signup } = useAuth(); + const { + register, + getValues, + handleSubmit, + formState: { errors }, + } = useForm(); // Initialize react-hook-form + + const [formData, setFormData] = useState({ + name: "", + email: "", + password: "", + confirmPassword: "", + phone: "", + location: "", + }); + const [firebaseInitialized, setFirebaseInitialized] = useState(true); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + + const onSubmit = async (data) => { + try { + if (!firebaseInitialized) { + console.log("Firebase is not yet initialized. Please wait."); + return; + } + const { email, password, confirmPassword, phone, location } = data; + + // Add validation logic for email and password + if (email === "" || password === "") { + console.error("Email and password are required."); + return; + } + if (!isValidEmail(email)) { + console.error("Invalid email format."); + return; + } + if (password.length < 6) { + console.error("Password must have at least 6 characters."); + return; + } + if (confirmPassword.length < 6) { + console.error("Password and Confirm Password do not match."); + + return; + } + if (phone === "") { + console.error("Phone number is required."); + return; + } + // Validate the location (country and city) + const locationParts = location.split(","); + if (locationParts.length !== 2) { + console.error( + "Location must be in the format 'Country, City'.", + ); + return; + } + + // Create a user with email and password + const userCredential = await signup(email, password); + } catch (error) { + console.error("Error signing up:", error.message); + } + }; + + const isValidEmail = (email) => { + const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; + return emailRegex.test(email); + }; + return (

Sign Up

-
+
+ {errors.name && ( + + {errors.name.message} + + )}
+ {errors.email && ( + + {errors.email.type === "required" + ? "Email is required." + : "Invalid email format."} + + )}
+ {errors.password && ( + + {errors.password.type === "required" + ? "Password is required." + : "Password must have at least 6 characters."} + + )}
-
- + {...register("location", { required: true })} + value={formData.location} + onChange={handleChange} + placeholder='Country, City' + autoComplete='off' + />
- @@ -83,4 +247,5 @@ const SignUpForm = () => {
); }; + export default SignUpForm; diff --git a/src/constants/index.js b/src/constants/index.js new file mode 100644 index 0000000..177101e --- /dev/null +++ b/src/constants/index.js @@ -0,0 +1,142 @@ +import Image from "next/image"; + +export const marketingCardData1 = [ + { + icon: ( + Icon 1 + ), + title: "Card 1 Title", + content: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + }, + { + icon: ( + Icon 1 + ), + title: "Card 2 Title", + content: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + }, + { + icon: ( + Icon 1 + ), + title: "Card 3 Title", + content: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + }, +]; +export const marketingCardData2 = [ + { + icon: ( + Icon 1 + ), + title: "Card 1 Title", + content: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + }, + { + icon: ( + Icon 1 + ), + title: "Card 2 Title", + content: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + }, + { + icon: ( + Icon 1 + ), + title: "Card 3 Title", + content: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + }, + { + icon: ( + Icon 1 + ), + title: "Card 4 Title", + content: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + }, +]; + +export const categories = [ + { + id: 1, + title: "Electronics", + imgSrc: "/images/category_img/category_electronics.webp", + href: "products/?category=electronics", + }, + { + id: 2, + title: "Academic", + imgSrc: "/images/category_img/category_academic.webp", + href: "products/?category=academic", + }, + { + id: 3, + title: "Clothes", + imgSrc: "/images/category_img/category_clothes.webp", + href: "products/?category=clothes", + }, + { + id: 4, + title: "Dorm", + imgSrc: "/images/category_img/category_dorm.webp", + href: "products/?category=dorm", + }, + { + id: 5, + title: "Entertainment", + imgSrc: "/images/category_img/category_entertainment.webp", + href: "products/?category=entertainment", + }, + { + id: 6, + title: "Beauty", + imgSrc: "/images/category_img/category_beauty.webp", + href: "products/?category=beauty", + }, + { + id: 7, + title: "Other", + imgSrc: "/images/category_img/category_other.webp", + href: "products/?category=other", + }, +]; diff --git a/src/layout/Layout.jsx b/src/layout/Layout.jsx index 00ef751..b93962f 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..8f05bd8 100644 --- a/src/lib/firebase/firestoreFunctions.js +++ b/src/lib/firebase/firestoreFunctions.js @@ -1,9 +1,12 @@ import { addDoc, collection, + deleteDoc, doc, getDoc, getDocs, + limit, + orderBy, query, updateDoc, where, @@ -67,7 +70,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 +91,22 @@ export const getItemByCategory = async (category) => { }); return items; }; + +// DELETE DOC + +export const deleteDocData = async (collection, docId) => { + return await deleteDoc(doc(db, collection, docId)); +}; + +// GET ALL +export const getAllItems = async () => { + const q = query(collection(db, "items"), orderBy("createdAt"), limit(15)); + const querySnapshot = await getDocs(q); + const items = []; + querySnapshot.forEach((doc) => { + let data = doc.data(); + data.id = doc.id; + items.push(data); + }); + return items; +}; diff --git a/src/pages/account/index.jsx b/src/pages/account/index.jsx new file mode 100644 index 0000000..8529575 --- /dev/null +++ b/src/pages/account/index.jsx @@ -0,0 +1,68 @@ +import Link from "next/link"; +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..7a76127 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 = Object.keys(itemQueryData).length !== 0; + 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"}

diff --git a/src/pages/addItemPage/index.jsx b/src/pages/addItemPage/index.jsx new file mode 100644 index 0000000..0ac346c --- /dev/null +++ b/src/pages/addItemPage/index.jsx @@ -0,0 +1,161 @@ +import React, { useState, useRef } from "react"; +import { BiSolidDownArrow } from "react-icons/bi"; + +export default function Index() { + const clothes = "whatever"; + const place = "whatever"; + const [selectedPhotos, setSelectedPhotos] = useState([]); + const [deleteIndices, setDeleteIndices] = useState([]); + const fileInputRef = useRef(null); + + const handleFileChange = (e) => { + const files = e.target.files; + const photoURLs = [...selectedPhotos]; + + for (let i = 0; i < files.length; i++) { + const photoURL = URL.createObjectURL(files[i]); + photoURLs.push(photoURL); + } + + setSelectedPhotos(photoURLs); + }; + + const handleDelete = (index) => { + const updatedPhotos = [...selectedPhotos]; + updatedPhotos.splice(index, 1); + setSelectedPhotos(updatedPhotos); + setDeleteIndices([...deleteIndices, index]); + }; + + const handleUploadClick = () => { + fileInputRef.current.click(); + }; + + return ( +
+
+

+ Add an Item +

+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +