Skip to content

Commit ca8ac1c

Browse files
committed
Addressing frontend
1 parent 35175a8 commit ca8ac1c

10 files changed

Lines changed: 517 additions & 125 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Link from "next/link";
2+
import { notFound } from "next/navigation";
3+
import { BackButton } from "@/components/listings/detail/BackButton";
4+
import { EditListingForm } from "@/components/listings/form/EditListingForm";
5+
import { ListingImageGallery } from "@/components/listings/detail/ListingImageGallery";
6+
import { getCurrentUser, getListingOrNotFound } from "@/lib/actions";
7+
8+
export default async function EditItemPage({ params }: { params: Promise<{ id: string }> }) {
9+
const { id } = await params;
10+
const [listing, currentUser] = await Promise.all([getListingOrNotFound(id), getCurrentUser()]);
11+
12+
if (listing.listing_type !== "item" || currentUser.id !== listing.seller.id) {
13+
notFound();
14+
}
15+
16+
return (
17+
<div className="container mx-auto w-full max-w-[96rem] px-4 pt-4 pb-12 sm:px-12">
18+
<Link href={`/items/${listing.id}`}>
19+
<BackButton />
20+
</Link>
21+
22+
<h1 className="mb-8 pt-2 text-3xl font-bold">Edit Item</h1>
23+
24+
<div className="grid grid-cols-1 gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
25+
<ListingImageGallery images={listing.images} />
26+
<EditListingForm listing={listing} />
27+
</div>
28+
</div>
29+
);
30+
}

frontend/app/items/[id]/page.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
import { ListingDetail } from "@/components/listings/detail/ListingDetail";
2-
import { getCurrentUser, getListingOrNotFound, getMyOfferForListing, getOffersForListing } from "@/lib/actions";
2+
import {
3+
getCurrentUser,
4+
getListingOrNotFound,
5+
getMyOfferForListing,
6+
getOffersReceivedForListing,
7+
} from "@/lib/actions";
38

