Google maps integration

This commit is contained in:
jackiettran
2025-09-09 22:49:55 -04:00
parent 69bf64fe70
commit 1d7db138df
25 changed files with 3711 additions and 577 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";
import api, { addressAPI, userAPI, itemAPI } from "../services/api";
@@ -83,6 +83,9 @@ const CreateItem: React.FC = () => {
const [userAddresses, setUserAddresses] = useState<Address[]>([]);
const [selectedAddressId, setSelectedAddressId] = useState<string>("");
const [addressesLoading, setAddressesLoading] = useState(true);
// Reference to LocationForm geocoding function
const geocodeLocationRef = useRef<(() => Promise<boolean>) | null>(null);
useEffect(() => {
fetchUserAddresses();
@@ -150,6 +153,15 @@ const CreateItem: React.FC = () => {
setLoading(true);
setError("");
// Try to geocode the address before submitting
if (geocodeLocationRef.current) {
try {
await geocodeLocationRef.current();
} catch (error) {
console.warn('Geocoding failed, creating item without coordinates:', error);
}
}
try {
// For now, we'll store image URLs as base64 strings
// In production, you'd upload to a service like S3
@@ -253,6 +265,14 @@ const CreateItem: React.FC = () => {
}
};
const handleCoordinatesChange = (latitude: number, longitude: number) => {
setFormData((prev) => ({
...prev,
latitude,
longitude,
}));
};
const handleAddressSelect = (addressId: string) => {
if (addressId === "new") {
// Clear form for new address entry
@@ -379,6 +399,10 @@ const CreateItem: React.FC = () => {
onChange={handleChange}
onAddressSelect={handleAddressSelect}
formatAddressDisplay={formatAddressDisplay}
onCoordinatesChange={handleCoordinatesChange}
onGeocodeRef={(geocodeFunction) => {
geocodeLocationRef.current = geocodeFunction;
}}
/>
<DeliveryOptions

View File

@@ -2,7 +2,6 @@ import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";
import { itemRequestAPI } from "../services/api";
import AddressAutocomplete from "../components/AddressAutocomplete";
const CreateItemRequest: React.FC = () => {
const navigate = useNavigate();
@@ -42,18 +41,6 @@ const CreateItemRequest: React.FC = () => {
}
};
const handleAddressChange = (value: string, lat?: number, lon?: number) => {
setFormData((prev) => ({
...prev,
address1: value,
latitude: lat,
longitude: lon,
city: prev.city,
state: prev.state,
zipCode: prev.zipCode,
country: prev.country,
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -186,10 +173,14 @@ const CreateItemRequest: React.FC = () => {
</div>
<div className="mb-3">
<label className="form-label">Address</label>
<AddressAutocomplete
<label htmlFor="address1" className="form-label">Address</label>
<input
type="text"
className="form-control"
id="address1"
name="address1"
value={formData.address1}
onChange={handleAddressChange}
onChange={handleChange}
placeholder="Enter your address or area"
/>
</div>

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { Item, Rental, Address } from "../types";
import { useAuth } from "../contexts/AuthContext";
@@ -58,6 +58,9 @@ const EditItem: React.FC = () => {
const [userAddresses, setUserAddresses] = useState<Address[]>([]);
const [selectedAddressId, setSelectedAddressId] = useState<string>("");
const [addressesLoading, setAddressesLoading] = useState(true);
// Reference to LocationForm geocoding function
const geocodeLocationRef = useRef<(() => Promise<boolean>) | null>(null);
const [formData, setFormData] = useState<ItemFormData>({
name: "",
description: "",
@@ -204,10 +207,27 @@ const EditItem: React.FC = () => {
}
};
const handleCoordinatesChange = (latitude: number, longitude: number) => {
setFormData((prev) => ({
...prev,
latitude,
longitude,
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
// Try to geocode the address before submitting
if (geocodeLocationRef.current) {
try {
await geocodeLocationRef.current();
} catch (error) {
console.warn('Geocoding failed, updating item without coordinates:', error);
}
}
try {
// Use existing image previews (which includes both old and new images)
const imageUrls = imagePreviews;
@@ -412,6 +432,10 @@ const EditItem: React.FC = () => {
onChange={handleChange}
onAddressSelect={handleAddressSelect}
formatAddressDisplay={formatAddressDisplay}
onCoordinatesChange={handleCoordinatesChange}
onGeocodeRef={(geocodeFunction) => {
geocodeLocationRef.current = geocodeFunction;
}}
/>
<DeliveryOptions

View File

@@ -3,7 +3,7 @@ import { useParams, useNavigate } from "react-router-dom";
import { Item, Rental } from "../types";
import { useAuth } from "../contexts/AuthContext";
import { itemAPI, rentalAPI } from "../services/api";
import LocationMap from "../components/LocationMap";
import GoogleMapWithRadius from "../components/GoogleMapWithRadius";
import ItemReviews from "../components/ItemReviews";
const ItemDetail: React.FC = () => {
@@ -357,11 +357,9 @@ const ItemDetail: React.FC = () => {
</div>
{/* Map */}
<LocationMap
<GoogleMapWithRadius
latitude={item.latitude}
longitude={item.longitude}
location={item.location}
itemName={item.name}
/>
<ItemReviews itemId={item.id} />

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useCallback } from "react";
import { useAuth } from "../contexts/AuthContext";
import { userAPI, itemAPI, rentalAPI, addressAPI } from "../services/api";
import { User, Item, Rental, Address } from "../types";
@@ -6,8 +6,11 @@ import { getImageUrl } from "../utils/imageUrl";
import AvailabilitySettings from "../components/AvailabilitySettings";
import ReviewItemModal from "../components/ReviewModal";
import ReviewRenterModal from "../components/ReviewRenterModal";
import StarRating from "../components/StarRating";
import ReviewDetailsModal from "../components/ReviewDetailsModal";
import {
geocodingService,
AddressComponents,
} from "../services/geocodingService";
const Profile: React.FC = () => {
const { user, updateUser, logout } = useAuth();
@@ -62,7 +65,14 @@ const Profile: React.FC = () => {
state: "",
zipCode: "",
country: "US",
latitude: undefined as number | undefined,
longitude: undefined as number | undefined,
});
const [addressGeocoding, setAddressGeocoding] = useState(false);
const [addressGeocodeError, setAddressGeocodeError] = useState<string | null>(
null
);
const [addressGeocodeSuccess, setAddressGeocodeSuccess] = useState(false);
// Rental history state
const [pastRenterRentals, setPastRenterRentals] = useState<Rental[]>([]);
@@ -404,6 +414,8 @@ const Profile: React.FC = () => {
state: "",
zipCode: "",
country: "US",
latitude: undefined,
longitude: undefined,
});
setEditingAddressId(null);
setShowAddressForm(true);
@@ -417,6 +429,8 @@ const Profile: React.FC = () => {
state: address.state,
zipCode: address.zipCode,
country: address.country,
latitude: address.latitude,
longitude: address.longitude,
});
setEditingAddressId(address.id);
setShowAddressForm(true);
@@ -429,8 +443,59 @@ const Profile: React.FC = () => {
setAddressFormData((prev) => ({ ...prev, [name]: value }));
};
// Geocoding function for address form
const geocodeAddressForm = useCallback(
async (addressData: typeof addressFormData) => {
if (
!geocodingService.isAddressComplete(addressData as AddressComponents)
) {
return;
}
setAddressGeocoding(true);
setAddressGeocodeError(null);
setAddressGeocodeSuccess(false);
try {
const result = await geocodingService.geocodeAddress(
addressData as AddressComponents
);
if ("error" in result) {
setAddressGeocodeError(result.details || result.error);
} else {
setAddressGeocodeSuccess(true);
setAddressFormData((prev) => ({
...prev,
latitude: result.latitude,
longitude: result.longitude,
}));
// Clear success message after 3 seconds
setTimeout(() => setAddressGeocodeSuccess(false), 3000);
}
} catch (error) {
setAddressGeocodeError("Failed to geocode address");
} finally {
setAddressGeocoding(false);
}
},
[]
);
const handleSaveAddress = async (e: React.FormEvent) => {
e.preventDefault();
// Try to geocode the address before saving
try {
await geocodeAddressForm(addressFormData);
} catch (error) {
// Geocoding failed, but we'll continue with saving
console.warn(
"Geocoding failed, saving address without coordinates:",
error
);
}
try {
if (editingAddressId) {
// Update existing address
@@ -469,7 +534,12 @@ const Profile: React.FC = () => {
state: "",
zipCode: "",
country: "US",
latitude: undefined,
longitude: undefined,
});
setAddressGeocoding(false);
setAddressGeocodeError(null);
setAddressGeocodeSuccess(false);
};
const usStates = [

View File

@@ -129,7 +129,7 @@ const PublicProfile: React.FC = () => {
)}
<div className="card-body">
<h6 className="card-title">{item.name}</h6>
<p className="card-text text-muted small">{item.location}</p>
<p className="card-text text-muted small">{item.city && item.state ? `${item.city}, ${item.state}` : ''}</p>
<div>
{item.pricePerDay && (
<span className="badge bg-primary">${item.pricePerDay}/day</span>

View File

@@ -280,7 +280,7 @@ const RentItem: React.FC = () => {
<p className="text-muted small">
{item.city && item.state
? `${item.city}, ${item.state}`
: item.location}
: ''}
</p>
<hr />