diff --git a/package-lock.json b/package-lock.json index c542a3c..f978d2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,8 @@ "react-dom": "^18.3.1", "react-hot-toast": "^2.4.1", "react-icons": "^5.2.1", - "stripe": "^15.7.0" + "stripe": "^15.7.0", + "use-debounce": "^10.0.1" }, "devDependencies": { "@types/node": "^20", @@ -5806,6 +5807,17 @@ "punycode": "^2.1.0" } }, + "node_modules/use-debounce": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.1.tgz", + "integrity": "sha512-0uUXjOfm44e6z4LZ/woZvkM8FwV1wiuoB6xnrrOmeAEjRDDzTLQNRFtYHvqUsJdrz1X37j0rVGIVp144GLHGKg==", + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 689875b..1378fdd 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "react-dom": "^18.3.1", "react-hot-toast": "^2.4.1", "react-icons": "^5.2.1", - "stripe": "^15.7.0" + "stripe": "^15.7.0", + "use-debounce": "^10.0.1" }, "devDependencies": { "@types/node": "^20", diff --git a/public/no-image.png b/public/no-image.png new file mode 100644 index 0000000..df2310d Binary files /dev/null and b/public/no-image.png differ diff --git a/src/app/display_accommodation/[accommodationId]/Accommodation.tsx b/src/app/display_accommodation/[accommodationId]/Accommodation.tsx index d4c5563..3401464 100644 --- a/src/app/display_accommodation/[accommodationId]/Accommodation.tsx +++ b/src/app/display_accommodation/[accommodationId]/Accommodation.tsx @@ -14,10 +14,16 @@ const Accommodation: React.FC = ({ accommodation, accommodat const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0); const handleNextPhoto = () => { + if(!photos || photos.length <= 1) { + return; + } setCurrentPhotoIndex((prevIndex) => (prevIndex + 1) % photos.length); }; const handlePrevPhoto = () => { + if(!photos || photos.length <= 1) { + return; + } setCurrentPhotoIndex((prevIndex) => (prevIndex - 1 + photos.length) % photos.length); }; @@ -26,15 +32,15 @@ const Accommodation: React.FC = ({ accommodation, accommodat

{title}

Location: {location}

