diff --git a/src/pages/MessagePage/MessagePage.jsx b/src/pages/MessagePage/MessagePage.jsx
index 6d20bf7..3c9e0cd 100644
--- a/src/pages/MessagePage/MessagePage.jsx
+++ b/src/pages/MessagePage/MessagePage.jsx
@@ -13,6 +13,8 @@ import { FONT_OPTIONS, FONT_DROPDOWN_ITEMS } from '@/constants/fontMap';
import { useEffect } from 'react';
import Button from '@/components/Button/Button';
import EditorWrapper from '@/pages/MessagePage/components/EditorWrapper';
+import logoIcon from '@/assets/icons/icon_logo_white.svg';
+import backIcon from '@/assets/icons/icon_back.svg';
// 상대와의 관계 옵션
const RELATIONSHIP_OPTIONS = ['친구', '지인', '동료', '가족'];
@@ -87,6 +89,11 @@ function MessagePage() {
navigate(`/post/${recipientId}`); // 실제 라우트에 맞게 수정
};
+ const handleGoBackClick = (e) => {
+ e.preventDefault();
+ navigate(-1);
+ };
+
return (
{/* 6) 전송 버튼 */}
-
-
+
+
+ 돌아가기
+
diff --git a/src/pages/MessagePage/MessagePage.module.scss b/src/pages/MessagePage/MessagePage.module.scss
index 075e190..dca2031 100644
--- a/src/pages/MessagePage/MessagePage.module.scss
+++ b/src/pages/MessagePage/MessagePage.module.scss
@@ -34,38 +34,73 @@
}
/* 전송 버튼 영역 */
- &__actions {
+ &__button-area {
display: flex;
flex-direction: column;
- align-items: center;
- justify-content: center;
- }
- &__spacer {
- height: 200px;
+ gap: 30px;
+
+ @include tablet {
+ flex-direction: row;
+ gap: 20px;
+ }
+
+ @include mobile {
+ flex-direction: row;
+ gap: 20px;
+ }
}
&__submit {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ width: 100%;
+ padding: 14px 0;
background-color: var(--color-purple-600);
- color: var(--color-white);
- padding: 0.75rem 2rem;
border: none;
border-radius: 12px;
- font-size: 1rem;
- font-weight: 600;
+ font-weight: 700;
+ font-size: 18px;
+ line-height: 28px;
+ color: var(--color-white);
cursor: pointer;
- transition: background-color 0.3s ease;
&:hover {
background-color: var(--color-purple-700);
}
- &:active {
- background-color: var(--color-purple-800);
- }
-
&:disabled {
- background-color: var(--color-gray-400);
+ background-color: var(--color-gray-300);
cursor: not-allowed;
}
}
+
+ &__back {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ width: 100%;
+ padding: 14px 0;
+ background-color: var(--color-white);
+ border: 1px solid var(--color-purple-600);
+ border-radius: 12px;
+ font-weight: 700;
+ font-size: 18px;
+ line-height: 28px;
+ color: var(--color-purple-600);
+ cursor: pointer;
+
+ &:hover {
+ background-color: var(--color-purple-100);
+ }
+ }
+
+ &__button-logo {
+ aspect-ratio: 1;
+ width: 18px;
+ }
}
diff --git a/src/pages/MessagePage/components/ProfileSelector.jsx b/src/pages/MessagePage/components/ProfileSelector.jsx
index 569c582..190db3b 100644
--- a/src/pages/MessagePage/components/ProfileSelector.jsx
+++ b/src/pages/MessagePage/components/ProfileSelector.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useMemo } from 'react';
+import React, { useState, useEffect, useMemo, useRef } from 'react';
import { useApi } from '@/hooks/useApi';
import { getProfileImages } from '@/apis/profileImagesApi';
import ImagePreloader from '@/components/ImagePreloader';
@@ -7,6 +7,9 @@ import AVATAR_PLACEHOLDER from '@/assets/images/image_profile_default.svg';
import HorizontalScrollContainer from '@/components/HorizontalScrollContainer/HorizontalScrollContainer';
import GradientImage from '@/components/GradientImage/GradientImage';
import LoadingLabel from '@/components/LoadingLabel/LoadingLabel';
+import Button from '@/components/Button/Button';
+import { uploadImageToCloudinary } from '@/apis/syncApi/uploadImageToCloudinary';
+import { useToast } from '@/hooks/useToast';
/**
* 프로필 이미지 선택기
@@ -16,78 +19,143 @@ import LoadingLabel from '@/components/LoadingLabel/LoadingLabel';
*/
function ProfileSelector({ onSelectImage }) {
+ const { showToast } = useToast();
// 프로필 이미지 목록 가져오기 (API 호출)
const { data: imageData, loading } = useApi(getProfileImages);
-
// imageData가 배열이 아닐 경우 빈 배열로 초기화
- const imageUrls = useMemo(() => {
+ const apiImageUrls = useMemo(() => {
return Array.isArray(imageData?.imageUrls) ? imageData.imageUrls : [];
}, [imageData]);
// 선택된 이미지 URL 상태
const [selectedUrl, setSelectedUrl] = useState('');
+ // 로딩된 이미지 개수 상태
const [loadedCount, setLoadedCount] = useState(0);
- const allLoaded = imageUrls.length > 0 && loadedCount >= imageUrls.length;
+ // 모든 이미지가 로드되었는지 여부
+ const allLoaded = apiImageUrls.length > 0 && loadedCount >= apiImageUrls.length;
+
+ /* ---------------- 사용자 업로드 이미지 ---------------- */
+ const [uploading, setUploading] = useState(false); // 업로드 진행 여부
+ const fileInputRef = useRef(null);
// 로딩 후, 선택된 이미지 URL이 없으면 첫 번째 URL을 기본값으로 설정
useEffect(() => {
- if (!loading && imageUrls.length > 0 && !selectedUrl) {
+ if (!loading && apiImageUrls.length > 0 && !selectedUrl) {
// 아직 선택된 값이 없으면 첫 번째 URL을 기본값으로
- setSelectedUrl((prev) => prev || imageUrls[0]);
- onSelectImage && onSelectImage(imageUrls[0]);
+ setSelectedUrl((prev) => prev || apiImageUrls[0]);
+ onSelectImage && onSelectImage(apiImageUrls[0]);
}
- }, [loading, imageUrls, selectedUrl, onSelectImage]);
+ }, [loading, apiImageUrls, selectedUrl, onSelectImage]);
const handleImageSelect = (url) => {
setSelectedUrl(url);
onSelectImage && onSelectImage(url);
};
+ const handleFileChange = async (e) => {
+ const file = e.target.files?.[0];
+ if (!file || !file.type.startsWith('image/')) {
+ alert('이미지 파일만 업로드할 수 있습니다.');
+ return;
+ }
+
+ const previewUrl = URL.createObjectURL(file);
+
+ setSelectedUrl(previewUrl);
+ onSelectImage?.(previewUrl);
+ setUploading(true);
+ // Cloudinary 업로드 시작
+ try {
+ const uploadedUrl = await uploadImageToCloudinary(file);
+ setSelectedUrl(uploadedUrl);
+ onSelectImage?.(uploadedUrl);
+ } catch (err) {
+ console.error(err);
+ showToast?.({
+ type: 'fail',
+ message: '프로필 이미지 업로드에 실패했습니다.',
+ });
+ setSelectedUrl('');
+ onSelectImage?.('');
+ } finally {
+ setUploading(false);
+ URL.revokeObjectURL(previewUrl);
+ e.target.value = '';
+ }
+ };
+
const handleThumbLoad = () => setLoadedCount((c) => c + 1);
+ const uploadBtnClick = () => {
+ if (uploading) return; // 업로드 중이면 무시
+ if (fileInputRef.current) {
+ fileInputRef.current.value = ''; // *** 핵심: 값 초기화 ***
+ fileInputRef.current.click(); // 파일 다이얼로그 오픈
+ }
+ };
return (
-
- {/* 백그라운드에서 모든 이미지 미리 로드 */}
-
- {/* 현재 선택된 이미지를 보여주는 영역 */}
-
-
-
+
+
+ {/* 백그라운드에서 모든 이미지 미리 로드 */}
+
+ {/* 현재 선택된 이미지를 보여주는 영역 */}
+
+
+
- {/* 이미지 리스트 */}
-
-
-
- {imageUrls.map((url, idx) => {
- const isSelected = url === selectedUrl;
+ {/* 이미지 리스트 */}
+
+
+
+ {apiImageUrls.map((url, idx) => {
+ const isSelected = url === selectedUrl;
- return (
- handleImageSelect(url)}
- draggable='false'
- onLoaded={handleThumbLoad}
- />
- );
- })}
-
+ return (
+ handleImageSelect(url)}
+ draggable='false'
+ onLoaded={handleThumbLoad}
+ />
+ );
+ })}
+
+
+
+
+
+ 내 프로필 업로드
+
);
}
diff --git a/src/pages/MessagePage/components/ProfileSelector.module.scss b/src/pages/MessagePage/components/ProfileSelector.module.scss
index 6e9dadb..83835b5 100644
--- a/src/pages/MessagePage/components/ProfileSelector.module.scss
+++ b/src/pages/MessagePage/components/ProfileSelector.module.scss
@@ -1,9 +1,18 @@
+.profile-selector__container {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
.profile-selector {
width: 100%;
display: flex;
gap: 32px;
align-items: center;
justify-content: center;
+ @include mobile {
+ gap: 0;
+ }
&__preview-container {
display: flex;
@@ -31,6 +40,11 @@
font-size: var(--font-size-16);
font-weight: var(--font-weight-regular);
color: var(--color-gray-500);
+ //텍스트 잘리지 않게
+ white-space: nowrap;
+ @include mobile {
+ margin-left: -40px;
+ }
}
&__images {
gap: 4px;