fixed image storage local on minio
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useCallback, useEffect, useRef } from "react";
|
import { useState, useCallback, useEffect, useRef } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import CloseIcon from "./icons/CloseIcon";
|
import CloseIcon from "./icons/CloseIcon";
|
||||||
import { API_URL } from "../config/authConstants";
|
import { getImageUrl } from "../config/authConstants";
|
||||||
import { useChats } from "../hooks/useChats";
|
import { useChats } from "../hooks/useChats";
|
||||||
import { useAuthContext } from "../context/AuthContext";
|
import { useAuthContext } from "../context/AuthContext";
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ export default function ImageViewer({ images, initialIndex = 0, onClose, postDat
|
|||||||
const { createOrGetChat } = useChats();
|
const { createOrGetChat } = useChats();
|
||||||
const { isAuthenticated } = useAuthContext();
|
const { isAuthenticated } = useAuthContext();
|
||||||
|
|
||||||
const urls = images.map(u => (u.startsWith("http") ? u : `${API_URL}${u}`));
|
const urls = images.map(u => getImageUrl(u));
|
||||||
|
|
||||||
const next = useCallback(() => {
|
const next = useCallback(() => {
|
||||||
setIndex(i => (i + 1) % urls.length);
|
setIndex(i => (i + 1) % urls.length);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useState } from "react";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useChats } from "../hooks/useChats";
|
import { useChats } from "../hooks/useChats";
|
||||||
import { useAuthContext } from "../context/AuthContext";
|
import { useAuthContext } from "../context/AuthContext";
|
||||||
import { API_URL } from "../config/authConstants";
|
import { getImageUrl } from "../config/authConstants";
|
||||||
import DateCalendarPopup from "./DateCalendarPopup";
|
import DateCalendarPopup from "./DateCalendarPopup";
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMark
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const imageUrl = ad.images?.[0] ? `${API_URL}${ad.images[0]}` : null;
|
const imageUrl = getImageUrl(ad.images?.[0]);
|
||||||
|
|
||||||
// ✅ Формируем строку адреса из city + district
|
// ✅ Формируем строку адреса из city + district
|
||||||
const address = [ad.city, ad.district].filter(Boolean).join(", ");
|
const address = [ad.city, ad.district].filter(Boolean).join(", ");
|
||||||
|
|||||||
@@ -24,21 +24,31 @@ export default function ProfileModal({ onClose, initialData = {} }) {
|
|||||||
const existingImageUrl = initialData.avatar || null;
|
const existingImageUrl = initialData.avatar || null;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
imageFile,
|
images,
|
||||||
displayImage,
|
|
||||||
imgError,
|
imgError,
|
||||||
compressing,
|
compressing,
|
||||||
fileInputRef,
|
fileInputRef,
|
||||||
handleFileChange,
|
handleFileChange,
|
||||||
removeImage,
|
removeImage,
|
||||||
|
getExistingUrlsToKeep,
|
||||||
|
getFilesForUpload,
|
||||||
} = useImageUpload(existingImageUrl);
|
} = useImageUpload(existingImageUrl);
|
||||||
|
|
||||||
|
const currentImage = images[0];
|
||||||
|
const displayImage = currentImage?.preview ?? null;
|
||||||
|
const imageFile = currentImage?.file ?? null;
|
||||||
|
|
||||||
// ─── Field change handler ───────────────────────────────────────────────
|
// ─── Field change handler ───────────────────────────────────────────────
|
||||||
const handleFieldChange = useCallback((key, value) => {
|
const handleFieldChange = useCallback((key, value) => {
|
||||||
setForm(prev => ({ ...prev, [key]: value }));
|
setForm(prev => ({ ...prev, [key]: value }));
|
||||||
setErrors(prev => ({ ...prev, [key]: "" }));
|
setErrors(prev => ({ ...prev, [key]: "" }));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// ─── Remove image handler (wraps removeImage with the only image id) ─────
|
||||||
|
const handleRemoveImage = useCallback(() => {
|
||||||
|
if (currentImage?.id) removeImage(currentImage.id);
|
||||||
|
}, [currentImage, removeImage]);
|
||||||
|
|
||||||
// ─── Phone field change with formatting ──────────────────────────────────
|
// ─── Phone field change with formatting ──────────────────────────────────
|
||||||
const handlePhoneChange = useCallback((value) => {
|
const handlePhoneChange = useCallback((value) => {
|
||||||
const formatted = formatPhone(value);
|
const formatted = formatPhone(value);
|
||||||
@@ -53,13 +63,15 @@ export default function ProfileModal({ onClose, initialData = {} }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newAvatars = getFilesForUpload();
|
||||||
|
const keptExisting = getExistingUrlsToKeep();
|
||||||
const profileData = {
|
const profileData = {
|
||||||
firstName: form.firstName.trim(),
|
firstName: form.firstName.trim(),
|
||||||
lastName: form.lastName.trim(),
|
lastName: form.lastName.trim(),
|
||||||
login: form.login.trim(),
|
login: form.login.trim(),
|
||||||
phone: form.phone.trim(),
|
phone: form.phone.trim(),
|
||||||
email: form.email.trim(),
|
email: form.email.trim(),
|
||||||
avatar: imageFile || existingImageUrl,
|
avatar: newAvatars.length > 0 ? newAvatars[0] : (keptExisting.length > 0 ? keptExisting[0] : null),
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatedUser = await updateProfile(user?.id, profileData);
|
const updatedUser = await updateProfile(user?.id, profileData);
|
||||||
@@ -82,7 +94,7 @@ export default function ProfileModal({ onClose, initialData = {} }) {
|
|||||||
compressing={compressing}
|
compressing={compressing}
|
||||||
fileInputRef={fileInputRef}
|
fileInputRef={fileInputRef}
|
||||||
onFileChange={handleFileChange}
|
onFileChange={handleFileChange}
|
||||||
onRemove={removeImage}
|
onRemove={handleRemoveImage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LoginField
|
<LoginField
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ import glassesIcon from '../assets/glasses.png';
|
|||||||
|
|
||||||
export const API_URL = process.env.REACT_APP_API_URL || "http://192.168.1.68:8080";
|
export const API_URL = process.env.REACT_APP_API_URL || "http://192.168.1.68:8080";
|
||||||
|
|
||||||
|
export function getImageUrl(filename) {
|
||||||
|
if (!filename) return null;
|
||||||
|
if (filename.startsWith("http")) return filename;
|
||||||
|
if (filename.startsWith("/")) return `${API_URL}${filename}`;
|
||||||
|
return `${API_URL}/api/v1/files/${filename}`;
|
||||||
|
}
|
||||||
|
|
||||||
export const FLOAT_ICONS = [
|
export const FLOAT_ICONS = [
|
||||||
{ icon: bagIcon, top: "5%", left: "5%", delay: "0s" },
|
{ icon: bagIcon, top: "5%", left: "5%", delay: "0s" },
|
||||||
{ icon: catIcon, top: "5%", right: "5%", delay: "1s" },
|
{ icon: catIcon, top: "5%", right: "5%", delay: "1s" },
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useRef, useEffect, useCallback } from "react";
|
import { useState, useRef, useEffect, useCallback } from "react";
|
||||||
import { compressImage } from "../utils/compressImage";
|
import { compressImage } from "../utils/compressImage";
|
||||||
import { API_URL } from "../config/authConstants";
|
import { getImageUrl } from "../config/authConstants";
|
||||||
|
|
||||||
const MAX_SIZE_MB = 5;
|
const MAX_SIZE_MB = 5;
|
||||||
export const ACCEPT = "image/jpeg,image/png,image/webp";
|
export const ACCEPT = "image/jpeg,image/png,image/webp";
|
||||||
@@ -21,7 +21,7 @@ export function useImageUpload(existingUrls = []) {
|
|||||||
setImages(existingUrlList.map((url, i) => ({
|
setImages(existingUrlList.map((url, i) => ({
|
||||||
id: `existing-${i}`,
|
id: `existing-${i}`,
|
||||||
file: null,
|
file: null,
|
||||||
preview: `${API_URL}${url}`,
|
preview: getImageUrl(url),
|
||||||
isExisting: true,
|
isExisting: true,
|
||||||
serverUrl: url,
|
serverUrl: url,
|
||||||
})));
|
})));
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useChats } from "../hooks/useChats";
|
|||||||
import { useAuthContext } from "../context/AuthContext";
|
import { useAuthContext } from "../context/AuthContext";
|
||||||
import { useUnread } from "../context/UnreadContext";
|
import { useUnread } from "../context/UnreadContext";
|
||||||
import { getPostForChat } from "../stores/chatPostStore";
|
import { getPostForChat } from "../stores/chatPostStore";
|
||||||
import { API_URL } from "../config/authConstants";
|
import { getImageUrl } from "../config/authConstants";
|
||||||
import { useDeleteChat } from "../hooks/useDeleteChat";
|
import { useDeleteChat } from "../hooks/useDeleteChat";
|
||||||
import TrashIcon from "../components/icons/TrashIcon";
|
import TrashIcon from "../components/icons/TrashIcon";
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -75,7 +75,7 @@ export default function ChatListPage() {
|
|||||||
{!loading && chats.map((chat) => {
|
{!loading && chats.map((chat) => {
|
||||||
const post = getPostForChat(chat.id);
|
const post = getPostForChat(chat.id);
|
||||||
const title = post?.title || chat.otherUser?.name || t('chat.user');
|
const title = post?.title || chat.otherUser?.name || t('chat.user');
|
||||||
const imgSrc = post?.images?.[0] ? `${API_URL}${post.images[0]}` : null;
|
const imgSrc = getImageUrl(post?.images?.[0]);
|
||||||
const emoji = post?.emoji;
|
const emoji = post?.emoji;
|
||||||
const unreadCount = unreadMap[chat.id] ?? chat.unreadCount ?? chat.unreadMessages ?? 0;
|
const unreadCount = unreadMap[chat.id] ?? chat.unreadCount ?? chat.unreadMessages ?? 0;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useChatSocket } from "../hooks/useChatSocket";
|
|||||||
import { getPostForChat } from "../stores/chatPostStore";
|
import { getPostForChat } from "../stores/chatPostStore";
|
||||||
import { setActiveChatId } from "../stores/activeChatStore";
|
import { setActiveChatId } from "../stores/activeChatStore";
|
||||||
import { useUnread } from "../context/UnreadContext";
|
import { useUnread } from "../context/UnreadContext";
|
||||||
import { API_URL } from "../config/authConstants";
|
import { getImageUrl } from "../config/authConstants";
|
||||||
import Picker from "@emoji-mart/react";
|
import Picker from "@emoji-mart/react";
|
||||||
import emojiData from "@emoji-mart/data";
|
import emojiData from "@emoji-mart/data";
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -69,7 +69,7 @@ export default function ChatPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const post = getPostForChat(chatId);
|
const post = getPostForChat(chatId);
|
||||||
const postImg = post?.images?.[0] ? `${API_URL}${post.images[0]}` : null;
|
const postImg = getImageUrl(post?.images?.[0]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="f-app">
|
<div className="f-app">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
||||||
import { useNavigate, useLocation } from "react-router-dom";
|
import { useNavigate, useLocation } from "react-router-dom";
|
||||||
import "../styles/index.css";
|
import "../styles/index.css";
|
||||||
import { API_URL } from "../config/authConstants";
|
import { API_URL, getImageUrl } from "../config/authConstants";
|
||||||
import Sidebar from "../components/Sidebar";
|
import Sidebar from "../components/Sidebar";
|
||||||
import RightSidebar from "../components/RightSidebar";
|
import RightSidebar from "../components/RightSidebar";
|
||||||
import PostOnFeed from "../components/PostOnFeed";
|
import PostOnFeed from "../components/PostOnFeed";
|
||||||
@@ -78,11 +78,7 @@ export default function MainFeed() {
|
|||||||
const firstName = user.firstName || nameParts[0] || "";
|
const firstName = user.firstName || nameParts[0] || "";
|
||||||
const lastName = user.lastName || nameParts.slice(1).join(" ") || "";
|
const lastName = user.lastName || nameParts.slice(1).join(" ") || "";
|
||||||
|
|
||||||
// Аватар может быть относительным путём (/uploads/avatars/...) — добавляем API_URL
|
const avatar = getImageUrl(user.avatar);
|
||||||
const rawAvatar = user.avatar || null;
|
|
||||||
const avatar = rawAvatar && rawAvatar.startsWith("/")
|
|
||||||
? `${API_URL}${rawAvatar}`
|
|
||||||
: rawAvatar;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
login: user.login || "",
|
login: user.login || "",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useCallback, useEffect } from "react";
|
import { useState, useCallback, useEffect } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { API_URL } from "../config/authConstants";
|
import { API_URL, getImageUrl } from "../config/authConstants";
|
||||||
import { useAuthContext } from "../context/AuthContext";
|
import { useAuthContext } from "../context/AuthContext";
|
||||||
import MenuIcon from "../components/icons/MenuIcon";
|
import MenuIcon from "../components/icons/MenuIcon";
|
||||||
import Sidebar from "../components/Sidebar";
|
import Sidebar from "../components/Sidebar";
|
||||||
@@ -140,7 +140,7 @@ export default function ReviewsPage() {
|
|||||||
lastName: user.lastName || (user.name || "").trim().split(/\s+/).slice(1).join(" ") || "",
|
lastName: user.lastName || (user.name || "").trim().split(/\s+/).slice(1).join(" ") || "",
|
||||||
email: user.email || "",
|
email: user.email || "",
|
||||||
phone: user.phone || "",
|
phone: user.phone || "",
|
||||||
avatar: user.avatar && user.avatar.startsWith("/") ? `${API_URL}${user.avatar}` : user.avatar || null,
|
avatar: getImageUrl(user.avatar),
|
||||||
id: user.id,
|
id: user.id,
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|||||||
Reference in New Issue
Block a user