Skip to content

Commit 0c0dde5

Browse files
authored
Merge pull request #61 from pennlabs/frontend/sublet-map
Frontend/sublet map
2 parents 4657130 + 8935fab commit 0c0dde5

9 files changed

Lines changed: 156 additions & 9 deletions

File tree

backend/market/models.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import hashlib
2+
import hmac
23
import math
34

5+
from django.conf import settings
46
from django.contrib.auth.models import AbstractUser
57
from django.core.exceptions import ValidationError
68
from django.core.validators import MinValueValidator
@@ -146,7 +148,11 @@ def _calculate_approximate_location(self, latitude, longitude):
146148

147149
lat_str = f"{float(latitude):.9f}"
148150
lon_str = f"{float(longitude):.9f}"
149-
seed = hashlib.md5(f"{lat_str}{lon_str}".encode()).hexdigest()
151+
seed = hmac.new(
152+
settings.SECRET_KEY.encode(),
153+
f"{lat_str}{lon_str}".encode(),
154+
hashlib.sha256,
155+
).hexdigest()
150156

151157
offset_factor = int(seed[:8], 16) / 0xFFFFFFFF
152158

backend/market/serializers.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,26 @@ class SubletDataSerializer(ModelSerializer):
104104

105105
class Meta:
106106
model = Sublet
107-
fields = ["street_address", "beds", "baths", "start_date", "end_date",
108-
"latitude", "longitude"]
107+
fields = [
108+
"street_address",
109+
"beds",
110+
"baths",
111+
"start_date",
112+
"end_date",
113+
"latitude",
114+
"longitude",
115+
]
109116

110117
def get_latitude(self, obj):
111-
if obj.approximate_location is not None:
112-
return float(obj.approximate_location[0])
118+
approx_lat, _ = obj.approximate_location
119+
if approx_lat is not None:
120+
return float(approx_lat)
113121
return None
114122

115123
def get_longitude(self, obj):
116-
if obj.approximate_location is not None:
117-
return float(obj.approximate_location[1])
124+
_, approx_lon = obj.approximate_location
125+
if approx_lon is not None:
126+
return float(approx_lon)
118127
return None
119128

120129
# Unified serializer for all listing types (Items and Sublets); used for CRUD operations

frontend/components/listings/detail/ListingDetail.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ListingImageGallery } from "@/components/listings/detail/ListingImageGa
99
import { ListingInfo } from "@/components/listings/detail/ListingInfo";
1010
import { UserCard } from "@/components/listings/detail/UserCard";
1111
import { BackButton } from "@/components/listings/detail/BackButton";
12+
import { SubletMap } from "@/components/listings/detail/SubletMap";
1213

1314
interface Props {
1415
listing: Item | Sublet;
@@ -55,6 +56,11 @@ export const ListingDetail = ({ listing, initialIsFavorited }: Props) => {
5556
toggleFavoriteMutation.mutate(!isFavorited);
5657
};
5758

59+
const subletCoords =
60+
listingType === "sublet" ? listing.additional_data : null;
61+
const hasLocation =
62+
subletCoords?.latitude != null && subletCoords?.longitude != null;
63+
5864
return (
5965
<div className="mx-auto flex w-full max-w-[96rem] flex-col p-8 px-4 sm:px-12">
6066
<div className="mb-4 flex items-center justify-between">
@@ -83,6 +89,20 @@ export const ListingDetail = ({ listing, initialIsFavorited }: Props) => {
8389
{...listing.additional_data}
8490
/>
8591
<UserCard user={listing.seller} label={listingOwnerLabel} />
92+
{hasLocation && (
93+
<div className="space-y-3">
94+
<div>
95+
<h2 className="text-lg font-semibold">{"Where you'll be living"}</h2>
96+
<p className="text-sm text-gray-500">
97+
Approximate location shown. The exact location will be shared once you connect with the owner.
98+
</p>
99+
</div>
100+
<SubletMap
101+
latitude={subletCoords.latitude!}
102+
longitude={subletCoords.longitude!}
103+
/>
104+
</div>
105+
)}
86106
<ListingActions
87107
listingId={listing.id}
88108
listingPrice={listing.price}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"use client";
2+
3+
import dynamic from "next/dynamic";
4+
5+
interface Props {
6+
latitude: number;
7+
longitude: number;
8+
}
9+
10+
const LazyMap = dynamic(
11+
() =>
12+
import("@/components/listings/detail/SubletMapContent").then((m) => m.SubletMapContent),
13+
{ ssr: false },
14+
);
15+
16+
export const SubletMap = ({ latitude, longitude }: Props) => {
17+
return <LazyMap latitude={latitude} longitude={longitude} />;
18+
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"use client";
2+
3+
import "leaflet/dist/leaflet.css";
4+
import { MapContainer, TileLayer, Circle } from "react-leaflet";
5+
6+
interface Props {
7+
latitude: number;
8+
longitude: number;
9+
}
10+
11+
const CIRCLE_RADIUS_METERS = 200;
12+
13+
export const SubletMapContent = ({ latitude, longitude }: Props) => {
14+
return (
15+
<MapContainer
16+
center={[latitude, longitude]}
17+
zoom={15}
18+
scrollWheelZoom={false}
19+
className="z-0 h-72 w-full rounded-lg sm:h-80"
20+
>
21+
<TileLayer
22+
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
23+
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
24+
/>
25+
<Circle
26+
center={[latitude, longitude]}
27+
radius={CIRCLE_RADIUS_METERS}
28+
pathOptions={{
29+
color: "#3b82f6",
30+
fillColor: "#3b82f6",
31+
fillOpacity: 0.2,
32+
weight: 2,
33+
}}
34+
/>
35+
</MapContainer>
36+
);
37+
};

frontend/lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const BASE_URL =
1111

1212
export const API_BASE_URL =
1313
process.env.NODE_ENV === "production" ? "REPLACE WITH PROD API URL" : "http://backend:8000"; // can't be localhost because server fetch happens in container
14+
1415

1516
export const PLATFORM_URL = process.env.PLATFORM_URL;
1617
export const CLIENT_ID = process.env.CLIENT_ID;

frontend/lib/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ export type ItemAdditionalData = {
4040

4141
export type SubletAdditionalData = {
4242
street_address: string;
43-
latitude: number;
44-
longitude: number;
4543
beds: number;
4644
baths: number;
4745
start_date: string;
4846
end_date: string;
47+
latitude: number;
48+
longitude: number;
4949
};
5050

5151
// ------------------------------------------------------------

frontend/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"clsx": "^2.1.1",
2929
"date-fns": "^4.1.0",
3030
"jose": "^3.20.4",
31+
"leaflet": "^1.9.4",
3132
"lucide-react": "^0.546.0",
3233
"next": "15.5.4",
3334
"next-themes": "^0.4.6",
@@ -36,13 +37,15 @@
3637
"react-dom": "19.1.0",
3738
"react-hook-form": "^7.69.0",
3839
"react-intersection-observer": "^9.16.0",
40+
"react-leaflet": "^5.0.0",
3941
"sonner": "^2.0.7",
4042
"tailwind-merge": "^3.3.1",
4143
"zod": "^4.2.1"
4244
},
4345
"devDependencies": {
4446
"@eslint/eslintrc": "^3",
4547
"@tailwindcss/postcss": "^4",
48+
"@types/leaflet": "^1.9.21",
4649
"@types/node": "^20",
4750
"@types/react": "^19",
4851
"@types/react-dom": "^19",

frontend/pnpm-lock.yaml

Lines changed: 53 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)