image optimization. Image resizing client side, index added to db, pagination
This commit is contained in:
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { forumAPI, addressAPI } from "../services/api";
|
||||
import { uploadFiles, getPublicImageUrl } from "../services/uploadService";
|
||||
import { uploadImagesWithVariants, getImageUrl } from "../services/uploadService";
|
||||
import TagInput from "../components/TagInput";
|
||||
import ForumImageUpload from "../components/ForumImageUpload";
|
||||
import VerificationCodeModal from "../components/VerificationCodeModal";
|
||||
@@ -73,7 +73,7 @@ const CreateForumPost: React.FC = () => {
|
||||
if (post.imageFilenames && post.imageFilenames.length > 0) {
|
||||
setExistingImageKeys(post.imageFilenames);
|
||||
setImagePreviews(
|
||||
post.imageFilenames.map((key: string) => getPublicImageUrl(key))
|
||||
post.imageFilenames.map((key: string) => getImageUrl(key, 'thumbnail'))
|
||||
);
|
||||
}
|
||||
} catch (err: any) {
|
||||
@@ -199,8 +199,8 @@ const CreateForumPost: React.FC = () => {
|
||||
// Upload images to S3 first (if any)
|
||||
let imageFilenames: string[] = [];
|
||||
if (imageFiles.length > 0) {
|
||||
const uploadResults = await uploadFiles("forum", imageFiles);
|
||||
imageFilenames = uploadResults.map((result) => result.key);
|
||||
const uploadResults = await uploadImagesWithVariants("forum", imageFiles);
|
||||
imageFilenames = uploadResults.map((result) => result.baseKey);
|
||||
}
|
||||
|
||||
// Build the post data
|
||||
|
||||
@@ -2,7 +2,7 @@ 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";
|
||||
import { uploadFiles } from "../services/uploadService";
|
||||
import { uploadImagesWithVariants } from "../services/uploadService";
|
||||
import AvailabilitySettings from "../components/AvailabilitySettings";
|
||||
import ImageUpload from "../components/ImageUpload";
|
||||
import ItemInformation from "../components/ItemInformation";
|
||||
@@ -217,11 +217,11 @@ const CreateItem: React.FC = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Upload images to S3 first
|
||||
// Upload images to S3 first (with resizing to thumbnail, medium, original)
|
||||
let imageFilenames: string[] = [];
|
||||
if (imageFiles.length > 0) {
|
||||
const uploadResults = await uploadFiles("item", imageFiles);
|
||||
imageFilenames = uploadResults.map((result) => result.key);
|
||||
const uploadResults = await uploadImagesWithVariants("item", imageFiles);
|
||||
imageFilenames = uploadResults.map((result) => result.baseKey);
|
||||
}
|
||||
|
||||
// Construct location from address components
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useParams, useNavigate } from "react-router-dom";
|
||||
import { Item, Rental, Address } from "../types";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { itemAPI, rentalAPI, addressAPI, userAPI } from "../services/api";
|
||||
import { uploadFiles, getPublicImageUrl } from "../services/uploadService";
|
||||
import { uploadImagesWithVariants, getImageUrl } from "../services/uploadService";
|
||||
import AvailabilitySettings from "../components/AvailabilitySettings";
|
||||
import ImageUpload from "../components/ImageUpload";
|
||||
import ItemInformation from "../components/ItemInformation";
|
||||
@@ -161,8 +161,8 @@ const EditItem: React.FC = () => {
|
||||
// Set existing images - store S3 keys and generate preview URLs
|
||||
if (item.imageFilenames && item.imageFilenames.length > 0) {
|
||||
setExistingImageKeys(item.imageFilenames);
|
||||
// Generate preview URLs from S3 keys
|
||||
setImagePreviews(item.imageFilenames.map((key: string) => getPublicImageUrl(key)));
|
||||
// Generate preview URLs from S3 keys (use thumbnail for previews)
|
||||
setImagePreviews(item.imageFilenames.map((key: string) => getImageUrl(key, 'thumbnail')));
|
||||
}
|
||||
|
||||
// Determine which pricing unit to select based on existing data
|
||||
@@ -315,11 +315,11 @@ const EditItem: React.FC = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Upload new images to S3 and get their keys
|
||||
// Upload new images to S3 and get their keys (with resizing)
|
||||
let newImageKeys: string[] = [];
|
||||
if (imageFiles.length > 0) {
|
||||
const uploadResults = await uploadFiles("item", imageFiles);
|
||||
newImageKeys = uploadResults.map((result) => result.key);
|
||||
const uploadResults = await uploadImagesWithVariants("item", imageFiles);
|
||||
newImageKeys = uploadResults.map((result) => result.baseKey);
|
||||
}
|
||||
|
||||
// Combine existing S3 keys with newly uploaded keys
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate, Link, useSearchParams } from 'react-router-dom';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { forumAPI } from '../services/api';
|
||||
import { uploadFiles, getPublicImageUrl } from '../services/uploadService';
|
||||
import { uploadImagesWithVariants, getImageUrl } from '../services/uploadService';
|
||||
import { ForumPost, ForumComment } from '../types';
|
||||
import CategoryBadge from '../components/CategoryBadge';
|
||||
import PostStatusBadge from '../components/PostStatusBadge';
|
||||
@@ -60,8 +60,8 @@ const ForumPostDetail: React.FC = () => {
|
||||
// Upload images to S3 first (if any)
|
||||
let imageFilenames: string[] = [];
|
||||
if (images.length > 0) {
|
||||
const uploadResults = await uploadFiles("forum", images);
|
||||
imageFilenames = uploadResults.map((result) => result.key);
|
||||
const uploadResults = await uploadImagesWithVariants("forum", images);
|
||||
imageFilenames = uploadResults.map((result) => result.baseKey);
|
||||
}
|
||||
|
||||
await forumAPI.createComment(id!, {
|
||||
@@ -92,8 +92,8 @@ const ForumPostDetail: React.FC = () => {
|
||||
// Upload images to S3 first (if any)
|
||||
let imageFilenames: string[] = [];
|
||||
if (images.length > 0) {
|
||||
const uploadResults = await uploadFiles("forum", images);
|
||||
imageFilenames = uploadResults.map((result) => result.key);
|
||||
const uploadResults = await uploadImagesWithVariants("forum", images);
|
||||
imageFilenames = uploadResults.map((result) => result.baseKey);
|
||||
}
|
||||
|
||||
await forumAPI.createComment(id!, {
|
||||
@@ -130,8 +130,8 @@ const ForumPostDetail: React.FC = () => {
|
||||
// Upload new images to S3
|
||||
let newImageFilenames: string[] = [];
|
||||
if (newImageFiles.length > 0) {
|
||||
const uploadResults = await uploadFiles("forum", newImageFiles);
|
||||
newImageFilenames = uploadResults.map((result) => result.key);
|
||||
const uploadResults = await uploadImagesWithVariants("forum", newImageFiles);
|
||||
newImageFilenames = uploadResults.map((result) => result.baseKey);
|
||||
}
|
||||
|
||||
// Combine existing and new image keys
|
||||
@@ -400,11 +400,18 @@ const ForumPostDetail: React.FC = () => {
|
||||
{post.imageFilenames.map((image, index) => (
|
||||
<div key={index} className="col-6 col-md-4">
|
||||
<img
|
||||
src={getPublicImageUrl(image)}
|
||||
src={getImageUrl(image, 'medium')}
|
||||
alt={`Post image`}
|
||||
className="img-fluid rounded"
|
||||
style={{ width: '100%', maxHeight: '400px', objectFit: 'contain', cursor: 'pointer' }}
|
||||
onClick={() => window.open(getPublicImageUrl(image), '_blank')}
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget;
|
||||
if (!target.dataset.fallback) {
|
||||
target.dataset.fallback = 'true';
|
||||
target.src = getImageUrl(image, 'original');
|
||||
}
|
||||
}}
|
||||
onClick={() => window.open(getImageUrl(image, 'original'), '_blank')}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -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 { getPublicImageUrl } from "../services/uploadService";
|
||||
import { getImageUrl } from "../services/uploadService";
|
||||
import GoogleMapWithRadius from "../components/GoogleMapWithRadius";
|
||||
import ItemReviews from "../components/ItemReviews";
|
||||
import ConfirmationModal from "../components/ConfirmationModal";
|
||||
@@ -419,9 +419,17 @@ const ItemDetail: React.FC = () => {
|
||||
{item.imageFilenames.length > 0 ? (
|
||||
<div className="mb-4">
|
||||
<img
|
||||
src={getPublicImageUrl(item.imageFilenames[selectedImage])}
|
||||
src={getImageUrl(item.imageFilenames[selectedImage], 'medium')}
|
||||
alt={item.name}
|
||||
className="img-fluid rounded mb-3"
|
||||
loading="lazy"
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget;
|
||||
if (!target.dataset.fallback) {
|
||||
target.dataset.fallback = 'true';
|
||||
target.src = getImageUrl(item.imageFilenames[selectedImage], 'original');
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
maxHeight: "500px",
|
||||
@@ -434,13 +442,21 @@ const ItemDetail: React.FC = () => {
|
||||
{item.imageFilenames.map((image, index) => (
|
||||
<img
|
||||
key={index}
|
||||
src={getPublicImageUrl(image)}
|
||||
src={getImageUrl(image, 'thumbnail')}
|
||||
alt={`${item.name} ${index + 1}`}
|
||||
className={`rounded cursor-pointer ${
|
||||
selectedImage === index
|
||||
? "border border-primary"
|
||||
: ""
|
||||
}`}
|
||||
loading="lazy"
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget;
|
||||
if (!target.dataset.fallback) {
|
||||
target.dataset.fallback = 'true';
|
||||
target.src = getImageUrl(image, 'original');
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
width: "80px",
|
||||
height: "80px",
|
||||
|
||||
@@ -7,8 +7,10 @@ import SearchResultsMap from "../components/SearchResultsMap";
|
||||
import FilterPanel from "../components/FilterPanel";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
|
||||
const ITEMS_PER_PAGE = 20;
|
||||
|
||||
const ItemList: React.FC = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const [items, setItems] = useState<Item[]>([]);
|
||||
@@ -19,6 +21,9 @@ const ItemList: React.FC = () => {
|
||||
const [locationName, setLocationName] = useState(searchParams.get("locationName") || "");
|
||||
const locationCheckDone = useRef(false);
|
||||
const filterButtonRef = useRef<HTMLDivElement>(null);
|
||||
const [currentPage, setCurrentPage] = useState(parseInt(searchParams.get("page") || "1"));
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [totalItems, setTotalItems] = useState(0);
|
||||
const [filters, setFilters] = useState({
|
||||
search: searchParams.get("search") || "",
|
||||
city: searchParams.get("city") || "",
|
||||
@@ -58,7 +63,12 @@ const ItemList: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
fetchItems();
|
||||
}, [filters]);
|
||||
}, [filters, currentPage]);
|
||||
|
||||
// Reset to page 1 when filters change
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [filters.search, filters.city, filters.zipCode, filters.lat, filters.lng, filters.radius]);
|
||||
|
||||
// Update filters when URL params change (e.g., from navbar search)
|
||||
useEffect(() => {
|
||||
@@ -106,13 +116,15 @@ const ItemList: React.FC = () => {
|
||||
const fetchItems = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const params = {
|
||||
const params: Record<string, string | number> = {
|
||||
...filters,
|
||||
page: currentPage,
|
||||
limit: ITEMS_PER_PAGE,
|
||||
};
|
||||
// Remove empty filters
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (!params[key as keyof typeof params]) {
|
||||
delete params[key as keyof typeof params];
|
||||
if (!params[key]) {
|
||||
delete params[key];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -122,6 +134,8 @@ const ItemList: React.FC = () => {
|
||||
// Filter only available items
|
||||
const availableItems = allItems.filter((item: Item) => item.isAvailable);
|
||||
setItems(availableItems);
|
||||
setTotalPages(response.data.totalPages || 1);
|
||||
setTotalItems(response.data.totalItems || availableItems.length);
|
||||
} catch (err: any) {
|
||||
console.error("Error fetching items:", err);
|
||||
console.error("Error response:", err.response);
|
||||
@@ -137,6 +151,20 @@ const ItemList: React.FC = () => {
|
||||
navigate(`/items/${item.id}`);
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
// Update URL with page parameter
|
||||
const params = new URLSearchParams(searchParams);
|
||||
if (page === 1) {
|
||||
params.delete("page");
|
||||
} else {
|
||||
params.set("page", page.toString());
|
||||
}
|
||||
navigate(`/items?${params.toString()}`, { replace: true });
|
||||
// Scroll to top
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
};
|
||||
|
||||
const getSearchLocationString = () => {
|
||||
if (filters.lat && filters.lng) {
|
||||
// When using coordinates, return them as a string for the map
|
||||
@@ -174,7 +202,10 @@ const ItemList: React.FC = () => {
|
||||
<div className="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-4 gap-3">
|
||||
<div>
|
||||
<h1 className="mb-1">Browse Items</h1>
|
||||
<span className="text-muted">{items.length} items found</span>
|
||||
<span className="text-muted">
|
||||
{totalItems} items found
|
||||
{totalPages > 1 && ` (page ${currentPage} of ${totalPages})`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
@@ -240,13 +271,108 @@ const ItemList: React.FC = () => {
|
||||
</p>
|
||||
</div>
|
||||
) : viewMode === 'list' ? (
|
||||
<div className="row">
|
||||
{items.map((item) => (
|
||||
<div key={item.id} className="col-md-6 col-lg-4 col-xl-3 mb-4">
|
||||
<ItemCard item={item} variant="standard" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<>
|
||||
<div className="row">
|
||||
{items.map((item) => (
|
||||
<div key={item.id} className="col-md-6 col-lg-4 col-xl-3 mb-4">
|
||||
<ItemCard item={item} variant="standard" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
{totalPages > 1 && (
|
||||
<nav aria-label="Item list pagination" className="mt-4">
|
||||
<ul className="pagination justify-content-center">
|
||||
<li className={`page-item ${currentPage === 1 ? 'disabled' : ''}`}>
|
||||
<button
|
||||
className="page-link"
|
||||
onClick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
aria-label="Previous page"
|
||||
>
|
||||
<i className="bi bi-chevron-left"></i>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
{/* Page numbers */}
|
||||
{(() => {
|
||||
const pages = [];
|
||||
const maxVisiblePages = 5;
|
||||
let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
|
||||
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
|
||||
|
||||
// Adjust start if we're near the end
|
||||
if (endPage - startPage + 1 < maxVisiblePages) {
|
||||
startPage = Math.max(1, endPage - maxVisiblePages + 1);
|
||||
}
|
||||
|
||||
// First page + ellipsis
|
||||
if (startPage > 1) {
|
||||
pages.push(
|
||||
<li key={1} className="page-item">
|
||||
<button className="page-link" onClick={() => handlePageChange(1)}>1</button>
|
||||
</li>
|
||||
);
|
||||
if (startPage > 2) {
|
||||
pages.push(
|
||||
<li key="start-ellipsis" className="page-item disabled">
|
||||
<span className="page-link">...</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Page numbers
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
pages.push(
|
||||
<li key={i} className={`page-item ${currentPage === i ? 'active' : ''}`}>
|
||||
<button
|
||||
className="page-link"
|
||||
onClick={() => handlePageChange(i)}
|
||||
aria-current={currentPage === i ? 'page' : undefined}
|
||||
>
|
||||
{i}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
// Last page + ellipsis
|
||||
if (endPage < totalPages) {
|
||||
if (endPage < totalPages - 1) {
|
||||
pages.push(
|
||||
<li key="end-ellipsis" className="page-item disabled">
|
||||
<span className="page-link">...</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
pages.push(
|
||||
<li key={totalPages} className="page-item">
|
||||
<button className="page-link" onClick={() => handlePageChange(totalPages)}>
|
||||
{totalPages}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return pages;
|
||||
})()}
|
||||
|
||||
<li className={`page-item ${currentPage === totalPages ? 'disabled' : ''}`}>
|
||||
<button
|
||||
className="page-link"
|
||||
onClick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
aria-label="Next page"
|
||||
>
|
||||
<i className="bi bi-chevron-right"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="mb-4">
|
||||
<SearchResultsMap
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useAuth } from "../contexts/AuthContext";
|
||||
import api from "../services/api";
|
||||
import { Item, Rental, ConditionCheck } from "../types";
|
||||
import { rentalAPI, conditionCheckAPI } from "../services/api";
|
||||
import { getPublicImageUrl } from "../services/uploadService";
|
||||
import { getImageUrl } from "../services/uploadService";
|
||||
import ReviewRenterModal from "../components/ReviewRenterModal";
|
||||
import RentalCancellationModal from "../components/RentalCancellationModal";
|
||||
import DeclineRentalModal from "../components/DeclineRentalModal";
|
||||
@@ -364,9 +364,16 @@ const Owning: React.FC = () => {
|
||||
{rental.item?.imageFilenames &&
|
||||
rental.item.imageFilenames[0] && (
|
||||
<img
|
||||
src={getPublicImageUrl(rental.item.imageFilenames[0])}
|
||||
src={getImageUrl(rental.item.imageFilenames[0], 'thumbnail')}
|
||||
className="card-img-top"
|
||||
alt={rental.item.name}
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget;
|
||||
if (!target.dataset.fallback && rental.item) {
|
||||
target.dataset.fallback = 'true';
|
||||
target.src = getImageUrl(rental.item.imageFilenames[0], 'original');
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
height: "200px",
|
||||
objectFit: "contain",
|
||||
@@ -617,9 +624,16 @@ const Owning: React.FC = () => {
|
||||
>
|
||||
{item.imageFilenames && item.imageFilenames[0] && (
|
||||
<img
|
||||
src={getPublicImageUrl(item.imageFilenames[0])}
|
||||
src={getImageUrl(item.imageFilenames[0], 'thumbnail')}
|
||||
className="card-img-top"
|
||||
alt={item.name}
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget;
|
||||
if (!target.dataset.fallback) {
|
||||
target.dataset.fallback = 'true';
|
||||
target.src = getImageUrl(item.imageFilenames[0], 'original');
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
height: "200px",
|
||||
objectFit: "contain",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { userAPI, itemAPI, rentalAPI, addressAPI, conditionCheckAPI } from "../services/api";
|
||||
import { User, Item, Rental, Address, ConditionCheck } from "../types";
|
||||
import { uploadFile, getPublicImageUrl } from "../services/uploadService";
|
||||
import { uploadImageWithVariants, getImageUrl } from "../services/uploadService";
|
||||
import AvailabilitySettings from "../components/AvailabilitySettings";
|
||||
import ReviewItemModal from "../components/ReviewModal";
|
||||
import ReviewRenterModal from "../components/ReviewRenterModal";
|
||||
@@ -168,7 +168,7 @@ const Profile: React.FC = () => {
|
||||
response.data.itemRequestNotificationRadius || 10,
|
||||
});
|
||||
if (response.data.imageFilename) {
|
||||
setImagePreview(getPublicImageUrl(response.data.imageFilename));
|
||||
setImagePreview(getImageUrl(response.data.imageFilename, 'thumbnail'));
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.response?.data?.message || "Failed to fetch profile");
|
||||
@@ -365,21 +365,21 @@ const Profile: React.FC = () => {
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
// Upload image to S3
|
||||
// Upload image to S3 (with resizing)
|
||||
try {
|
||||
const { key, publicUrl } = await uploadFile("profile", file);
|
||||
const { baseKey, publicUrl } = await uploadImageWithVariants("profile", file);
|
||||
|
||||
// Update the imageFilename in formData with the S3 key
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
imageFilename: key,
|
||||
imageFilename: baseKey,
|
||||
}));
|
||||
|
||||
// Update preview to use the S3 URL
|
||||
setImagePreview(publicUrl);
|
||||
// Update preview to use the thumbnail URL
|
||||
setImagePreview(getImageUrl(baseKey, 'thumbnail'));
|
||||
|
||||
// Save imageFilename to database immediately
|
||||
const response = await userAPI.updateProfile({ imageFilename: key });
|
||||
const response = await userAPI.updateProfile({ imageFilename: baseKey });
|
||||
setProfileData(response.data);
|
||||
updateUser(response.data);
|
||||
} catch (err: any) {
|
||||
@@ -389,7 +389,7 @@ const Profile: React.FC = () => {
|
||||
setImageFile(null);
|
||||
setImagePreview(
|
||||
profileData?.imageFilename
|
||||
? getPublicImageUrl(profileData.imageFilename)
|
||||
? getImageUrl(profileData.imageFilename, 'thumbnail')
|
||||
: null
|
||||
);
|
||||
}
|
||||
@@ -450,7 +450,7 @@ const Profile: React.FC = () => {
|
||||
profileData.itemRequestNotificationRadius || 10,
|
||||
});
|
||||
setImagePreview(
|
||||
profileData.imageFilename ? getPublicImageUrl(profileData.imageFilename) : null
|
||||
profileData.imageFilename ? getImageUrl(profileData.imageFilename, 'thumbnail') : null
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1269,9 +1269,16 @@ const Profile: React.FC = () => {
|
||||
<div className="card h-100">
|
||||
{rental.item?.imageFilenames && rental.item.imageFilenames[0] && (
|
||||
<img
|
||||
src={getPublicImageUrl(rental.item.imageFilenames[0])}
|
||||
src={getImageUrl(rental.item.imageFilenames[0], 'thumbnail')}
|
||||
className="card-img-top"
|
||||
alt={rental.item.name}
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget;
|
||||
if (!target.dataset.fallback && rental.item) {
|
||||
target.dataset.fallback = 'true';
|
||||
target.src = getImageUrl(rental.item.imageFilenames[0], 'original');
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
height: "150px",
|
||||
objectFit: "cover",
|
||||
@@ -1424,9 +1431,16 @@ const Profile: React.FC = () => {
|
||||
<div className="card h-100">
|
||||
{rental.item?.imageFilenames && rental.item.imageFilenames[0] && (
|
||||
<img
|
||||
src={getPublicImageUrl(rental.item.imageFilenames[0])}
|
||||
src={getImageUrl(rental.item.imageFilenames[0], 'thumbnail')}
|
||||
className="card-img-top"
|
||||
alt={rental.item.name}
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget;
|
||||
if (!target.dataset.fallback && rental.item) {
|
||||
target.dataset.fallback = 'true';
|
||||
target.src = getImageUrl(rental.item.imageFilenames[0], 'original');
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
height: "150px",
|
||||
objectFit: "cover",
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { User, Item } from '../types';
|
||||
import { userAPI, itemAPI } from '../services/api';
|
||||
import { getPublicImageUrl } from '../services/uploadService';
|
||||
import { getImageUrl } from '../services/uploadService';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import ChatWindow from '../components/ChatWindow';
|
||||
import Avatar from '../components/Avatar';
|
||||
@@ -101,9 +101,16 @@ const PublicProfile: React.FC = () => {
|
||||
>
|
||||
{item.imageFilenames.length > 0 ? (
|
||||
<img
|
||||
src={getPublicImageUrl(item.imageFilenames[0])}
|
||||
src={getImageUrl(item.imageFilenames[0], 'thumbnail')}
|
||||
className="card-img-top"
|
||||
alt={item.name}
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget;
|
||||
if (!target.dataset.fallback) {
|
||||
target.dataset.fallback = 'true';
|
||||
target.src = getImageUrl(item.imageFilenames[0], 'original');
|
||||
}
|
||||
}}
|
||||
style={{ height: '200px', objectFit: 'contain', backgroundColor: '#f8f9fa' }}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useParams, useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { Item } from "../types";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { itemAPI, rentalAPI } from "../services/api";
|
||||
import { getPublicImageUrl } from "../services/uploadService";
|
||||
import { getImageUrl } from "../services/uploadService";
|
||||
import EmbeddedStripeCheckout from "../components/EmbeddedStripeCheckout";
|
||||
import VerificationCodeModal from "../components/VerificationCodeModal";
|
||||
|
||||
@@ -261,9 +261,16 @@ const RentItem: React.FC = () => {
|
||||
<div className="card-body">
|
||||
{item.imageFilenames && item.imageFilenames[0] && (
|
||||
<img
|
||||
src={getPublicImageUrl(item.imageFilenames[0])}
|
||||
src={getImageUrl(item.imageFilenames[0], 'medium')}
|
||||
alt={item.name}
|
||||
className="img-fluid rounded mb-3"
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget;
|
||||
if (!target.dataset.fallback) {
|
||||
target.dataset.fallback = 'true';
|
||||
target.src = getImageUrl(item.imageFilenames[0], 'original');
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "150px",
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { rentalAPI, conditionCheckAPI } from "../services/api";
|
||||
import { getPublicImageUrl } from "../services/uploadService";
|
||||
import { getImageUrl } from "../services/uploadService";
|
||||
import { Rental, ConditionCheck } from "../types";
|
||||
import ReviewItemModal from "../components/ReviewModal";
|
||||
import RentalCancellationModal from "../components/RentalCancellationModal";
|
||||
@@ -243,9 +243,16 @@ const Renting: React.FC = () => {
|
||||
{rental.item?.imageFilenames &&
|
||||
rental.item.imageFilenames[0] && (
|
||||
<img
|
||||
src={getPublicImageUrl(rental.item.imageFilenames[0])}
|
||||
src={getImageUrl(rental.item.imageFilenames[0], 'thumbnail')}
|
||||
className="card-img-top"
|
||||
alt={rental.item.name}
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget;
|
||||
if (!target.dataset.fallback && rental.item) {
|
||||
target.dataset.fallback = 'true';
|
||||
target.src = getImageUrl(rental.item.imageFilenames[0], 'original');
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
height: "200px",
|
||||
objectFit: "contain",
|
||||
|
||||
Reference in New Issue
Block a user