Merge pull request #32 from SlimusMinus/fixed_save-image

fixed image storage local on minio
This commit is contained in:
SlimusMinus
2026-07-10 01:25:46 +03:00
committed by GitHub
9 changed files with 37 additions and 22 deletions

View File

@@ -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);

View File

@@ -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(", ");

View File

@@ -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

View File

@@ -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" },

View File

@@ -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,
}))); })));

View File

@@ -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 (

View File

@@ -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">

View File

@@ -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 || "",

View File

@@ -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,
} }
: {}; : {};