- { photos ? + { photos && photos.length ?
{`Photo
- : null} -

{description}

+ : +
+ {`No +
+ } +

Experience Description:
{description}

Number of rooms: {numberOfRooms}

Price: {price} lei per night

diff --git a/src/app/display_accommodation/[accommodationId]/page.tsx b/src/app/display_accommodation/[accommodationId]/page.tsx index 7f3dd4c..5b962a7 100644 --- a/src/app/display_accommodation/[accommodationId]/page.tsx +++ b/src/app/display_accommodation/[accommodationId]/page.tsx @@ -74,6 +74,7 @@ const DisplayAccommodation: NextPage = ({ params }) = }, [receivedData]); return ( +
@@ -113,6 +114,7 @@ const DisplayAccommodation: NextPage = ({ params }) =
+ ); }; diff --git a/src/app/edit_accommodation/[accommodationId]/page.tsx b/src/app/edit_accommodation/[accommodationId]/page.tsx index c187838..888c382 100644 --- a/src/app/edit_accommodation/[accommodationId]/page.tsx +++ b/src/app/edit_accommodation/[accommodationId]/page.tsx @@ -27,7 +27,6 @@ const EditAccommodation: NextPage = ({ params }) => { const [accommodation, setAccommodation] = useState({} as DocumentData); const [receivedData, setData] = useState(false); - const { user } = useContext(UserContext); useEffect(() => { @@ -37,12 +36,12 @@ const EditAccommodation: NextPage = ({ params }) => { const accommodationData = accommodationRef.data(); if (accommodationData.uid == user.uid) { setAccommodation(accommodationRef.data()); - } else { - window.location.href = '/'; + } else if (user.uid) { + window.location.href ="/" } setData(true); } else { - window.location.href = '/'; + window.history.back() } })(); if (accommodation) { @@ -55,7 +54,7 @@ const EditAccommodation: NextPage = ({ params }) => { } setPrice(accommodation.price); } - }, [receivedData]); + }, [receivedData, user]); const handleFormSubmit = (e: FormEvent) => { diff --git a/src/app/my_accommodations/page.tsx b/src/app/my_accommodations/page.tsx index c3e2f22..4ce986c 100644 --- a/src/app/my_accommodations/page.tsx +++ b/src/app/my_accommodations/page.tsx @@ -37,26 +37,33 @@ const MyAccommodations: React.FC = () => { return ( -
+
+

My Accommodations

- - Add accommodation + + New Accommodation
-
+
+
+ {!accommodations.length && +
+ You don't have any experiences posted yet. +
+ } {accommodations.map((value) => ( -
-
+
+

{value.at(1).title}

{value.at(1).price} lei

- + -
))}
- setShowModal(false)} onConfirm={async (_e) => await handleDeleteAccommodation(selectedAccommodationId)} title="Confirm Submission" description="Are you sure you want to delete this accommodation? Operation cannot be undone!" - /> + />
); diff --git a/src/app/page.tsx b/src/app/page.tsx index e1dab18..d0b4b1d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,30 +2,41 @@ import Link from "next/link"; import { UserContext } from "../lib/context"; -import { useContext } from "react"; +import { useContext, useState } from "react"; import PostsFeed from "@/components/PostsFeed"; +import BrowseAccommodations from "@/components/BrowseAccommodations"; export default function Home() { - const { username } = useContext(UserContext); + const { username, usertype } = useContext(UserContext); + const [feedPosts, setFeedPosts] = useState([] as any[]); return (
{username ? <>

Welcome back, {username}!

-

Start planning your next trip.

- Create a post + { usertype === "guest" ? +

Browse experiences and plan your next vacation!

+ : usertype === "host" && + <> +

Manage your experiences or add a new one!

+ + Create Experience + + + } : <> -

Welcome to Turismo!

-

Sign in to start planning your next trip

- Sign in +

Welcome to Trek Trill!

+

Sign in to start planning your next trip!

+ Sign in }
-

Recent posts

- +

Latest accommodations on Trek Trill

+ {usertype === "guest" && } +
); } diff --git a/src/components/BrowseAccommodations.tsx b/src/components/BrowseAccommodations.tsx new file mode 100644 index 0000000..40dcd9f --- /dev/null +++ b/src/components/BrowseAccommodations.tsx @@ -0,0 +1,46 @@ +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react" +import { SlLocationPin } from "react-icons/sl"; +import { useDebounce } from "use-debounce" + +export default function BrowseAccommodations(props: {posts: any[]}) { + const [searchQuery, setSearchQuery]= useState("") + const [debouncedQuery] = useDebounce(searchQuery, 500); + const [filteredPosts, setFilteredPosts] = useState([] as any[]); + const router = useRouter(); + + const handleChange = (e: any) => setSearchQuery(e.target.value.toLowerCase()) + + const handleResultClick = (postId:string) => { + router.push(`/display_accommodation/${postId}`); + } + + useEffect(() => { + setFilteredPosts(props.posts.filter((post) => { + return post.location.toLowerCase().includes(searchQuery) || + post.title.toLowerCase().includes(searchQuery) + })) + }, [debouncedQuery]) + + useEffect(() => { + console.log("Filtered posts", filteredPosts) + }, [filteredPosts]); + + return ( +
+ + +
    li]:p-2 [&>li]:rounded-md [&>li]:bg-white ${debouncedQuery !== "" ? "inline-block" : "hidden"}`}> + {filteredPosts.length === 0 &&
  • No results.
  • } + {filteredPosts.map((post) => { + return ( +
  • handleResultClick(post.id)}> + {post.title} + {post.location} +
  • + ) + })} +
+
+ ) +} \ No newline at end of file diff --git a/src/components/PostsFeed.tsx b/src/components/PostsFeed.tsx index 0082f6d..ad93152 100644 --- a/src/components/PostsFeed.tsx +++ b/src/components/PostsFeed.tsx @@ -2,34 +2,49 @@ import { db } from "@/lib/firebase"; import { DocumentData, collection, getDocs } from "firebase/firestore"; -import { useEffect, useState } from "react"; +import { SetStateAction, useEffect, useState } from "react"; import Image from "next/image"; +import Link from "next/link"; +import { SlLocationPin } from "react-icons/sl"; +import { BsCashCoin } from "react-icons/bs"; -export default function PostsFeed() { +export default function PostsFeed(props: {usertype?: string, setFeedPosts?: React.Dispatch>}) { const [posts, setPosts] = useState([] as DocumentData[]); useEffect(() => { (async () => { - const querySnapshot = await getDocs(collection(db, 'posts')); - const postsData = querySnapshot.docs.map(doc => doc.data()); + const querySnapshot = await getDocs(collection(db, 'accommodations')); + const postsData = querySnapshot.docs.map(doc => ({...doc.data(), id: doc.id})); setPosts(postsData); + if(props.setFeedPosts) { + props.setFeedPosts(postsData) + } })(); }, []); return (
{posts.map((post, index) => { - if(!post.hasOwnProperty('post_title') || !post.hasOwnProperty('post_description')) { - return null; - } - return ( -
-

{post.post_title}

-

{post.post_description}

- {post?.images && post.images.length > 0 && post.images.map((image: string, jndex: number) => { - return {`Image - })} +
+

{post.title}

+

{post.location}

+

{post.description}

+
+ {post?.photos && post.photos.length > 0 ? + post.photos.map((image: string, jndex: number) => { + return {`Image + }) + : + No Images + } +
+

{post.price} RON per room

+ {props.usertype === "guest" ? + View Experience + : props.usertype !== "host" && + + }
)} )}