image optimization. Image resizing client side, index added to db, pagination
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { getPublicImageUrl } from "../services/uploadService";
|
||||
import { getImageUrl } from "../services/uploadService";
|
||||
|
||||
interface AvatarUser {
|
||||
id?: string;
|
||||
@@ -98,8 +98,8 @@ const Avatar: React.FC<AvatarProps> = ({
|
||||
}
|
||||
|
||||
const { firstName, lastName, imageFilename, id } = user;
|
||||
// Use direct imageUrl if provided, otherwise construct from imageFilename
|
||||
const imageUrl = directImageUrl || (imageFilename ? getPublicImageUrl(imageFilename) : null);
|
||||
// Use direct imageUrl if provided, otherwise construct from imageFilename (use thumbnail for avatars)
|
||||
const imageUrl = directImageUrl || (imageFilename ? getImageUrl(imageFilename, 'thumbnail') : null);
|
||||
const hasValidImage = imageUrl && !imageError;
|
||||
|
||||
if (hasValidImage) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import React, {
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { messageAPI } from "../services/api";
|
||||
import { getSignedUrl, uploadFile } from "../services/uploadService";
|
||||
import { getSignedImageUrl, uploadImageWithVariants } from "../services/uploadService";
|
||||
import { User, Message } from "../types";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { useSocket } from "../contexts/SocketContext";
|
||||
@@ -204,7 +204,8 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
const newUrls = new Map(imageUrls);
|
||||
await Promise.all(
|
||||
messagesWithImages.map(async (m) => {
|
||||
const url = await getSignedUrl(m.imageFilename!);
|
||||
// Use thumbnail size for chat previews
|
||||
const url = await getSignedImageUrl(m.imageFilename!, 'thumbnail');
|
||||
newUrls.set(m.imageFilename!, url);
|
||||
})
|
||||
);
|
||||
@@ -374,11 +375,11 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
}
|
||||
|
||||
try {
|
||||
// Upload image to S3 first if present
|
||||
// Upload image to S3 first if present (with resizing)
|
||||
let imageFilename: string | undefined;
|
||||
if (imageToSend) {
|
||||
const { key } = await uploadFile("message", imageToSend);
|
||||
imageFilename = key;
|
||||
const { baseKey } = await uploadImageWithVariants("message", imageToSend);
|
||||
imageFilename = baseKey;
|
||||
}
|
||||
|
||||
const response = await messageAPI.sendMessage({
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
||||
import { ForumComment } from "../types";
|
||||
import CommentForm from "./CommentForm";
|
||||
import ForumImageUpload from "./ForumImageUpload";
|
||||
import { getPublicImageUrl } from "../services/uploadService";
|
||||
import { getImageUrl } from "../services/uploadService";
|
||||
import { IMAGE_LIMITS } from "../config/imageLimits";
|
||||
|
||||
interface CommentThreadProps {
|
||||
@@ -77,7 +77,7 @@ const CommentThread: React.FC<CommentThreadProps> = ({
|
||||
setEditContent(comment.content);
|
||||
const existingKeys = comment.imageFilenames || [];
|
||||
setExistingImageKeys(existingKeys);
|
||||
setEditImagePreviews(existingKeys.map((key) => getPublicImageUrl(key)));
|
||||
setEditImagePreviews(existingKeys.map((key) => getImageUrl(key, 'thumbnail')));
|
||||
setEditImageFiles([]);
|
||||
};
|
||||
|
||||
@@ -280,7 +280,7 @@ const CommentThread: React.FC<CommentThreadProps> = ({
|
||||
{comment.imageFilenames.map((image, index) => (
|
||||
<div key={index} className="col-4 col-md-3">
|
||||
<img
|
||||
src={getPublicImageUrl(image)}
|
||||
src={getImageUrl(image, 'thumbnail')}
|
||||
alt={`Comment image`}
|
||||
className="img-fluid rounded"
|
||||
style={{
|
||||
@@ -289,8 +289,15 @@ const CommentThread: React.FC<CommentThreadProps> = ({
|
||||
objectFit: "contain",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget;
|
||||
if (!target.dataset.fallback) {
|
||||
target.dataset.fallback = 'true';
|
||||
target.src = getImageUrl(image, 'original');
|
||||
}
|
||||
}}
|
||||
onClick={() =>
|
||||
window.open(getPublicImageUrl(image), "_blank")
|
||||
window.open(getImageUrl(image, 'original'), "_blank")
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { conditionCheckAPI } from "../services/api";
|
||||
import { uploadFiles } from "../services/uploadService";
|
||||
import { uploadImagesWithVariants } from "../services/uploadService";
|
||||
import { IMAGE_LIMITS } from "../config/imageLimits";
|
||||
|
||||
interface ConditionCheckModalProps {
|
||||
@@ -84,9 +84,9 @@ const ConditionCheckModal: React.FC<ConditionCheckModalProps> = ({
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
|
||||
// Upload photos to S3 first
|
||||
const uploadResults = await uploadFiles("condition-check", photos);
|
||||
const imageFilenames = uploadResults.map((result) => result.key);
|
||||
// Upload photos to S3 first (with resizing)
|
||||
const uploadResults = await uploadImagesWithVariants("condition-check", photos);
|
||||
const imageFilenames = uploadResults.map((result) => result.baseKey);
|
||||
|
||||
// Submit condition check with S3 keys
|
||||
await conditionCheckAPI.submitConditionCheck(rentalId, {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { ConditionCheck } from "../types";
|
||||
import { getSignedUrl } from "../services/uploadService";
|
||||
import { getSignedImageUrl } from "../services/uploadService";
|
||||
|
||||
interface ConditionCheckViewerModalProps {
|
||||
show: boolean;
|
||||
@@ -51,7 +51,7 @@ const ConditionCheckViewerModal: React.FC<ConditionCheckViewerModalProps> = ({
|
||||
try {
|
||||
await Promise.all(
|
||||
validKeys.map(async (key) => {
|
||||
const url = await getSignedUrl(key);
|
||||
const url = await getSignedImageUrl(key, 'medium');
|
||||
newUrls.set(key, url);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Item } from '../types';
|
||||
import { getPublicImageUrl } from '../services/uploadService';
|
||||
import { getImageUrl } from '../services/uploadService';
|
||||
|
||||
interface ItemCardProps {
|
||||
item: Item;
|
||||
@@ -50,9 +50,18 @@ const ItemCard: React.FC<ItemCardProps> = ({
|
||||
<div className="card h-100" style={{ cursor: 'pointer' }}>
|
||||
{item.imageFilenames && item.imageFilenames[0] ? (
|
||||
<img
|
||||
src={getPublicImageUrl(item.imageFilenames[0])}
|
||||
src={getImageUrl(item.imageFilenames[0], 'thumbnail')}
|
||||
className="card-img-top"
|
||||
alt={item.name}
|
||||
loading="lazy"
|
||||
onError={(e) => {
|
||||
// Fallback to original for images uploaded before resizing was added
|
||||
const target = e.currentTarget;
|
||||
if (!target.dataset.fallback) {
|
||||
target.dataset.fallback = 'true';
|
||||
target.src = getImageUrl(item.imageFilenames[0], 'original');
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
height: isCompact ? '150px' : '200px',
|
||||
objectFit: 'contain',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Item } from '../types';
|
||||
import { getPublicImageUrl } from '../services/uploadService';
|
||||
import { getImageUrl } from '../services/uploadService';
|
||||
|
||||
interface ItemMarkerInfoProps {
|
||||
item: Item;
|
||||
@@ -26,9 +26,16 @@ const ItemMarkerInfo: React.FC<ItemMarkerInfoProps> = ({ item, onViewDetails })
|
||||
<div className="card border-0">
|
||||
{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: '120px',
|
||||
objectFit: 'contain',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { rentalAPI, conditionCheckAPI } from "../services/api";
|
||||
import { uploadFiles } from "../services/uploadService";
|
||||
import { uploadImagesWithVariants } from "../services/uploadService";
|
||||
import { Rental } from "../types";
|
||||
|
||||
interface ReturnStatusModalProps {
|
||||
@@ -290,9 +290,9 @@ const ReturnStatusModal: React.FC<ReturnStatusModalProps> = ({
|
||||
|
||||
// Submit post-rental condition check if photos are provided
|
||||
if (photos.length > 0) {
|
||||
// Upload photos to S3 first
|
||||
const uploadResults = await uploadFiles("condition-check", photos);
|
||||
const imageFilenames = uploadResults.map((result) => result.key);
|
||||
// Upload photos to S3 first (with resizing)
|
||||
const uploadResults = await uploadImagesWithVariants("condition-check", photos);
|
||||
const imageFilenames = uploadResults.map((result) => result.baseKey);
|
||||
|
||||
await conditionCheckAPI.submitConditionCheck(rental.id, {
|
||||
checkType: "post_rental_owner",
|
||||
|
||||
Reference in New Issue
Block a user