Skip to content

Commit

Permalink
feat(listing, reservation): done creating listing detail and reservat…
Browse files Browse the repository at this point in the history
…ion creation
  • Loading branch information
devinpapalangi committed Aug 7, 2024
1 parent b1d54d4 commit 84b0979
Show file tree
Hide file tree
Showing 18 changed files with 560 additions and 8 deletions.
41 changes: 41 additions & 0 deletions app/actions/getListingById.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { list } from "postcss";
import prisma from "../libs/prismadb";

interface IParams {
listingId?: string;
}

export default async function getListingById(params: IParams) {
try {
const { listingId } = params;

const listing = await prisma.listing.findUnique({
where: {
id: listingId,
},
include: {
user: true,
},
});

if (!listing) {
return null;
}

const safeListing = {
...listing,
createdAt: listing.createdAt.toISOString(),
updatedAt: listing.updatedAt.toISOString(),
user: {
...listing.user,
createdAt: listing.user.createdAt.toISOString(),
updatedAt: listing.user.updatedAt.toISOString(),
emailVerified: listing.user.emailVerified?.toISOString() || null,
},
};

return safeListing;
} catch (error) {
throw new Error("Error");
}
}
6 changes: 5 additions & 1 deletion app/actions/getListings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ export default async function getListings() {
},
});

return listings;
const safeListings = listings.map((listing) => ({
...listing,
createdAt: listing.createdAt.toISOString(),
}));
return safeListings;
} catch (error) {
throw new Error("Failed to fetch listings");
}
Expand Down
36 changes: 36 additions & 0 deletions app/api/reservations/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { getCurrentUser } from "@/app/actions/getCurrentUser";
import { NextResponse } from "next/server";
import prisma from "@/app/libs/prismadb";

export async function POST(request: Request) {
const currentUser = await getCurrentUser();

if (!currentUser) {
return NextResponse.error();
}

const body = await request.json();
const { listingId, startDate, endDate, totalPrice } = body;

if (!listingId || !startDate || !endDate || !totalPrice) {
return NextResponse.error();
}

const listingAndReservation = await prisma.listing.update({
where: {
id: listingId,
},
data: {
reservations: {
create: {
userId: currentUser.id,
startDate,
endDate,
totalPrice,
},
},
},
});

return NextResponse.json(listingAndReservation);
}
29 changes: 29 additions & 0 deletions app/components/Inputs/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use client";

import React from "react";
import { DateRange, Range, RangeKeyDict } from "react-date-range";

import "react-date-range/dist/styles.css";
import "react-date-range/dist/theme/default.css";
interface Props {
value: Range;
onChange: (value: RangeKeyDict) => void;
disabledDates?: Date[];
}

const Calendar: React.FC<Props> = ({ value, onChange, disabledDates }) => {
return (
<DateRange
rangeColors={["#262626"]}
ranges={[value]}
date={new Date()}
onChange={onChange}
direction="vertical"
showDateDisplay={false}
disabledDates={disabledDates}
minDate={new Date()}
/>
);
};

export default Calendar;
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AiFillHeart, AiOutlineHeart } from "react-icons/ai";

interface Props {
id: string;
currentUser: SafeUser | null;
currentUser?: SafeUser | null;
}

