diff --git a/next.config.mjs b/next.config.mjs index 3209207..de73d45 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -10,6 +10,7 @@ const nextConfig = { '308b-203-249-127-39.ngrok-free.app', '4870-203-249-127-39.ngrok-free.app', '308b-203-249-127-39.ngrok-free.app', + 'opgg-com-image.akamaized.net', ], remotePatterns: [ { diff --git a/package-lock.json b/package-lock.json index f510783..07ab2e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "react-paginate": "^8.2.0", "react-slick": "^0.30.2", "react-syntax-highlighter": "^15.6.1", + "react-toastify": "^11.0.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", "sharp": "^0.33.5", @@ -2646,6 +2647,15 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -8341,6 +8351,19 @@ "react": ">= 0.14.0" } }, + "node_modules/react-toastify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.0.tgz", + "integrity": "sha512-NLTt0hNNlVZPLnY+uzUvmE2H2jRHUpaPi7H3+84+3fMjZSpfngyL/r+2VcaepNItWuN0BCBiZdVluXixXYSFmw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index 15addb1..d7f8436 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "react-paginate": "^8.2.0", "react-slick": "^0.30.2", "react-syntax-highlighter": "^15.6.1", + "react-toastify": "^11.0.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", "sharp": "^0.33.5", diff --git a/public/images/4581_7415.jpeg b/public/images/4581_7415.jpeg new file mode 100644 index 0000000..58f154a Binary files /dev/null and b/public/images/4581_7415.jpeg differ diff --git a/public/images/posts/003542.png b/public/images/posts/003542.png new file mode 100644 index 0000000..b955211 Binary files /dev/null and b/public/images/posts/003542.png differ diff --git a/public/images/posts/20240304182024_1994834_913_346.jpg b/public/images/posts/20240304182024_1994834_913_346.jpg new file mode 100644 index 0000000..491aa7c Binary files /dev/null and b/public/images/posts/20240304182024_1994834_913_346.jpg differ diff --git a/public/images/posts/AD.36584394.1.jpg b/public/images/posts/AD.36584394.1.jpg new file mode 100644 index 0000000..61c4c6f Binary files /dev/null and b/public/images/posts/AD.36584394.1.jpg differ diff --git a/public/images/posts/img (1).jpg b/public/images/posts/img (1).jpg new file mode 100644 index 0000000..58d85b5 Binary files /dev/null and b/public/images/posts/img (1).jpg differ diff --git a/public/images/posts/img.jpg b/public/images/posts/img.jpg new file mode 100644 index 0000000..dd9bf20 Binary files /dev/null and b/public/images/posts/img.jpg differ diff --git a/public/images/posts/long_short_img_03.png b/public/images/posts/long_short_img_03.png new file mode 100644 index 0000000..309d157 Binary files /dev/null and b/public/images/posts/long_short_img_03.png differ diff --git a/public/images/posts/v23446652a1c921d57fefb09a4a612f648.png b/public/images/posts/v23446652a1c921d57fefb09a4a612f648.png new file mode 100644 index 0000000..6ead458 Binary files /dev/null and b/public/images/posts/v23446652a1c921d57fefb09a4a612f648.png differ diff --git "a/public/images/posts/\355\231\224\353\251\264 \354\272\241\354\262\230 2024-12-17 184043.png" "b/public/images/posts/\355\231\224\353\251\264 \354\272\241\354\262\230 2024-12-17 184043.png" new file mode 100644 index 0000000..735ade0 Binary files /dev/null and "b/public/images/posts/\355\231\224\353\251\264 \354\272\241\354\262\230 2024-12-17 184043.png" differ diff --git a/public/images/sosuke.jpg b/public/images/sosuke.jpg new file mode 100644 index 0000000..90d568a Binary files /dev/null and b/public/images/sosuke.jpg differ diff --git a/src/app/(route)/layout.tsx b/src/app/(route)/layout.tsx index b0b8ff2..6ecdbbc 100644 --- a/src/app/(route)/layout.tsx +++ b/src/app/(route)/layout.tsx @@ -1,8 +1,10 @@ import type { Metadata } from 'next'; import localFont from 'next/font/local'; +import { ToastContainer } from 'react-toastify'; import Header from '../components/common/layout/Header'; import Rum from '../components/common/layout/Rum'; import UserProvider from '../components/common/layout/useProvider'; +import 'react-toastify/dist/ReactToastify.css'; import '../styles/globals.css'; export const metadata: Metadata = { @@ -31,6 +33,17 @@ export default function RootLayout({
{children} + diff --git a/src/app/api/blog/search/route.ts b/src/app/api/blog/search/route.ts index 5b2e43c..b61c4b6 100644 --- a/src/app/api/blog/search/route.ts +++ b/src/app/api/blog/search/route.ts @@ -5,9 +5,11 @@ export async function GET(req: Request) { const { searchParams } = new URL(req.url); const query = searchParams.get('query') || ''; // 검색어 - const page = searchParams.get('page') || '1'; // 페이지 번호 + const page = parseInt(searchParams.get('page') || '0', 10); // 페이지 번호를 정수로 변환 - const data = await getSearchPost(req, query); + console.log('Parsed Page:', page); // 디버깅용 로그 + + const data = await getSearchPost(req, page, query); return NextResponse.json(data); } diff --git a/src/app/components/analyze/AnalyzeContainer.tsx b/src/app/components/analyze/AnalyzeContainer.tsx index 6b703a2..985b2b0 100644 --- a/src/app/components/analyze/AnalyzeContainer.tsx +++ b/src/app/components/analyze/AnalyzeContainer.tsx @@ -15,7 +15,7 @@ import Icons from '../common/Icons'; const AnalyzeContainer = () => { const fadeInVariants = { hidden: { opacity: 0, y: 20 }, - visible: { opacity: 1, y: 0 }, + visible: { opacity: 1, y: 0, transition: { duration: 1 } }, }; const [nickname, setNickname] = useState(''); @@ -96,7 +96,7 @@ const AnalyzeContainer = () => { className="px-[6%] w-full h-auto flex-col flex gap-3" >
-
+
{ANALYZE_RESULT_TITLE[0]}
@@ -104,10 +104,19 @@ const AnalyzeContainer = () => {

{ANALYZE_RESULT_GUIDE[0]}

-
-

위험도: {analysisData.investmentStyle.riskLevel}

-

거래 패턴: {analysisData.investmentStyle.tradingPattern}

-

분석: {analysisData.investmentStyle.analysis}

+
+

+ 위험도 :{' '} + {analysisData.investmentStyle.riskLevel} +

+

+ 거래 패턴 :{' '} + {analysisData.investmentStyle.tradingPattern} +

+

+ 분석 :{' '} + {analysisData.investmentStyle.analysis} +

@@ -117,7 +126,7 @@ const AnalyzeContainer = () => { className="px-[6%] w-full h-auto flex-col flex gap-3" >
-
+
{ANALYZE_RESULT_TITLE[1]}
@@ -125,19 +134,48 @@ const AnalyzeContainer = () => {

{ANALYZE_RESULT_GUIDE[1]}

-
-

추천 전략: {analysisData.investmentStrategy.recommendation}

+
+

+ 추천 전략 :{' '} + {analysisData.investmentStrategy.recommendation} +

- 리스크 관리: {analysisData.investmentStrategy.riskManagement} + 리스크 관리 :{' '} + {analysisData.investmentStrategy.riskManagement} +

+

+ 분석 :{' '} + {analysisData.investmentStrategy.analysis}

-

분석: {analysisData.investmentStrategy.analysis}

) : ( -

- 분석 데이터를 불러오는 중... -

+
+ + 📈 {nickname}님의 제테크 타입을 분석중입니다... + +
+ {[0, 1, 2].map((index) => ( + + ))} +
+
)} ); diff --git a/src/app/components/blog/detail/BlogDetailContainer.tsx b/src/app/components/blog/detail/BlogDetailContainer.tsx index f1ef178..63fa1f4 100644 --- a/src/app/components/blog/detail/BlogDetailContainer.tsx +++ b/src/app/components/blog/detail/BlogDetailContainer.tsx @@ -20,13 +20,6 @@ const BlogDetailContainer = ({ postId }: PostDetailProps) => { const currentUserId = user?.result?.nickname; const router = useRouter(); - const { scrollYProgress } = useScroll(); - const scaleX = useSpring(scrollYProgress, { - stiffness: 100, - damping: 30, - restDelta: 0.001, - }); - // Scroll to top before the component is painted useLayoutEffect(() => { window.scrollTo(0, 0); @@ -59,7 +52,25 @@ const BlogDetailContainer = ({ postId }: PostDetailProps) => { if (!blogData) { console.log(blogData); - return
Loading...
; + return ( +
+ {[0, 1, 2].map((index) => ( + + ))} +
+ ); } const handleNicknameClick = () => { @@ -68,36 +79,57 @@ const BlogDetailContainer = ({ postId }: PostDetailProps) => { return ( - {/* 스크롤 진행바 */} - - {/* 콘텐츠 */} -
+ className="container mx-auto p-4" + initial={{ opacity: 0, y: 20 }} + animate={{ opacity: 1, y: 0 }} + transition={{ + delay: 0.3, + duration: 0.6, + ease: 'easeOut', + }} + > - - - -
+ + + + + + + + + +
); }; diff --git a/src/app/components/blog/main/all/BlogPost.tsx b/src/app/components/blog/main/all/BlogPost.tsx index 85c0ebf..d783360 100644 --- a/src/app/components/blog/main/all/BlogPost.tsx +++ b/src/app/components/blog/main/all/BlogPost.tsx @@ -14,7 +14,25 @@ const BlogPost = ({ post }: BlogPostProps) => { ? post.tags.split(',').map((tag: string) => `#${tag}`) : []; - const textContent = post.content.replace(/!\[.*?\]\(.*?\)/g, '').trim(); + const removeHtmlTags = (content: string) => { + return content.replace(/<[^>]*>?/gm, ''); + }; + + const removeMarkdownTags = (content: string) => { + return content + .replace(/[#*~`>+-]/g, '') // Markdown 기호 제거 + .replace(/\n/g, ' ') // 줄바꿈을 공백으로 변경 + .trim(); // 앞뒤 공백 제거 + }; + + const removeImageTags = (content: string) => { + return content.replace(/!\[.*?\]\(.*?\)/g, ''); // 이미지 태그 제거 + }; + + // 결과 출력 시 처리 + const textContent = post.content + ? removeImageTags(removeHtmlTags(removeMarkdownTags(post.content))).trim() + : ''; return ( { - const [query, setQuery] = useState(''); - const [results, setResults] = useState([]); // 타입 변경 - const [loading, setLoading] = useState(false); - const [searchExecuted, setSearchExecuted] = useState(false); - const [totalPages, setTotalPages] = useState(0); // 전체 페이지 수 - const [currentPage, setCurrentPage] = useState(1); // 현재 페이지 + const [query, setQuery] = useState(''); // 검색어 + const [results, setResults] = useState([]); // 검색 결과 + const [searchExecuted, setSearchExecuted] = useState(false); // 검색 여부 const [totalResults, setTotalResults] = useState(0); // 전체 결과 수 - const router = useRouter(); + const [totalPages, setTotalPages] = useState(0); // 전체 페이지 수 + const [currentPage, setCurrentPage] = useState(0); // 현재 페이지 - const handleSearch = async (page = 1) => { - setLoading(true); + // 검색 실행 함수 + const handleSearch = async (page = 0) => { setSearchExecuted(true); try { const response = await callGet( - `/api/blog/search?query=${query}&page=1&size=10`, + `/api/blog/search?query=${query}&page=${page}&size=9`, ); if (response.isSuccess) { const { content, totalPages: responseTotalPages, totalElements, - } = response.result; // totalPages 이름 변경 + } = response.result; - // API 응답 데이터를 SearchPostTypes[] 형태로 변환 const transformedResults = content.map((post: any) => ({ id: post.id, userId: post.userId, @@ -44,33 +41,35 @@ const BlogSearch = () => { updatedAt: post.updatedAt, imageUrls: post.imageUrls, likeCount: post.likeCount, + thumbnailUrl: post.thumbnailUrl, // 썸네일 추가 })); - setResults(transformedResults); // 변환된 데이터를 설정 - setTotalPages(responseTotalPages); // 변경된 변수 사용 - setTotalResults(totalElements); // 전체 검색 결과 수 + setResults(transformedResults); // 검색 결과 설정 + setTotalResults(totalElements); // 총 결과 수 설정 + setTotalPages(responseTotalPages); // 총 페이지 수 설정 setCurrentPage(page); // 현재 페이지 설정 - } else { - console.error('검색 결과를 가져오지 못했습니다.'); - setResults([]); } } catch (error) { console.error('Error fetching the data', error); - setResults([]); - } finally { - setLoading(false); } }; - // 디바운스 설정 + // 페이지 변경 함수 + const handlePageChange = ({ selected }: { selected: number }) => { + const newPage = selected; // 0-based 페이지 + setCurrentPage(newPage); // 현재 페이지 업데이트 + handleSearch(newPage); // 새 페이지 데이터 요청 + }; + + // 검색어 변경 시 검색 실행 useEffect(() => { const debounceSearch = setTimeout(() => { - if (query) { - handleSearch(1); // 입력할 때마다 첫 페이지부터 검색 + if (query.trim()) { + handleSearch(0); // 페이지 0부터 검색 } - }, 500); // 500ms 대기 (사용자가 입력을 멈춘 후 요청) + }, 500); - return () => clearTimeout(debounceSearch); // 이전 타이머를 클리어 + return () => clearTimeout(debounceSearch); }, [query]); return ( @@ -79,18 +78,22 @@ const BlogSearch = () => { handleSearch(1)} // 수동 검색 버튼 동작 + handleSearch={() => handleSearch(0)} // 수동 검색 /> - + {/* 검색 실행 후에만 페이지네이션 표시 */} + {searchExecuted && totalResults > 0 && ( +
+ +
+ )}
); diff --git a/src/app/components/blog/main/blogsearch/Result.tsx b/src/app/components/blog/main/blogsearch/Result.tsx index fafae54..2360c04 100644 --- a/src/app/components/blog/main/blogsearch/Result.tsx +++ b/src/app/components/blog/main/blogsearch/Result.tsx @@ -1,5 +1,6 @@ 'use client'; +import { motion } from 'framer-motion'; import Icons from '@/app/components/common/Icons'; import { searchBig } from '@/app/constants/iconPath'; import SearchPost from './SearchPost'; @@ -7,25 +8,47 @@ import SearchPost from './SearchPost'; interface ResultsProps { results: SearchPostTypes[]; // SearchPostTypes로 변경 searchExecuted: boolean; - loading: boolean; totalResults: number; - currentPage: number; - totalPages: number; - handlePageChange: (page: number) => void; } const Results: React.FC = ({ results, searchExecuted, - loading, totalResults, - currentPage, - totalPages, - handlePageChange, }) => { + const containerVariants = { + hidden: { opacity: 0, y: -20 }, // 초기 상태 + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.8, // 애니메이션 지속 시간 + ease: 'easeOut', + }, + }, + }; + + const itemVariants = { + hidden: { opacity: 0, scale: 0.9 }, // 초기 상태 + visible: { + opacity: 1, + scale: 1, + transition: { + duration: 0.3, + ease: 'easeOut', + }, + }, + }; + return ( -
- {searchExecuted && ( + + {/* 검색 결과 개수 */} + {searchExecuted && totalResults > 0 && (

{totalResults}개의 포스트를 @@ -34,25 +57,24 @@ const Results: React.FC = ({

)} - {loading ? ( -
-

로딩 중...

-
- ) : searchExecuted && results.length === 0 ? ( -
- -

검색 결과가 없습니다.

-
- ) : ( -
-
- {results.map((post) => ( + {/* 검색 결과 리스트 */} + {searchExecuted && totalResults > 0 && ( + + {results.map((post) => ( + - ))} -
-
+
+ ))} + )} -
+ ); }; diff --git a/src/app/components/blog/main/blogsearch/SearchBar.tsx b/src/app/components/blog/main/blogsearch/SearchBar.tsx index 4f43790..cec0ea2 100644 --- a/src/app/components/blog/main/blogsearch/SearchBar.tsx +++ b/src/app/components/blog/main/blogsearch/SearchBar.tsx @@ -5,11 +5,14 @@ interface SearchBarProps { query: string; setQuery: (query: string) => void; handleSearch: () => void; + onKeyDown?: (e: React.KeyboardEvent) => void; // onKeyDown 추가 } + const SearchBar: React.FC = ({ query, setQuery, handleSearch, + onKeyDown, // 추가된 prop }) => { return (
@@ -18,6 +21,7 @@ const SearchBar: React.FC = ({ placeholder="검색어를 입력해 주세요." value={query} onChange={(e) => setQuery(e.target.value)} + onKeyDown={onKeyDown} // onKeyDown 이벤트 추가 className="flex-1 border-none outline-none p-3 text-base rounded-full" />
); }; + export default SearchBar; diff --git a/src/app/components/blog/main/blogsearch/SearchPost.tsx b/src/app/components/blog/main/blogsearch/SearchPost.tsx index 709394c..4858dd3 100644 --- a/src/app/components/blog/main/blogsearch/SearchPost.tsx +++ b/src/app/components/blog/main/blogsearch/SearchPost.tsx @@ -14,12 +14,25 @@ const SearchPost = ({ post }: SearchPostProps) => { ? post.tags.split(',').map((tag: string) => `#${tag.trim()}`) : []; - const textContent = post.content.replace(/!\[.*?\]\(.*?\)/g, '').trim(); + const removeHtmlTags = (content: string) => { + return content.replace(/<[^>]*>?/gm, ''); + }; - const thumbnailUrl = - post.imageUrls && post.imageUrls.length > 0 - ? post.imageUrls[0] - : '/images/3c.png'; + const removeMarkdownTags = (content: string) => { + return content + .replace(/[#*~`>+-]/g, '') // Markdown 기호 제거 + .replace(/\n/g, ' ') // 줄바꿈을 공백으로 변경 + .trim(); // 앞뒤 공백 제거 + }; + + const removeImageTags = (content: string) => { + return content.replace(/!\[.*?\]\(.*?\)/g, ''); // 이미지 태그 제거 + }; + + // 결과 출력 시 처리 + const textContent = post.content + ? removeImageTags(removeHtmlTags(removeMarkdownTags(post.content))).trim() + : ''; return ( { >
{post.title} { ? post.tags.split(',').map((tag: string) => `#${tag}`) : []; - const textContent = post.content.replace(/!\[.*?\]\(.*?\)/g, '').trim(); + const removeHtmlTags = (content: string) => { + return content.replace(/<[^>]*>?/gm, ''); + }; - const thumbnailUrl = - post.imageUrls && post.imageUrls.length > 0 - ? post.imageUrls[0] - : '/images/3c.png'; + const removeMarkdownTags = (content: string) => { + return content + .replace(/[#*~`>+-]/g, '') // Markdown 기호 제거 + .replace(/\n/g, ' ') // 줄바꿈을 공백으로 변경 + .trim(); // 앞뒤 공백 제거 + }; + + const removeImageTags = (content: string) => { + return content.replace(/!\[.*?\]\(.*?\)/g, ''); // 이미지 태그 제거 + }; + + // 결과 출력 시 처리 + const textContent = post.content + ? removeImageTags(removeHtmlTags(removeMarkdownTags(post.content))).trim() + : ''; return ( { >
{post.title}
-

{post.title}

{truncateString(textContent, 66)} diff --git a/src/app/components/mypage/MyPostCard.tsx b/src/app/components/mypage/MyPostCard.tsx index 63af28b..2212467 100644 --- a/src/app/components/mypage/MyPostCard.tsx +++ b/src/app/components/mypage/MyPostCard.tsx @@ -15,7 +15,25 @@ const MyPostCard = ({ mypost }: MyPostCardsProps) => { router.push(`/blog/detail?id=${mypost.postId}`); }; - const textContent = mypost.content.replace(/!\[.*?\]\(.*?\)/g, '').trim(); + const removeHtmlTags = (content: string) => { + return content.replace(/<[^>]*>?/gm, ''); + }; + + const removeMarkdownTags = (content: string) => { + return content + .replace(/[#*~`>+-]/g, '') // Markdown 기호 제거 + .replace(/\n/g, ' ') // 줄바꿈을 공백으로 변경 + .trim(); // 앞뒤 공백 제거 + }; + + const removeImageTags = (content: string) => { + return content.replace(/!\[.*?\]\(.*?\)/g, ''); // 이미지 태그 제거 + }; + + // 결과 출력 시 처리 + const textContent = mypost.content + ? removeImageTags(removeHtmlTags(removeMarkdownTags(mypost.content))).trim() + : ''; return (

({ @@ -68,40 +71,94 @@ function AccountContainer() { fetchProfileData(); }, []); + const handleProfileImageUpload = async (file: File) => { + try { + // Presigned URL 요청 + console.log('Uploading File:', file.name); + + const response = await fetch( + `/api/blog/images?bucketName=dev-user&fileName=${file.name}`, + ); + + if (!response.ok) { + throw new Error('Presigned URL 요청 실패'); + } + + const resData = await response.json(); + const presignedUrl = resData.result; + console.log('Presigned URL:', presignedUrl); + + // 이미지 PUT 요청 + const uploadResponse = await fetch(presignedUrl, { + method: 'PUT', + headers: { + 'Content-Type': file.type, + }, + body: file, + }); + + if (!uploadResponse.ok) { + throw new Error('이미지 업로드 실패'); + } + + // 업로드된 이미지 URL 생성 + const imageUrl = presignedUrl.split('?')[0]; + console.log('Uploaded Image URL:', imageUrl); + + // 이미지 URL 확인 (선택적 검증) + const verifyResponse = await fetch(imageUrl); + if (!verifyResponse.ok) { + throw new Error('이미지 확인 실패'); + } + + console.log('Image Verified Successfully'); + + // formData에 이미지 URL 업데이트 + updateFormData('profileImageUrl', imageUrl); + toast.success('프로필 이미지가 성공적으로 업로드되었습니다.'); + } catch (error) { + console.error('Error uploading profile image:', error); + toast.error('이미지 업로드에 실패했습니다.'); + } + }; + const handleSignUpClick = () => { if (isSatisfied) { setIsSaveModalOpen(true); - } else alert('입력정보를 확인해주세요!'); + } else toast.error('입력정보를 확인해주세요!'); }; const handleSave = async () => { + // 관심 키워드 배열 변환 const mappedInterests = formData.interestKeywords.map( - (keyword) => INTEREST_MAP[keyword], + (keyword) => INTEREST_MAP[keyword] || keyword, ); + const mappedSalaryRange = INCOME_RANGE_MAP[formData.salaryRange]; + // 기존 이미지 유지 로직 const payload = { nickname: formData.nickname, blogName: formData.blogName, birth: formData.birth, salary: mappedSalaryRange, interestKeywords: mappedInterests, - profileImageUrl: formData.profileImageUrl, + profileImageUrl: formData.profileImageUrl || '/images/profile.png', // 기존 이미지 유지 }; try { - const response = await callPatch('/api/users', { payload }); + const response = await callPatch('/api/users', payload); if (response.isSuccess) { setIsSaveModalOpen(false); setIsSaveFinModalOpen(true); - alert('프로필이 성공적으로 저장되었습니다.'); + toast.success('프로필이 성공적으로 저장되었습니다.'); } else { - alert('프로필 저장에 실패했습니다.'); + toast.error('프로필 저장에 실패했습니다.'); console.error('응답 실패:', response.message); } } catch (error) { console.error('프로필 저장 중 오류 발생:', error); - alert('프로필 저장 중 오류가 발생했습니다.'); + toast.error('프로필 저장 중 오류가 발생했습니다.'); } }; @@ -110,14 +167,36 @@ function AccountContainer() {
{ACCOUNT_TEXT[0]}
-
- profile +
+
+ profile +
+ document.getElementById('profileImageInput')?.click() + } + className="absolute bottom-0 right-0 w-6 h-6 bg-gray-500 rounded-full flex items-center justify-center border border-gray-300 shadow-md z-10 cursor-pointer" + > + +
+ { + const file = e.target.files?.[0]; + if (file) { + handleProfileImageUpload(file); + } + }} + /> +
diff --git a/src/app/components/mypage/myaccount/MyPersonalInfo.tsx b/src/app/components/mypage/myaccount/MyPersonalInfo.tsx index 49e7f34..36f5efb 100644 --- a/src/app/components/mypage/myaccount/MyPersonalInfo.tsx +++ b/src/app/components/mypage/myaccount/MyPersonalInfo.tsx @@ -6,6 +6,8 @@ import { } from '@/app/constants/auth'; import { isCorrect } from '@/app/utils/qualify'; import { ACCOUNT_TEXT } from '@/app/constants/mypage'; +import { useState } from 'react'; +import { callPost } from '@/app/utils/callApi'; import Input from '../../common/Input'; interface MyPersonalInfoProps { @@ -14,6 +16,45 @@ interface MyPersonalInfoProps { } const MyPersonalInfo = ({ formData, updateFormData }: MyPersonalInfoProps) => { + const [checkStatus, setCheckStatus] = useState<{ + text: string; + textColor: string; + }>({ text: '', textColor: 'gray-1' }); + + const handleBlogNameCheck = async () => { + // 입력값 검증 + if (!isCorrect(formData.blogName)) { + setCheckStatus({ + text: '유효하지 않은 블로그명입니다.', + textColor: 'red-1', + }); + return; + } + + try { + const response = await callPost('/api/auth/signup/blogname', { + blogName: formData.blogName, + }); + + if (response.isSuccess) { + setCheckStatus({ + text: '사용 가능한 블로그명입니다.', + textColor: 'blue-1', + }); + updateFormData('isPossible', true); // 사용 가능 상태 업데이트 + } else { + setCheckStatus({ + text: '이미 사용 중인 블로그명입니다.', + textColor: 'red-1', + }); + updateFormData('isPossible', false); // 사용 불가능 상태 업데이트 + } + } catch (error) { + console.error('블로그명 중복 확인 실패:', error); + setCheckStatus({ text: '서버 오류가 발생했습니다.', textColor: 'red-1' }); + } + }; + return (
@@ -22,7 +63,7 @@ const MyPersonalInfo = ({ formData, updateFormData }: MyPersonalInfoProps) => { type="signUp" textValue={formData.nickname} onChange={(e) => updateFormData('nickname', e.target.value)} - placeholder="NAKDO" + placeholder="NICKNAME" maxLength={8} className="pr-8 rounded-[10px]" /> @@ -33,23 +74,24 @@ const MyPersonalInfo = ({ formData, updateFormData }: MyPersonalInfoProps) => { updateFormData('blogName', e.target.value)} + onChange={(e) => { + updateFormData('blogName', e.target.value); + updateFormData('isPossible', false); // 입력값 변경 시 상태 초기화 + setCheckStatus({ text: '', textColor: 'gray-1' }); + }} placeholder={BLOGNAME_TEXT[1]} maxLength={8} />
-
- {BLOGNAME_TEXT[2]} +
+ {checkStatus.text}
diff --git a/src/app/service/getRequest.ts b/src/app/service/getRequest.ts index 0539803..1219da5 100644 --- a/src/app/service/getRequest.ts +++ b/src/app/service/getRequest.ts @@ -182,9 +182,13 @@ export const getAnalysis = async (req: Request) => { return getRequest(url, req); }; -export const getSearchPost = async (req: Request, query: string) => { - const url = `/api/blogs/search?query=${query}&page=1&size=12 -`; +export const getSearchPost = async ( + req: Request, + page: number, + query?: string, +) => { + const url = `/api/blogs/search?query=${query}&page=${page}&size=9`; + return getRequest(url, req); }; export const getHoldStock = async (