Skip to content

Commit

Permalink
Merge branch 'dashboard'
Browse files Browse the repository at this point in the history
  • Loading branch information
lGnyte committed May 28, 2024
2 parents 72c9cb1 + 4f7f88e commit 9bd5660
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 44 deletions.
14 changes: 13 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Binary file added public/no-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 22 additions & 4 deletions src/app/display_accommodation/[accommodationId]/Accommodation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@ const Accommodation: React.FC<AccommodationProps> = ({ 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);
};

Expand All @@ -26,15 +32,15 @@ const Accommodation: React.FC<AccommodationProps> = ({ accommodation, accommodat
<h1 className="text-3xl">{title}</h1>
<AccommodationStarRating accommodationId={accommodationId} />
<p><strong>Location:</strong> {location}</p>
{ photos ?
{ photos && photos.length ?
<div style={{ position: 'relative', marginBottom: '20px' }}>
<Image
src={photos[currentPhotoIndex]}
alt={`Photo ${currentPhotoIndex + 1}`}
width={800}
height={600}
objectFit="cover"
style={{ borderRadius: '8px' }}
className="width-[800px] h-[600px]"
/>
<button
onClick={handlePrevPhoto}
Expand Down Expand Up @@ -69,8 +75,20 @@ const Accommodation: React.FC<AccommodationProps> = ({ accommodation, accommodat
</button>
</div>
: null}
<p className="pb-4">{description}</p>
:
<div>
<Image
priority
src={"/no-image.png"}
alt={`No images`}
width={500}
height={500}
style={{ borderRadius: '8px' }}
className='w-[500px] h-[500px]'
/>
</div>
}
<p className="mb-4"><strong>Experience Description:</strong> <br />{description}</p>
<p><strong>Number of rooms:</strong> {numberOfRooms}</p>
<p><strong>Price:</strong> {price} lei per night</p>
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/app/display_accommodation/[accommodationId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const DisplayAccommodation: NextPage<DisplayAccommodationProps> = ({ params }) =
}, [receivedData]);

return (
<AuthCheck usertype='guest'>
<div className="max-w-4xl mx-auto p-5 flex flex-col">
<Accommodation accommodation={accommodation} accommodationId={params.accommodationId} />
<div className="flex space-x-12 mt-4 pb-10 px-10" >
Expand Down Expand Up @@ -113,6 +114,7 @@ const DisplayAccommodation: NextPage<DisplayAccommodationProps> = ({ params }) =
<Reviews accommodationId={params.accommodationId} />
</AuthCheck>
</div>
</AuthCheck>
);

};
Expand Down
9 changes: 4 additions & 5 deletions src/app/edit_accommodation/[accommodationId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const EditAccommodation: NextPage<EditAccommodationProps> = ({ params }) => {
const [accommodation, setAccommodation] = useState({} as DocumentData);
const [receivedData, setData] = useState(false);


const { user } = useContext(UserContext);

useEffect(() => {
Expand All @@ -37,12 +36,12 @@ const EditAccommodation: NextPage<EditAccommodationProps> = ({ 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) {
Expand All @@ -55,7 +54,7 @@ const EditAccommodation: NextPage<EditAccommodationProps> = ({ params }) => {
}
setPrice(accommodation.price);
}
}, [receivedData]);
}, [receivedData, user]);


const handleFormSubmit = (e: FormEvent) => {
Expand Down
27 changes: 17 additions & 10 deletions src/app/my_accommodations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,33 @@ const MyAccommodations: React.FC = () => {

return (
<AuthCheck usertype="host">
<div className="container mx-auto">
<div className="mx-10 pt-6">
<h1 className='text-4xl font-bold'>My Accommodations</h1>
<div className="mt-8 mb-4">
<Link href={"/add_accommodation"} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Add accommodation
<Link href={"/add_accommodation"} className="bg-gray-800 hover:bg-gray-500 duration-200 text-white font-semibold py-2 px-4 rounded-md">
New Accommodation
</Link>
</div>
<div>
<hr className='my-4' />
<div className='flex gap-6'>
{!accommodations.length &&
<div>
You don't have any experiences posted yet.
</div>
}
{accommodations.map((value) => (
<div key={value.at(0)} className="flex items-center justify-between border-b border-gray-200 py-4">
<div>
<div key={value.at(0)} className="flex w-[400px] gap-10 items-center justify-between border border-gray-200 p-4 rounded-md shadow-md">
<div className="flex-1">
<h2 className="text-lg font-semibold">{value.at(1).title}</h2>
<p className="text-gray-600">{value.at(1).price} lei</p>
</div>
<div className="flex items-center space-x-4">
<Link href={`/edit_accommodation/${value.at(0)}`} className="text-blue-500 hover:text-blue-700">
<Link href={`/edit_accommodation/${value.at(0)}`} title='Edit Accommodation' className="text-blue-500 hover:text-blue-700">
<svg className="h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 4H7.2C6.0799 4 5.51984 4 5.09202 4.21799C4.71569 4.40974 4.40973 4.7157 4.21799 5.09202C4 5.51985 4 6.0799 4 7.2V16.8C4 17.9201 4 18.4802 4.21799 18.908C4.40973 19.2843 4.71569 19.5903 5.09202 19.782C5.51984 20 6.0799 20 7.2 20H16.8C17.9201 20 18.4802 20 18.908 19.782C19.2843 19.5903 19.5903 19.2843 19.782 18.908C20 18.4802 20 17.9201 20 16.8V12.5M15.5 5.5L18.3284 8.32843M10.7627 10.2373L17.411 3.58902C18.192 2.80797 19.4584 2.80797 20.2394 3.58902C21.0205 4.37007 21.0205 5.6364 20.2394 6.41745L13.3774 13.2794C12.6158 14.0411 12.235 14.4219 11.8012 14.7247C11.4162 14.9936 11.0009 15.2162 10.564 15.3882C10.0717 15.582 9.54378 15.6885 8.48793 15.9016L8 16L8.04745 15.6678C8.21536 14.4925 8.29932 13.9048 8.49029 13.3561C8.65975 12.8692 8.89125 12.4063 9.17906 11.9786C9.50341 11.4966 9.92319 11.0768 10.7627 10.2373Z" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</Link>
<button className="text-red-500 hover:text-red-700" onClick={() => {setSelectedAccommodationId(value.at(0)); setShowModal(true)}}>
<button className="text-red-500 hover:text-red-700" onClick={() => {setSelectedAccommodationId(value.at(0)); setShowModal(true)}} title='Delete Accommodation'>
<svg className="h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" strokeWidth="0"></g><g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g><g id="SVGRepo_iconCarrier">
<path d="M18 6L17.1991 18.0129C17.129 19.065 17.0939 19.5911 16.8667 19.99C16.6666 20.3412 16.3648 20.6235 16.0011 20.7998C15.588 21 15.0607 21 14.0062 21H9.99377C8.93927 21 8.41202 21 7.99889 20.7998C7.63517 20.6235 7.33339 20.3412 7.13332 19.99C6.90607 19.5911 6.871 19.065 6.80086 18.0129L6 6M4 6H20M16 6L15.7294 5.18807C15.4671 4.40125 15.3359 4.00784 15.0927 3.71698C14.8779 3.46013 14.6021 3.26132 14.2905 3.13878C13.9376 3 13.523 3 12.6936 3H11.3064C10.477 3 10.0624 3 9.70951 3.13878C9.39792 3.26132 9.12208 3.46013 8.90729 3.71698C8.66405 4.00784 8.53292 4.40125 8.27064 5.18807L8 6M14 10V17M10 10V17" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
</path>
Expand All @@ -67,13 +74,13 @@ const MyAccommodations: React.FC = () => {
</div>
))}
</div>
<ConfirmationModal
<ConfirmationModal
show={showModal}
onClose={() => 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!"
/>
/>
</div>
</AuthCheck>
);
Expand Down
29 changes: 20 additions & 9 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<main className="p-10">
{username ?
<>
<h1 className="text-3xl font-bold mb-3">Welcome back, {username}!</h1>
<p className="text-xl mb-2">Start planning your next trip.</p>
<Link href="/post" className="px-4 py-2 bg-gray-500 rounded-md text-lg text-white font-bold">Create a post</Link>
{ usertype === "guest" ?
<p className="text-xl mb-2">Browse experiences and plan your next vacation!</p>
: usertype === "host" &&
<>
<p className="text-xl mb-2">Manage your experiences or add a new one!</p>
<Link href={"/add_accommodation"} className="px-4 py-2 bg-gray-800 rounded-md hover:bg-gray-500 duration-200 text-white font-semibold">
Create Experience
</Link>
</>
}
</>
:
<>
<h1 className="text-3xl font-bold">Welcome to Turismo!</h1>
<p className="text-xl mb-2">Sign in to start planning your next trip</p>
<Link href="/login" className="px-4 py-2 bg-gray-500 rounded-md text-xl text-white font-bold">Sign in</Link>
<h1 className="text-3xl font-bold">Welcome to Trek Trill!</h1>
<p className="text-xl mb-2">Sign in to start planning your next trip!</p>
<Link href="/login" className="px-4 py-2 bg-gray-800 hover:bg-gray-500 duration-200 rounded-md text-xl text-white font-bold">Sign in</Link>
</>
}
<hr className="my-10" />
<h2 className="text-2xl font-bold">Recent posts</h2>
<PostsFeed />
<h2 className="text-2xl font-bold mb-6">Latest accommodations on Trek Trill</h2>
{usertype === "guest" && <BrowseAccommodations posts={feedPosts} />}
<PostsFeed usertype={usertype} setFeedPosts={setFeedPosts} />
</main>
);
}
Expand Down
46 changes: 46 additions & 0 deletions src/components/BrowseAccommodations.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="mb-10">
<label className="text-lg mr-2" htmlFor="search">Browse by location or name:</label>
<input type="text" name="search" placeholder="e.g. Iasi" id="search" value={searchQuery} onChange={handleChange} className="p-2 border rounded-md" />
<ul className={`absolute border rounded-md ml-2 w-[300px] [&>li]:p-2 [&>li]:rounded-md [&>li]:bg-white ${debouncedQuery !== "" ? "inline-block" : "hidden"}`}>
{filteredPosts.length === 0 && <li className="">No results.</li>}
{filteredPosts.map((post) => {
return (
<li key={post.id} className="cursor-pointer flex justify-between hover:bg-gray-100" onClick={() => handleResultClick(post.id)}>
<span>{post.title}</span>
<span className="text-sm text-gray-500"><SlLocationPin className="inline-block" /> {post.location}</span>
</li>
)
})}
</ul>
</div>
)
}
43 changes: 29 additions & 14 deletions src/components/PostsFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<SetStateAction<any[]>>}) {
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 (
<section className="grid grid-cols-3 gap-4">
{posts.map((post, index) => {
if(!post.hasOwnProperty('post_title') || !post.hasOwnProperty('post_description')) {
return null;
}

return (
<div key={`post${index}`} className="bg-gray-100 p-4 rounded-md">
<h3 className="text-xl font-bold">{post.post_title}</h3>
<p className="text-lg">{post.post_description}</p>
{post?.images && post.images.length > 0 && post.images.map((image: string, jndex: number) => {
return <Image key={`${index}_img_${jndex}`} src={image} width={100} height={100} alt={`Image ${index + 1}`} className="inline-block" />
})}
<div key={`post${index}`} className="bg-gray-100 p-4 rounded-md border border-gray-300 shadow-md">
<h3 className="text-xl font-bold">{post.title}</h3>
<p className="text-sm"><SlLocationPin className="inline-block" /> {post.location}</p>
<p className="text-lg mb-4">{post.description}</p>
<div>
{post?.photos && post.photos.length > 0 ?
post.photos.map((image: string, jndex: number) => {
return <Image key={`${index}_img_${jndex}`} src={image} width={100} height={100} alt={`Image ${index + 1}`} className="w-[100px] h-[100px] inline-block" />
})
:
<Image src={"/no-image.png"} width={100} height={100} alt="No Images" className="w-[100px] h-[100px]" />
}
</div>
<p className="text-sm mb-4"><BsCashCoin className="inline-block" /> <strong>{post.price} RON</strong> per room</p>
{props.usertype === "guest" ?
<Link href={`display_accommodation/${post.id}`} className="bg-gray-800 hover:bg-gray-500 rounded-md duration-200 px-4 py-2 text-white font-semibold mt-2">View Experience</Link>
: props.usertype !== "host" &&
<button disabled className="px-2 py-1 bg-gray-300 cursor-not-allowed rounded-md">Sign In to view</button>
}
</div>
)}
)}
Expand Down

0 comments on commit 9bd5660

Please sign in to comment.