const HeartButton: React.FC<Props> = ({ id, currentUser }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
"use client";

import { useCountries } from "@/app/hooks/useCountries";
import { SafeUser } from "@/app/types";
import { SafeListing, SafeUser } from "@/app/types";
import { Listing, Reservation } from "@prisma/client";
import { useRouter } from "next/navigation";
import { format } from "date-fns";
import React, { useCallback, useMemo } from "react";
import Image from "next/image";
import HeartButton from "./HeartButton";
import Button from "../Button";
import HeartButton from "./HeartButton";

interface Props {
currentUser: SafeUser | null;
data: Listing;
data: SafeListing;
reservation?: Reservation;
onAction?: (id: string) => void;
actionLabel?: string;
Expand Down
28 changes: 28 additions & 0 deletions app/components/Listing/ListingCategory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import React from "react";
import { Category } from "@/app/utils/types";
import { IconType } from "react-icons";

interface Props {
category: Category;
}

const ListingCategory: React.FC<Props> = ({ category }) => {
const Icon = category.icon as IconType;
return (
<div className="flex flex-col gap-6">
<div className="flex flex-row items-center gap-4">
<Icon size={40} className="text-neutral-600" />
<div className="flex flex-col">
<div className="text-lg font-semibold">{category.label}</div>
<div className="text-neutral-500 font-light">
{category.description}
</div>
</div>
</div>
</div>
);
};

export default ListingCategory;
48 changes: 48 additions & 0 deletions app/components/Listing/ListingHead.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client";

import { useCountries } from "@/app/hooks/useCountries";
import { SafeUser } from "@/app/types";
import React from "react";
import Heading from "../Heading";
import Image from "next/image";
import HeartButton from "./HeartButton";

interface Props {
title: string;
imageSrc: string;
locationValue: string;
id: string;
currentUser?: SafeUser | null;
}
const ListingHead: React.FC<Props> = ({
title,
imageSrc,
locationValue,
id,
currentUser,
}) => {
const { getByValue } = useCountries();

const location = getByValue(locationValue);
return (
<>
<Heading
title={title}
subtitle={`${location?.region}, ${location?.label}`}
/>
<div className="w-full h-[60vh] overflow-hidden rounded-xl relative">
<Image
src={imageSrc}
alt="image"
fill
className="object-cover w-full"
/>
<div className="absolute top-5 right-5">
<HeartButton id={id} currentUser={currentUser} />
</div>
</div>
</>
);
};

export default ListingHead;
59 changes: 59 additions & 0 deletions app/components/Listing/ListingInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use client";

import { useCountries } from "@/app/hooks/useCountries";
import { SafeUser } from "@/app/types";
import { Category } from "@/app/utils/types";
import React from "react";
import Avatar from "../Avatar";
import ListingCategory from "./ListingCategory";
import dynamic from "next/dynamic";

const Map = dynamic(() => import("../Map"), {
ssr: false,
});

interface Props {
user: SafeUser;
description: string;
category?: Category;
roomCount: number;
guessCount: number;
bathroomCount: number;
locationValue: string;
}

const ListingInfo: React.FC<Props> = ({
user,
description,
category,
roomCount,
guessCount,
bathroomCount,
locationValue,
}) => {
const { getByValue } = useCountries();
const coordinates = getByValue(locationValue)?.latitudeLongitude;
return (
<div className="col-span-4 flex flex-col gap-8">
<div className="flex flex-col gap-2">
<div className="text-xl font-semibold flex flex-row items-center gap-2">
<div>Hosted by {user?.name}</div>
<Avatar src={user?.image} />
</div>
<div className="flex flex-row items-center gap-4 font-light text-neutral-500">
<div>{guessCount} guests</div>
<div>{roomCount} rooms</div>
<div>{bathroomCount} bathrooms</div>
</div>
</div>
<hr />
{category && <ListingCategory category={category} />}
<hr />
<div className="text-lg font-light text-neutral-500">{description}</div>
<hr />
<Map center={coordinates} />
</div>
);
};

export default ListingInfo;
50 changes: 50 additions & 0 deletions app/components/Listing/ListingReservation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client";

import React from "react";
import { Range } from "react-date-range";
import Calendar from "../Inputs/Calendar";
import Button from "../Button";

interface Props {
price: number;
dateRange: Range;
totalPrice: number;
onChange: (value: Range) => void;
onSubmit: () => void;
disabled?: boolean;
disabledDates: Date[];
}
const ListingReservation: React.FC<Props> = ({
price,
dateRange,
totalPrice,
onChange,
onSubmit,
disabled,
disabledDates,
}) => {
return (
<div className="bg-white rounded-xl border-[1px] border-neutral-200 overflow-hidden">
<div className="flex flex-row items-center gap-1 p-4">
<div className="text-2xl font-semibold">{`$${price}`}</div>
<div className="font-light text-neutral-600">night</div>
</div>
<hr />
<Calendar
value={dateRange}
disabledDates={disabledDates}
onChange={(value) => onChange(value.selection)}
/>
<div className="p-4">
<Button disabled={disabled} label="Reserve" onClick={onSubmit} />
</div>
<hr />
<div className="p-4 flex flex-row items-center justify-between font-semibold text-lg">
<div>Total</div>
<div>$ {totalPrice}</div>
</div>
</div>
);
};

export default ListingReservation;
9 changes: 9 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}

.rdrMonthP {
width: 100% !important;
}

.rdrCalendarWrapper {
width: 100% !important;
font-size: 16px !important;
}
Loading

0 comments on commit 84b0979

Please sign in to comment.