49
export default async function ItemPage({ params }: { params: Promise<{ id: string }> }) {
510
const { id } = await params;
611
const [item, currentUser] = await Promise.all([getListingOrNotFound(id), getCurrentUser()]);
712
const isOwner = currentUser?.id === item.seller.id;
8-
const offersResponse = isOwner ? await getOffersForListing(item.id) : null;
9-
const offers = offersResponse?.results ?? [];
10-
const initialMyOffer = !isOwner ? await getMyOfferForListing(item.id) : null;
13+
const offersReceivedResponse = isOwner ? await getOffersReceivedForListing(item.id) : null;
14+
const offersReceived = offersReceivedResponse?.results ?? [];
15+
const myOfferGiven = !isOwner ? await getMyOfferForListing(item.id) : null;
1116

1217
return (
1318
<ListingDetail
1419
listing={item}
1520
initialIsFavorited={item.is_favorited ?? false}
16-
offers={offers}
21+
offersReceived={offersReceived}
1722
isOwner={isOwner}
18-
initialMyOffer={initialMyOffer}
23+
myOfferGiven={myOfferGiven}
1924
/>
2025
);
2126
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Link from "next/link";
2+
import { notFound } from "next/navigation";
3+
import { BackButton } from "@/components/listings/detail/BackButton";
4+
import { EditListingForm } from "@/components/listings/form/EditListingForm";
5+
import { ListingImageGallery } from "@/components/listings/detail/ListingImageGallery";
6+
import { getCurrentUser, getListingOrNotFound } from "@/lib/actions";
7+
8+
export default async function EditSubletPage({ params }: { params: Promise<{ id: string }> }) {
9+
const { id } = await params;
10+
const [listing, currentUser] = await Promise.all([getListingOrNotFound(id), getCurrentUser()]);
11+
12+
if (listing.listing_type !== "sublet" || currentUser.id !== listing.seller.id) {
13+
notFound();
14+
}
15+
16+
return (
17+
<div className="container mx-auto w-full max-w-[96rem] px-4 pt-4 pb-12 sm:px-12">
18+
<Link href={`/sublets/${listing.id}`}>
19+
<BackButton />
20+
</Link>
21+
22+
<h1 className="mb-8 pt-2 text-3xl font-bold">Edit Sublet</h1>
23+
24+
<div className="grid grid-cols-1 gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
25+
<ListingImageGallery images={listing.images} />
26+
<EditListingForm listing={listing} />
27+
</div>
28+
</div>
29+
);
30+
}

frontend/app/sublets/[id]/page.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
import { ListingDetail } from "@/components/listings/detail/ListingDetail";
2-
import { getCurrentUser, getListingOrNotFound, getMyOfferForListing, getOffersForListing } from "@/lib/actions";
2+
import {
3+
getCurrentUser,
4+
getListingOrNotFound,
5+
getMyOfferForListing,
6+
getOffersReceivedForListing,
7+
} from "@/lib/actions";
38

49
export default async function SubletPage({ params }: { params: Promise<{ id: string }> }) {
510
const { id } = await params;
611
const [sublet, currentUser] = await Promise.all([getListingOrNotFound(id), getCurrentUser()]);
712
const isOwner = currentUser?.id === sublet.seller.id;
8-
const offersResponse = isOwner ? await getOffersForListing(sublet.id) : null;
9-
const offers = offersResponse?.results ?? [];
10-
const initialMyOffer = !isOwner ? await getMyOfferForListing(sublet.id) : null;
13+
const offersReceivedResponse = isOwner ? await getOffersReceivedForListing(sublet.id) : null;
14+
const offersReceived = offersReceivedResponse?.results ?? [];
15+
const myOfferGiven = !isOwner ? await getMyOfferForListing(sublet.id) : null;
1116

1217
return (
1318
<ListingDetail
1419
listing={sublet}
1520
initialIsFavorited={sublet.is_favorited ?? false}
16-
offers={offers}
21+
offersReceived={offersReceived}
1722
isOwner={isOwner}
18-
initialMyOffer={initialMyOffer}
23+
myOfferGiven={myOfferGiven}
1924
/>
2025
);
2126
}

frontend/components/listings/detail/DeleteListing.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useRouter } from "next/navigation";
4-
import { useMutation } from "@tanstack/react-query";
4+
import { useMutation, useQueryClient } from "@tanstack/react-query";
55
import { Button } from "@/components/ui/button";
66
import { deleteListing } from "@/lib/actions";
77
import type { Item, Sublet } from "@/lib/types";
@@ -22,11 +22,15 @@ interface Props {
2222

2323
export const DeleteListing = ({ listing, open, onOpenChange }: Props) => {
2424
const router = useRouter();
25+
const queryClient = useQueryClient();
2526
const typeLabel = listing.listing_type === "sublet" ? "Sublet" : "Item";
2627

2728
const deleteMutation = useMutation({
2829
mutationFn: () => deleteListing(listing.id),
2930
onSuccess: () => {
31+
const listKey = listing.listing_type === "sublet" ? "sublets" : "items";
32+
queryClient.invalidateQueries({ queryKey: [listKey] });
33+
queryClient.removeQueries({ queryKey: ["listing", listing.id] });
3034
router.push(listing.listing_type === "sublet" ? "/sublets" : "/items");
3135
},
3236
onError: () => {

frontend/components/listings/detail/ListingActions.tsx

Lines changed: 7 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,23 @@
11
"use client";
22

33
import { useState } from "react";
4+
import Link from "next/link";
45
import { DollarSign } from "lucide-react";
5-
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
6+
import { useQuery, useQueryClient } from "@tanstack/react-query";
67
import { MakeOfferModal } from "@/components/listings/offer/MakeOfferModal";
78
import { PhoneInputModal } from "@/components/listings/offer/PhoneInputModal";
89
import { VerificationCodeModal } from "@/components/listings/offer/VerificationCodeModal";
9-
import { EditListing } from "@/components/listings/detail/EditListing";
1010
import { DeleteListing } from "@/components/listings/detail/DeleteListing";
1111
import { Button } from "@/components/ui/button";
12-
import {
13-
Dialog,
14-
DialogContent,
15-
DialogDescription,
16-
DialogFooter,
17-
DialogHeader,
18-
DialogTitle,
19-
} from "@/components/ui/dialog";
2012
import { getPhoneStatus } from "@/lib/actions";
21-
import type { Item, Offer, Sublet } from "@/lib/types";
22-
import { getMyOfferForListing } from "@/lib/actions";
23-
import { MyOfferCard } from "@/components/listings/offer/MyOfferCard";
24-
import { EditMyOfferModal } from "@/components/listings/offer/EditMyOfferModal";
25-
import { deleteMyOfferForListing } from "@/lib/actions";
13+
import type { Item, Sublet } from "@/lib/types";
2614

2715
interface Props {
2816
listing: Item | Sublet;
2917
listingPrice: number;
3018
listingOwnerLabel: string;
3119
priceLabel?: string;
3220
isOwner?: boolean;
33-
initialMyOffer?: Offer | null;
3421
}
3522

3623
type ModalState = "none" | "phone-input" | "verification" | "offer";
@@ -41,12 +28,10 @@ export const ListingActions = ({
4128
priceLabel,
4229
listingOwnerLabel,
4330
isOwner = false,
44-
initialMyOffer = null,
4531
}: Props) => {
4632
const [modalState, setModalState] = useState<ModalState>("none");
4733
const [pendingPhoneNumber, setPendingPhoneNumber] = useState<string>("");
4834
const [isChangingPhone, setIsChangingPhone] = useState<boolean>(false);
49-
const [isEditing, setIsEditing] = useState(false);
5035
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
5136

5237
const queryClient = useQueryClient();
@@ -57,35 +42,16 @@ export const ListingActions = ({
5742
enabled: !isOwner,
5843
});
5944

60-
const myOfferQuery = useQuery({
61-
queryKey: ["myOffer", listing.id],
62-
queryFn: () => getMyOfferForListing(listing.id),
63-
initialData: initialMyOffer,
64-
enabled: !isOwner,
65-
staleTime: Infinity,
66-
});
67-
68-
const myOffer = myOfferQuery.data ?? null;
69-
70-
const [isEditOfferOpen, setIsEditOfferOpen] = useState(false);
71-
const [isDeleteOfferOpen, setIsDeleteOfferOpen] = useState(false);
72-
73-
const deleteMyOfferMutation = useMutation({
74-
mutationFn: () => deleteMyOfferForListing(listing.id),
75-
onSuccess: () => {
76-
setIsDeleteOfferOpen(false);
77-
queryClient.invalidateQueries({ queryKey: ["myOffer", listing.id] });
78-
},
79-
});
80-
8145
if (isOwner) {
8246
const typeLabel = listing.listing_type === "sublet" ? "Sublet" : "Item";
47+
const editHref =
48+
listing.listing_type === "sublet" ? `/sublets/${listing.id}/edit` : `/items/${listing.id}/edit`;
8349

8450
return (
8551
<>
8652
<div className="flex items-center justify-end gap-2">
87-
<Button className="cursor-pointer" variant="outline" onClick={() => setIsEditing(true)}>
88-
Edit Listing
53+
<Button className="cursor-pointer" variant="outline" asChild>
54+
<Link href={editHref}>Edit Listing</Link>
8955
</Button>
9056
<Button
9157
className="cursor-pointer bg-red-500 text-white hover:bg-red-600"
@@ -94,12 +60,6 @@ export const ListingActions = ({
9460
Delete {typeLabel}
9561
</Button>
9662
</div>
97-
98-
<EditListing
99-
listing={listing}
100-
open={isEditing}
101-
onOpenChange={setIsEditing}
102-
/>
10363
<DeleteListing
10464
listing={listing}
10565
open={isDeleteOpen}
@@ -140,55 +100,6 @@ export const ListingActions = ({
140100
setModalState("phone-input");
141101
};
142102

143-
if (myOffer) {
144-
return (
145-
<>
146-
<MyOfferCard
147-
offer={myOffer}
148-
onEdit={() => setIsEditOfferOpen(true)}
149-
onDelete={() => setIsDeleteOfferOpen(true)}
150-
/>
151-
152-
<EditMyOfferModal
153-
isOpen={isEditOfferOpen}
154-
onClose={() => setIsEditOfferOpen(false)}
155-
offer={myOffer}
156-
onSaved={() => {
157-
queryClient.invalidateQueries({ queryKey: ["myOffer", listing.id] });
158-
setIsEditOfferOpen(false);
159-
}}
160-
/>
161-
162-
<Dialog open={isDeleteOfferOpen} onOpenChange={setIsDeleteOfferOpen}>
163-
<DialogContent>
164-
<DialogHeader>
165-
<DialogTitle>Delete Offer</DialogTitle>
166-
<DialogDescription>
167-
Are you sure you want to withdraw your offer?
168-
</DialogDescription>
169-
</DialogHeader>
170-
<DialogFooter>
171-
<Button
172-
className="cursor-pointer"
173-
variant="outline"
174-
onClick={() => setIsDeleteOfferOpen(false)}
175-
>
176-
Cancel
177-
</Button>
178-
<Button
179-
className="cursor-pointer bg-red-500 text-white hover:bg-red-600"
180-
onClick={() => deleteMyOfferMutation.mutate()}
181-
disabled={deleteMyOfferMutation.isPending}
182-
>
183-
{deleteMyOfferMutation.isPending ? "Withdrawing..." : "Withdraw"}
184-
</Button>
185-
</DialogFooter>
186-
</DialogContent>
187-
</Dialog>
188-
</>
189-
);
190-
}
191-
192103
return (
193104
<>
194105
<Button

frontend/components/listings/detail/ListingDetail.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { ListingImageGallery } from "@/components/listings/detail/ListingImageGa
66
import { ListingInfo } from "@/components/listings/detail/ListingInfo";
77
import { UserCard } from "@/components/listings/detail/UserCard";
88
import { ListingActions } from "@/components/listings/detail/ListingActions";
9-
import { OffersSection } from "@/components/listings/offer/OffersSection";
9+
import { OffersReceivedSection } from "@/components/listings/offer/OffersSection";
1010
import { BackButton } from "@/components/listings/detail/BackButton";
1111
import {
1212
addToUsersFavorites,
@@ -18,17 +18,17 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
1818
interface Props {
1919
listing: Item | Sublet;
2020
initialIsFavorited: boolean;
21-
offers: Offer[];
21+
offersReceived: Offer[];
2222
isOwner: boolean;
23-
initialMyOffer?: Offer | null;
23+
myOfferGiven?: Offer | null;
2424
}
2525

2626
export const ListingDetail = ({
2727
listing,
2828
initialIsFavorited,
29-
offers,
29+
offersReceived,
3030
isOwner,
31-
initialMyOffer = null,
31+
myOfferGiven = null,
3232
}: Props) => {
3333
const queryClient = useQueryClient();
3434

@@ -142,9 +142,13 @@ export const ListingDetail = ({
142142
priceLabel={priceLabel}
143143
listingOwnerLabel={listingOwnerLabel}
144144
isOwner={isOwner}
145-
initialMyOffer={initialMyOffer}
146145
/>
147-
{isOwner && <OffersSection offers={offers} />}
146+
<OffersReceivedSection
147+
isOwner={isOwner}
148+
offersReceived={offersReceived}
149+
myOfferGiven={myOfferGiven}
150+
listingId={listingData.id}
151+
/>
148152
</div>
149153
</div>
150154
</div>

0 commit comments

Comments
 (0)