Skip to content

Commit fd9c0e7

Browse files
committed
Adding seller's view
1 parent f585ad7 commit fd9c0e7

9 files changed

Lines changed: 582 additions & 31 deletions

File tree

backend/market/serializers.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,14 @@ class OfferSerializer(ModelSerializer):
4646

4747
class Meta:
4848
model = Offer
49-
fields = ["id", "user", "listing", "offered_price", "message", "created_at"]
49+
fields = [
50+
"id",
51+
"user",
52+
"listing",
53+
"offered_price",
54+
"message",
55+
"created_at",
56+
]
5057
read_only_fields = ["id", "created_at", "user"]
5158

5259
def create(self, validated_data):

backend/market/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
OffersReceived,
1212
Tags,
1313
UserFavorites,
14+
accept_offer,
1415
get_current_user,
1516
get_phone_status,
17+
reject_offer,
1618
send_verification_code,
1719
verify_phone_code,
1820
)
@@ -49,6 +51,9 @@
4951
"listings/<listing_id>/offers/",
5052
Offers.as_view({"get": "list", "post": "create", "delete": "destroy"}),
5153
),
54+
# Offer accept / reject
55+
path("offers/<int:offer_id>/accept/", accept_offer, name="offer-accept"),
56+
path("offers/<int:offer_id>/reject/", reject_offer, name="offer-reject"),
5257
# Image Creation
5358
path("listings/<listing_id>/images/", CreateImages.as_view()),
5459
# Image Deletion

backend/market/views.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,28 @@ def list(self, request, *args, **kwargs):
342342
return super().list(request, *args, **kwargs)
343343

344344

345+
@api_view(["POST"])
346+
@permission_classes([IsAuthenticated])
347+
def accept_offer(request, offer_id):
348+
offer = get_object_or_404(Offer, pk=offer_id)
349+
if offer.listing.seller != request.user:
350+
raise exceptions.PermissionDenied("Only the listing owner can accept offers.")
351+
offer.status = Offer.Status.ACCEPTED
352+
offer.save(update_fields=["status"])
353+
return Response(OfferSerializer(offer).data)
354+
355+
356+
@api_view(["POST"])
357+
@permission_classes([IsAuthenticated])
358+
def reject_offer(request, offer_id):
359+
offer = get_object_or_404(Offer, pk=offer_id)
360+
if offer.listing.seller != request.user:
361+
raise exceptions.PermissionDenied("Only the listing owner can reject offers.")
362+
offer.status = Offer.Status.REJECTED
363+
offer.save(update_fields=["status"])
364+
return Response(OfferSerializer(offer).data)
365+
366+
345367
@api_view(["POST"])
346368
@permission_classes([IsAuthenticated])
347369
def send_verification_code(request):

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
import { ListingDetail } from "@/components/listings/detail/ListingDetail";
2-
import { getListingOrNotFound } from "@/lib/actions";
2+
import {
3+
getCurrentUser,
4+
getListingOrNotFound,
5+
getOffersMade,
6+
getOffersReceived,
7+
} from "@/lib/actions";
38

49
export default async function ItemPage({ params }: { params: Promise<{ id: string }> }) {
510
const { id } = await params;
6-
const item = await getListingOrNotFound(id);
11+
const [item, currentUser] = await Promise.all([getListingOrNotFound(id), getCurrentUser()]);
12+
const isOwner = currentUser?.id === item.seller.id;
13+
const offersResponse = await (isOwner ? getOffersReceived() : getOffersMade());
14+
const offers = offersResponse?.results?.filter((offer) => offer.listing === item.id) ?? [];
715

8-
return <ListingDetail listing={item} initialIsFavorited={item.is_favorited ?? false} />;
16+
return (
17+
<ListingDetail
18+
listing={item}
19+
initialIsFavorited={item.is_favorited ?? false}
20+
offers={offers}
21+
offersMode={isOwner ? "received" : "made"}
22+
canEdit={isOwner}
23+
/>
24+
);
925
}

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
11
import { ListingDetail } from "@/components/listings/detail/ListingDetail";
2-
import { getListingOrNotFound } from "@/lib/actions";
2+
import {
3+
getCurrentUser,
4+
getListingOrNotFound,
5+
getOffersMade,
6+
getOffersReceived,
7+
} from "@/lib/actions";
38

49
export default async function SubletPage({ params }: { params: Promise<{ id: string }> }) {
510
const { id } = await params;
6-
const sublet = await getListingOrNotFound(id);
11+
const [sublet, currentUser] = await Promise.all([
12+
getListingOrNotFound(id),
13+
getCurrentUser().catch(() => null),
14+
]);
15+
const isOwner = currentUser?.id === sublet.seller.id;
16+
const offersResponse = await (isOwner ? getOffersReceived() : getOffersMade()).catch(() => null);
17+
const offers = offersResponse?.results?.filter((offer) => offer.listing === sublet.id) ?? [];
718

8-
return <ListingDetail listing={sublet} initialIsFavorited={sublet.is_favorited ?? false} />;
19+
return (
20+
<ListingDetail
21+
listing={sublet}
22+
initialIsFavorited={sublet.is_favorited ?? false}
23+
offers={offers}
24+
offersMode={isOwner ? "received" : "made"}
25+
canEdit={isOwner}
26+
/>
27+
);
928
}

frontend/components/listings/detail/ListingActions.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface Props {
1414
listingPrice: number;
1515
listingOwnerLabel: string;
1616
priceLabel?: string;
17+
canEdit?: boolean;
1718
}
1819

1920
type ModalState = "none" | "phone-input" | "verification" | "offer";
@@ -23,6 +24,7 @@ export const ListingActions = ({
2324
listingPrice,
2425
priceLabel,
2526
listingOwnerLabel,
27+
canEdit = false,
2628
}: Props) => {
2729
const [modalState, setModalState] = useState<ModalState>("none");
2830
const [pendingPhoneNumber, setPendingPhoneNumber] = useState<string>("");
@@ -35,6 +37,10 @@ export const ListingActions = ({
3537
queryFn: getPhoneStatus,
3638
});
3739

40+
if (canEdit) {
41+
return null;
42+
}
43+
3844
const handleMakeOfferClick = () => {
3945
if (!phoneStatus) return;
4046

0 commit comments

Comments
 (0)