+
+
+ {coData.content}
+
+
+
{formattedDateTime}
- {/*추천 알고리즘 구현해야됨*/}
- {/*
-
-
- {boardData?.result.slice(0, visibleCards).map((posts: any, index: number) => {
- const createDateTime = new Date(posts.post.createDateTime);
- const formattedDateTime = `${createDateTime.getFullYear()}.${String(createDateTime.getMonth() + 1).padStart(2, '0')}.${String(createDateTime.getDate()).padStart(2, '0')} ${String(createDateTime.getHours()).padStart(2, '0')}:${String(createDateTime.getMinutes()).padStart(2, '0')}`;
-
- const getTextFromHtml = (html: any) => {
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
- return doc.body.innerText; // 텍스트만 반환
- };
-
- const bodyText = getTextFromHtml(posts.post.body);
-
- const getTransportImage = (transport: string, ticketColor: any) => {
- switch (transport) {
- case 'Airplane':
- return
;
- case 'Car':
- return
;
- case 'Bus':
- return
;
- case 'Bicycle':
- return
;
- case 'Train':
- return
;
- default:
- return null; // 기본값 또는 대체 이미지
- }
- };
- console.log()
- return (
-
-
-
- {posts.ticket.departureCode ? (
-
-
{posts.ticket.departureCode}
-
- {getTransportImage(posts.ticket.transport, posts.ticket.ticketColor)}
-
-
{posts.ticket.destinationCode}
-
- ) : (
-
-
- )}
-
-
{posts.post.title}
- {bodyText}
-
-
-
-
- {posts.member.nickName}
-
-
- {posts.post.isLiked ? (
-
- ) : (
-
- )}
- {posts.post.likeCount}
-
- {posts.post.commentCount}
-
-
-
-
- );
- })}
-
-
- */}
-
+
);
diff --git a/src/app/post/page.tsx b/src/app/post/page.tsx
index 13539384..0e1f0843 100644
--- a/src/app/post/page.tsx
+++ b/src/app/post/page.tsx
@@ -198,8 +198,8 @@ function PostWrite() {
memberNum: Number(passengerCount),
startDate: formatDates(startDate),
endDate: formatDates(endDate),
- ticketColor: ticketColor,
- transport: transportStr
+ ticketColor: ticketColor || 'Aquamarine',
+ transport: transportStr || "Airplane"
}
try {
console.log(postRequest, ticketRequest)
@@ -250,6 +250,10 @@ function PostWrite() {
}
};
+ const handleTagRemove = (index: number) => {
+ setTags((prevTags) => prevTags.filter((_, i) => i !== index)); // 해당 인덱스의 태그를 삭제
+ };
+
const { getLocationData } = getCountry({ setResult });
const { getLocationData1 } = getCountry1({ setResult1 });
@@ -304,7 +308,7 @@ function PostWrite() {
setInputValue1(e.target.value)} // 입력 값 변경 시 핸들러
onKeyDown={(e) => {
@@ -344,7 +348,7 @@ function PostWrite() {
setInputValue2(e.target.value)} // 입력 값 변경 시 핸들러
onKeyDown={(e) => {
@@ -465,10 +469,16 @@ function PostWrite() {
value={inputValue}
onChange={handleInputChange}
onKeyDown={handleKeyDown} />
-
+
{tags.map((tag, index) => (
-
+
{tag}
+
))}
diff --git a/src/components/auth/LoginForm.tsx b/src/components/auth/LoginForm.tsx
index 6bd89fc5..228533e7 100644
--- a/src/components/auth/LoginForm.tsx
+++ b/src/components/auth/LoginForm.tsx
@@ -54,6 +54,7 @@ const LoginForm = () => {
Swal.fire({
icon: 'error',
+ iconColor: '#FB3463',
title: '기존에 회원가입을 \n완료하지 않았습니다.',
text: '블로그 설정 페이지로 이동합니다.',
confirmButtonText: '확인',
diff --git a/src/components/blogSignUp/BlogRegisterFirst.tsx b/src/components/blogSignUp/BlogRegisterFirst.tsx
index f66485b5..5e0b536f 100644
--- a/src/components/blogSignUp/BlogRegisterFirst.tsx
+++ b/src/components/blogSignUp/BlogRegisterFirst.tsx
@@ -14,6 +14,7 @@ import {
import useUserInfo from "@/hooks/useUserInfo";
import { swear_words_arr } from "@/constants/wearWordsArr";
import { getByteLength } from "@/constants/getByteLength";
+import Cookies from "js-cookie";
const BlogRegisterFirst = () => {
const [profileImage, setProfileImage] = useState<{
@@ -33,6 +34,32 @@ const BlogRegisterFirst = () => {
const { setUserInfo } = useUserInfo();
const router = useRouter();
+ useEffect(() => {
+ // 페이지 진입 시 로그인 상태 및 role 값을 확인
+ checkLoginStatus();
+ }, []);
+
+ const checkLoginStatus = () => {
+ const accessToken = Cookies.get("accessToken");
+ const refreshToken = Cookies.get("refreshToken");
+ const role = Cookies.get("role");
+
+ // 로그인이 된 상태에서 role이 GUEST일 경우, 회원가입 페이지에 머물도록 함
+ if (accessToken && refreshToken && role === "GUEST") {
+ // GUEST 사용자이므로 회원가입 페이지에 머물게 합니다.
+ return;
+ }
+
+ // 로그인이 되었지만 role이 MEMBER 또는 ADMIN인 경우 리다이렉트
+ if (role === "MEMBER" || role === "ADMIN") {
+ router.push("/");
+ return;
+ }
+
+ // 로그인하지 않은 사용자는 이 페이지에 접근할 수 있도록 허용
+ };
+
+
const checkSwearWords = (value: string) => {
const lowerValue = value.toLowerCase();
return swear_words_arr.some((swearWord: string) =>
@@ -197,6 +224,7 @@ const BlogRegisterFirst = () => {
className="object-cover w-full h-full"
width={100}
height={100}
+ unoptimized={true}
/>
) : (
{
!blogNameError.includes("사용 가능") ||
!imageUploaded
? "cursor-not-allowed bg-[#cfcfcf] hover:bg-[#cfcfcf]"
- : ""
+ : "bg-btn-color"
}`}
onClick={handleSubmit}
style={{ fontSize: "1.2rem" }}
diff --git a/src/components/blogSignUp/BlogRegisterSecond.tsx b/src/components/blogSignUp/BlogRegisterSecond.tsx
index fcf13661..17429e13 100644
--- a/src/components/blogSignUp/BlogRegisterSecond.tsx
+++ b/src/components/blogSignUp/BlogRegisterSecond.tsx
@@ -1,15 +1,44 @@
"use client";
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
import Image from "next/image";
import BlogStep2 from "../../../public/BlogStep2.svg";
import Link from "next/link";
import { submitInterests } from "@/services/blog"
import { blogInterests } from "@/constants/blogPreference";
+import { useRouter } from "next/navigation";
+import Cookies from "js-cookie";
const BlogRegisterSecond = () => {
const [selectedInterests, setSelectedInterests] = useState([]);
+ const router = useRouter();
+
+ useEffect(() => {
+ // 페이지 진입 시 로그인 상태 및 role 값을 확인
+ checkLoginStatus();
+ }, []);
+
+ const checkLoginStatus = () => {
+ const accessToken = Cookies.get("accessToken");
+ const refreshToken = Cookies.get("refreshToken");
+ const role = Cookies.get("role");
+
+ // 로그인이 된 상태에서 role이 GUEST일 경우, 회원가입 페이지에 머물도록 함
+ if (accessToken && refreshToken && role === "GUEST") {
+ // GUEST 사용자이므로 회원가입 페이지에 머물게 합니다.
+ return;
+ }
+
+ // 로그인이 되었지만 role이 MEMBER 또는 ADMIN인 경우 리다이렉트
+ if (role === "MEMBER" || role === "ADMIN") {
+ router.push("/");
+ return;
+ }
+
+ // 로그인하지 않은 사용자는 이 페이지에 접근할 수 있도록 허용
+ };
+
const toggleInterest = (interest: string) => {
if (selectedInterests.includes(interest)) {
setSelectedInterests(
diff --git a/src/components/blogSignUp/BlogRegisterThird.tsx b/src/components/blogSignUp/BlogRegisterThird.tsx
index ced4170d..a03793a5 100644
--- a/src/components/blogSignUp/BlogRegisterThird.tsx
+++ b/src/components/blogSignUp/BlogRegisterThird.tsx
@@ -1,44 +1,80 @@
-'use client'
+'use client';
-import React from "react";
+import React, { useEffect } from "react";
import Image from "next/image";
import useUserInfo from "@/hooks/useUserInfo";
import BlogStep3 from "../../../public/BlogStep3.svg";
-import CheckIcon from "../../../public/CheckIcon.svg";
import TrippyImage from "../../../public/TrippyImage.svg";
-import AirplaneIcon from "../../../public/TrippyAirplane.svg";
import Link from "next/link";
+import Cookies from "js-cookie";
+import { useRouter } from "next/navigation";
const BlogRegisterThird = () => {
const { userInfo } = useUserInfo();
+ const router = useRouter();
+
+ useEffect(() => {
+ // 페이지 진입 시 로그인 상태 및 role 값을 확인
+ checkLoginStatus();
+ }, []);
+
+ const checkLoginStatus = () => {
+ const accessToken = Cookies.get("accessToken");
+ const refreshToken = Cookies.get("refreshToken");
+ const role = Cookies.get("role");
+
+ // 로그인이 된 상태에서 role이 GUEST일 경우, 회원가입 페이지에 머물도록 함
+ if (accessToken && refreshToken && role === "GUEST") {
+ // GUEST 사용자이므로 회원가입 페이지에 머물게 합니다.
+ return;
+ }
+
+ // 로그인이 되었지만 role이 MEMBER 또는 ADMIN인 경우 리다이렉트
+ if (role === "MEMBER" || role === "ADMIN") {
+ router.push("/");
+ return;
+ }
+
+ // 로그인하지 않은 사용자는 이 페이지에 접근할 수 있도록 허용
+ };
+
+ // 홈으로 버튼 클릭 시 쿠키 업데이트 로직
+ const handleHomeClick = () => {
+ const role = Cookies.get("role");
+
+ // role이 GUEST이면 MEMBER로 업데이트
+ if (role === "GUEST") {
+ Cookies.set("role", "MEMBER");
+ }
+ };
return (
-
-
-
-
+
-
-
회원가입 완료
-
-
{userInfo.nickName}님의 회원가입이 완료되었습니다.
-
다양한 여행 이야기를 둘러보세요!
-
+
+
회원가입 완료
+
+
{userInfo.nickName}님의 회원가입이 완료되었습니다.
+
다양한 여행 이야기를 둘러보세요!
+
-
-
-
+
+
+
);
};
-export default BlogRegisterThird;
+export default BlogRegisterThird;
\ No newline at end of file
diff --git a/src/components/ootd/ImageUploader.tsx b/src/components/ootd/ImageUploader.tsx
index 271fc34a..3304a2de 100644
--- a/src/components/ootd/ImageUploader.tsx
+++ b/src/components/ootd/ImageUploader.tsx
@@ -1,5 +1,6 @@
-import React, { ChangeEvent, useEffect, useState } from 'react';
+import React, { ChangeEvent, useCallback, useState } from 'react';
import Slider from 'react-slick';
+import Cropper from 'react-easy-crop';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
import OotdDefault from '../../../public/ootdDefaultImg.svg';
@@ -11,6 +12,8 @@ import Image from 'next/image';
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
import LeftArrowIcon from '../../../public/left-arrow.svg';
import RightArrowIcon from '../../../public/right-arrow.svg';
+import { getCroppedImg } from '@/utils/getCroppedImg';
+import { Area } from 'react-easy-crop';
interface ImageUploaderProps {
onImagesChange: (images: UploadedImage[]) => void;
@@ -20,29 +23,60 @@ interface ImageUploaderProps {
const ImageUploader: React.FC
= ({ onImagesChange, initialImages = [] }) => {
const [images, setImages] = useState(initialImages);
const [isUploading, setIsUploading] = useState(false);
+ const [imageSrc, setImageSrc] = useState(null); // 크롭할 이미지 소스
+ const [crop, setCrop] = useState({ x: 0, y: 0 });
+ const [zoom, setZoom] = useState(1);
+ const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
+ const [isCropping, setIsCropping] = useState(false);
+
+ // 모달을 열고 닫는 상태
+ const [isModalOpen, setIsModalOpen] = useState(false);
const handleImageUpload = async (event: ChangeEvent) => {
const file = event.target.files?.[0];
if (!file) return;
- setIsUploading(true);
+ setIsCropping(true);
+ const reader = new FileReader();
+ reader.onload = () => {
+ setImageSrc(reader.result as string);
+ setIsModalOpen(true); // 모달을 엽니다
+ };
+ reader.readAsDataURL(file);
+ };
+
+ const onCropComplete = useCallback((croppedArea: Area, croppedAreaPixels: Area) => {
+ setCroppedAreaPixels(croppedAreaPixels);
+ }, []);
+ const handleCropImage = async () => {
+ if (!imageSrc || !croppedAreaPixels) return;
+
+ setIsUploading(true);
try {
- const uploadedImage = await uploadImage(file);
- const newImages = [...images, uploadedImage.result];
- setImages(newImages);
- onImagesChange(newImages);
+ const croppedBlob = await getCroppedImg(imageSrc, croppedAreaPixels);
+
+ if (croppedBlob) {
+ const fileName = 'croppedImage.jpg'; // 파일 이름을 지정해 줍니다.
+ const croppedFile = new File([croppedBlob], fileName, { type: 'image/jpeg' });
+ const uploadedImage = await uploadImage(croppedFile); // File 형식으로 업로드
+ const newImages = [...images, uploadedImage.result];
+ setImages(newImages);
+ onImagesChange(newImages);
+ setIsModalOpen(false); // 모달을 닫습니다
+ }
} catch (error) {
console.error('Error uploading image:', error);
} finally {
setIsUploading(false);
+ setIsCropping(false);
+ setImageSrc(null);
}
};
const handleDeleteImage = (index: number) => {
const newImages = images.filter((_, i) => i !== index);
setImages(newImages);
- // Notify parent component about image deletion
onImagesChange(newImages);
};
@@ -54,7 +88,6 @@ const ImageUploader: React.FC = ({ onImagesChange, initialIm
newImages.splice(result.destination.index, 0, reorderedImage);
setImages(newImages);
- // Notify parent component about image reordering
onImagesChange(newImages);
};
@@ -71,9 +104,9 @@ const ImageUploader: React.FC = ({ onImagesChange, initialIm
display: 'block',
position: 'absolute', // 부모 요소 기준으로 절대 배치
right: '5px',
- top: '33%', // 세로 중앙에 배치
+ top: '50%', // 세로 중앙에 배치
transform: 'translateY(-50%)', // 세로 중앙 정렬
- zIndex: 1000,
+ zIndex: 10, // 모달과 겹치지 않도록 z-index 조정
}}
onClick={onClick}
>
@@ -94,9 +127,9 @@ const ImageUploader: React.FC = ({ onImagesChange, initialIm
display: 'block',
position: 'absolute', // 부모 요소 기준으로 절대 배치
left: '3px',
- top: '33%', // 세로 중앙에 배치
+ top: '50%', // 세로 중앙에 배치
transform: 'translateY(-50%)', // 세로 중앙 정렬
- zIndex: 1000,
+ zIndex: 10, // 모달과 겹치지 않도록 z-index 조정
}}
onClick={onClick}
>
@@ -105,8 +138,7 @@ const ImageUploader: React.FC = ({ onImagesChange, initialIm
)
);
};
-
-
+
const settings = {
dots: true,
infinite: displayImages.length > 1,
@@ -127,21 +159,54 @@ const ImageUploader: React.FC = ({ onImagesChange, initialIm
style={{ display: 'none' }}
id="image-upload-input"
/>
- displayImages.length === 0 && document.getElementById('image-upload-input')?.click()}
- className="relative cursor-pointer overflow-hidden bg-cover bg-center mx-auto"
- style={{
- backgroundImage: displayImages.length > 0 ? 'none' : `url(${OotdDefault.src})`,
- maxWidth: '460px',
- aspectRatio: '1 / 1', // 1:1 비율 유지
- }}
- >
+ {/* 크롭 모달 */}
+ {isModalOpen && (
+
+
+
이미지 영역 선택
+
+ {imageSrc && (
+
+ )}
+
+
+
+ 완료
+
+
setIsModalOpen(false)}>
+ 취소
+
+
+
+
+ )}
+ {/* 기존 이미지 영역은 모달이 열려도 그대로 유지 */}
+
displayImages.length === 0 && document.getElementById('image-upload-input')?.click()}
+ className={`relative cursor-pointer overflow-hidden bg-cover bg-center mx-auto ${
+ isModalOpen ? 'opacity-50' : '' // 모달이 열릴 때 투명도 조정으로만 시각적인 효과 적용
+ }`}
+ style={{
+ backgroundImage: displayImages.length > 0 ? 'none' : `url(${OotdDefault.src})`,
+ maxWidth: '460px',
+ aspectRatio: '1 / 1', // 1:1 비율 유지
+ }}
+ >
{!isUploading && displayImages.length === 0 && (
)}
@@ -150,7 +215,7 @@ const ImageUploader: React.FC
= ({ onImagesChange, initialIm
@@ -187,84 +252,83 @@ const ImageUploader: React.FC = ({ onImagesChange, initialIm
maxWidth: '460px', // 부모 컨테이너 최대 너비
}}
>
- {images.map((image, index) => (
-
- {(provided) => (
-
-
(
+
+ {(provided) => (
+
+
+
handleDeleteImage(index)}
+ className="absolute top-0 right-0 rounded-full cursor-pointer"
+ >
+
+
+
+ )}
+
+ ))}
+ {images.length > 0 && images.length < 5 && (
+
document.getElementById('image-upload-input')?.click()}
+ className="flex justify-center items-center cursor-pointer ml-[2%]"
style={{
- width: '100%',
- height: '100%',
- position: 'absolute',
- top: 0,
- left: 0,
- objectFit: 'cover',
+ width: '18%', // 새로운 이미지 추가 버튼도 같은 크기
+ paddingBottom: '18%', // 가로세로 비율 1:1
+ position: 'relative',
}}
- />
-
handleDeleteImage(index)}
- className="absolute top-0 right-0 rounded-full cursor-pointer"
>
-
- )}
-
- ))}
- {images.length > 0 && images.length < 5 && (
-
document.getElementById('image-upload-input')?.click()}
- className="flex justify-center items-center cursor-pointer ml-[2%]"
- style={{
- width: '18%', // 새로운 이미지 추가 버튼도 같은 크기
- paddingBottom: '18%', // 가로세로 비율 1:1
- position: 'relative',
- }}
- >
-
-
- )}
- {provided.placeholder}
-
- )}
-
-
-
+ )}
+ {provided.placeholder}
+
+ )}
+
+
);
};
-export default ImageUploader;
+export default ImageUploader;
\ No newline at end of file
diff --git a/src/components/ootd/LocationInput.tsx b/src/components/ootd/LocationInput.tsx
index aae3fdd6..e8b82262 100644
--- a/src/components/ootd/LocationInput.tsx
+++ b/src/components/ootd/LocationInput.tsx
@@ -1,9 +1,9 @@
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useState, useRef } from 'react';
import { Modal } from 'react-bootstrap';
-import { GoogleMap, Marker, useLoadScript, Libraries } from '@react-google-maps/api';
-import SearchIcon from '../../../public/icon_search.svg';
-import 'bootstrap/dist/css/bootstrap.min.css';
+import { GoogleMap, Marker, Autocomplete, useLoadScript, Libraries } from '@react-google-maps/api';
+import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
+import SearchIcon from '../../../public/icon_search.svg';
interface LocationInputProps {
onLocationChange: (location: { lat: number; lng: number; address: string }) => void;
@@ -20,10 +20,12 @@ const LocationInput: React.FC = ({ onLocationChange, selecte
const [modalIsOpen, setModalIsOpen] = useState(false);
const [selectedLocation, setSelectedLocation] = useState<{ lat: number; lng: number }>({
- lat: 37.5665,
- lng: 126.9780,
+ lat: 37.5665,
+ lng: 126.9780,
});
const [currentAddress, setCurrentAddress] = useState('');
+ const autocompleteRef = useRef(null);
+ const inputRef = useRef(null);
const onMapClick = useCallback((event: google.maps.MapMouseEvent) => {
if (event.latLng) {
@@ -40,6 +42,18 @@ const LocationInput: React.FC = ({ onLocationChange, selecte
}
}, []);
+ const onPlaceChanged = () => {
+ if (autocompleteRef.current) {
+ const place = autocompleteRef.current.getPlace();
+ if (place.geometry?.location) {
+ const lat = place.geometry.location.lat();
+ const lng = place.geometry.location.lng();
+ setSelectedLocation({ lat, lng });
+ setCurrentAddress(place.formatted_address || '');
+ }
+ }
+ };
+
const handleConfirm = () => {
onLocationChange({
lat: selectedLocation.lat,
@@ -50,10 +64,10 @@ const LocationInput: React.FC = ({ onLocationChange, selecte
};
return (
-
+
-
-
추천 장소
- {isSpotsLoading &&
추천 장소를 불러오는 중입니다...
}
-
- {recommendedSpots?.result && recommendedSpots.result.length > 0 ? (
-
- {recommendedSpots.result.map((spot: any, index: number) => (
- -
-
{spot.title}
- {spot.content}
- {/* {spot.imgUrl && spot.imgUrl.length > 0 && (
-
- )} */}
-
- ))}
-
- ) : (
- !isSpotsLoading &&
추천 장소가 없습니다.
- )}
-
>
);
};
diff --git a/src/components/ootd/PostOotd.tsx b/src/components/ootd/PostOotd.tsx
index 3aa07541..271ee991 100644
--- a/src/components/ootd/PostOotd.tsx
+++ b/src/components/ootd/PostOotd.tsx
@@ -154,8 +154,11 @@ const PostOotd: React.FC = () => {
if (typeof window !== 'undefined') {
Swal.fire({
icon: 'error',
+ iconColor: '#FB3463',
+ confirmButtonText: '확인',
+ confirmButtonColor: '#FB3463',
title: '입력 오류',
- text: '위치와 날짜를 모두 입력해주세요.',
+ text: '지역과 날짜를 모두 입력해주세요.',
});
}
return;
@@ -267,11 +270,11 @@ const PostOotd: React.FC = () => {
return (
-
임시저장
-
+ */}
= ({ postId }) => {
+ const { data: recommendedSpots, isLoading } = useQuery(
+ ['recommendedSpots', postId],
+ () => fetchRecommendedSpots(postId),
+ {
+ enabled: !!postId, // postId가 존재할 때만 호출되도록 설정
+ refetchOnWindowFocus: false,
+ }
+ );
+
+ return (
+
+
추천 장소
+ {isLoading &&
추천 장소를 불러오는 중입니다...
}
+
+ {recommendedSpots?.result && recommendedSpots.result.length > 0 ? (
+
+ {recommendedSpots.result.map((spot: any, index: number) => (
+ -
+
{spot.title}
+ {spot.content}
+ {/* 추천 장소 이미지가 필요하면 아래 코드 주석 해제 */}
+ {spot.imgUrl && spot.imgUrl.length > 0 && (
+
+ )}
+
+ ))}
+
+ ) : (
+ !isLoading &&
추천 장소가 없습니다.
+ )}
+
+ );
+};
+
+export default RecommendedSpot;
\ No newline at end of file
diff --git a/src/components/pages/home/RecommendBoard.tsx b/src/components/pages/home/RecommendBoard.tsx
index ce4ac90e..46cfcfda 100644
--- a/src/components/pages/home/RecommendBoard.tsx
+++ b/src/components/pages/home/RecommendBoard.tsx
@@ -21,6 +21,8 @@ import { colorTicket } from '@/types/board';
import nonheartImg from "@/dummy/heartbin.svg"
import heartImg from "@/dummy/heart.svg";
import moment from "@/dummy/moment.svg"
+import SkeletonRecommendOotdPost from '../ootd/SkeletonRecommendOotdPost';
+import SkeletonRecBoard from './SkeletonRecBoard';
const TagContainer: React.FC = ({ item }) => {
const containerRef = useRef(null);
@@ -79,19 +81,15 @@ const RecommendBoard = () => {
const router = useRouter();
const scrollRef = useRef(null);
const swiperRef = useRef(null);
+ const [showSkeleton, setShowSkeleton] = useState(true);
const { data, isLoading, error } = useQuery(['recommendOotdPost', selectedInterest], () => fetchRecommendBoard(selectedInterest), {
keepPreviousData: true,
});
- console.log(data)
- const totalCount = data?.result.totalCnt;
+ console.log(data, selectedInterest)
+ const totalCount = data?.result?.totalCnt;
+
- useEffect(() => {
- if (userInfo) {
- fetchLikedPosts().then(setLikedPosts);
- fetchUserInterests(); // 관심사 정보 가져오기
- }
- }, [userInfo]);
// 사용자 정보에서 관심사 가져오기
const fetchUserInterests = async () => {
@@ -111,6 +109,13 @@ const RecommendBoard = () => {
}
};
+ useEffect(() => {
+ if (userInfo) {
+ fetchLikedPosts().then(setLikedPosts);
+ fetchUserInterests(); // 관심사 정보 가져오기
+ }
+ }, [userInfo]);
+
// 화면 크기에 따라 itemsPerSlide를 설정하는 함수
const updateItemsPerSlide = () => {
const width = window.innerWidth;
@@ -140,6 +145,17 @@ const RecommendBoard = () => {
}
};
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') { // 클라이언트에서만 실행되도록 체크
+ const delay = setTimeout(() => {
+ setShowSkeleton(false);
+ }, 1000); // 스켈레톤을 1초 동안 유지
+
+ return () => clearTimeout(delay);
+ }
+ }, [isLoading]);
+
const handleDrag = (e: React.MouseEvent) => {
e.preventDefault();
const startX = e.clientX;
@@ -176,7 +192,7 @@ const RecommendBoard = () => {
};
// 로딩 시에는 null을 반환
- if (loading || isLoading) return null;
+ // if (loading || isLoading) return null;
if (error) return null;
console.log(data)
@@ -198,6 +214,12 @@ const RecommendBoard = () => {
}
};
+
+
+ if (loading || showSkeleton || isLoading) {
+ return ;
+ }
+
return (
{!userInfo && (
@@ -332,17 +354,18 @@ const RecommendBoard = () => {
handleOotdItemClick(item.post.id)}>
{window.innerWidth < 500 && (
-
+
-
{
{window.innerWidth < 500 ? (
-
+
{item.post.title}
{bodyText}
@@ -367,7 +390,7 @@ const RecommendBoard = () => {
) : (
{
{item.ticket.departureCode && (
<>
-
{item.ticket.departureCode}
+
{item.ticket.departureCode}
{getTransportImage(item.ticket.transport, item.ticket.ticketColor)}
-
{item.ticket.destinationCode}
+
{item.ticket.destinationCode}
>
)}
@@ -405,9 +428,10 @@ const RecommendBoard = () => {
diff --git a/src/components/pages/home/SkeletonBoard.tsx b/src/components/pages/home/SkeletonBoard.tsx
new file mode 100644
index 00000000..daed9f3f
--- /dev/null
+++ b/src/components/pages/home/SkeletonBoard.tsx
@@ -0,0 +1,60 @@
+import React, { useState, useEffect } from 'react';
+
+const SkeletonBoard: React.FC = () => {
+ const getItemCount = (width: number) => {
+ if (width < 700) {
+ return 2;
+ } else if (width < 1000) {
+ return 3;
+ } else {
+ return 4;
+ }
+ };
+
+ const [itemCount, setItemCount] = useState
(() => {
+ if (typeof window !== 'undefined') {
+ return getItemCount(window.innerWidth);
+ }
+ return 4; // 서버 측에서는 기본값을 반환
+ });
+
+ // 화면 크기에 따라 표시할 아이템 수를 설정하는 함수
+ const updateItemCount = () => {
+ if (typeof window !== 'undefined') {
+ const width = window.innerWidth;
+ setItemCount(getItemCount(width));
+ }
+ };
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ updateItemCount(); // 처음 페이지 로드 시 실행
+ window.addEventListener('resize', updateItemCount); // 창 크기 변경 시 실행
+ return () => window.removeEventListener('resize', updateItemCount); // 이벤트 리스너 제거
+ }
+ }, []);
+
+ return (
+
+
+
+
+ {/* 스켈레톤 카드들 */}
+
+
+ {[...Array(itemCount)].map((_, index) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default SkeletonBoard;
\ No newline at end of file
diff --git a/src/components/pages/home/SkeletonRecBoard.tsx b/src/components/pages/home/SkeletonRecBoard.tsx
new file mode 100644
index 00000000..c0467bdc
--- /dev/null
+++ b/src/components/pages/home/SkeletonRecBoard.tsx
@@ -0,0 +1,60 @@
+import React, { useState, useEffect } from 'react';
+
+const SkeletonRecBoard: React.FC = () => {
+ const getItemCount = (width: number) => {
+ if (width < 700) {
+ return 2;
+ } else if (width < 1000) {
+ return 3;
+ } else {
+ return 4;
+ }
+ };
+
+ const [itemCount, setItemCount] = useState(() => {
+ if (typeof window !== 'undefined') {
+ return getItemCount(window.innerWidth);
+ }
+ return 4; // 서버 측에서는 기본값을 반환
+ });
+
+ // 화면 크기에 따라 표시할 아이템 수를 설정하는 함수
+ const updateItemCount = () => {
+ if (typeof window !== 'undefined') {
+ const width = window.innerWidth;
+ setItemCount(getItemCount(width));
+ }
+ };
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ updateItemCount(); // 처음 페이지 로드 시 실행
+ window.addEventListener('resize', updateItemCount); // 창 크기 변경 시 실행
+ return () => window.removeEventListener('resize', updateItemCount); // 이벤트 리스너 제거
+ }
+ }, []);
+
+ return (
+
+
+
+
+ {/* 스켈레톤 카드들 */}
+
+
+ {[...Array(itemCount)].map((_, index) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default SkeletonRecBoard;
\ No newline at end of file
diff --git a/src/components/pages/home/recentPost.tsx b/src/components/pages/home/recentPost.tsx
index 4eb615f5..ce1c3582 100644
--- a/src/components/pages/home/recentPost.tsx
+++ b/src/components/pages/home/recentPost.tsx
@@ -1,5 +1,5 @@
import Image from 'next/image';
-import React, { ReactNode, useState } from 'react'
+import React, { ReactNode, useEffect, useState } from 'react'
import Link from "next/link";
import nonheartImg from "@/dummy/heartbin.svg"
import heartImg from "@/dummy/heart.svg";
@@ -7,12 +7,13 @@ import moment from "@/dummy/moment.svg"
import { useQuery } from 'react-query';
import { getAllBoardCount, getFollowBoard } from '@/services/board/get/getBoard';
import DefaultImage from '../../../../public/defaultImage.svg';
+import { useUserStore } from '@/store/useUserStore';
+import SkeletonBoard from './SkeletonBoard';
interface HomeRecentProps {
allPosts: number;
setAllPosts: any;
boardData: any;
- userInfo: any;
boardRefetch: any;
PAGE_SIZE: any;
pages: number;
@@ -20,13 +21,17 @@ interface HomeRecentProps {
}
-function RecentPost({ allPosts, setAllPosts, boardData, userInfo, boardRefetch, PAGE_SIZE, pages, setPages }: HomeRecentProps) {
+function RecentPost({ allPosts, setAllPosts, boardData, boardRefetch, PAGE_SIZE, pages, setPages }: HomeRecentProps) {
const [sortOrder, setSortOrder] = useState('최신순');
// const [sortOrders, setSortOrders] = useState('최신순');
- const memberIds = userInfo && userInfo?.memberId;
- const [isClicked, setIsClicked] = useState(false)
+ const [isClicked, setIsClicked] = useState(false);
+ const { userInfo, loading } = useUserStore((state) => ({
+ userInfo: state.userInfo,
+ loading: state.loading,
+ }));
+ const memberIds = userInfo && userInfo?.memberId;
const { data: followData } = useQuery({
queryKey: ['followData'],
queryFn: () => getFollowBoard(memberIds)
@@ -99,12 +104,27 @@ function RecentPost({ allPosts, setAllPosts, boardData, userInfo, boardRefetch,
}, 100)
};
+ const [showSkeleton, setShowSkeleton] = useState(true);
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') { // 클라이언트에서만 실행되도록 체크
+ const delay = setTimeout(() => {
+ setShowSkeleton(false);
+ }, 1000); // 스켈레톤을 1초 동안 유지
+
+ return () => clearTimeout(delay);
+ }
+ }, [loading]);
+
+ if (loading || showSkeleton) {
+ return ;
+ };
+
return (
-
최근 여행 여정을
- 함께 해보세요!
+
트리피의 다양한 여정을 함께 해보세요!
setAllPosts(0)}>전체글
setAllPosts(1)}>팔로잉
@@ -122,7 +142,7 @@ function RecentPost({ allPosts, setAllPosts, boardData, userInfo, boardRefetch,
{allPosts === 0 ? (
-
+
{sortedPosts().map((posts: any, index: number) => {
const BoardId = posts.post.id;
@@ -141,12 +161,12 @@ function RecentPost({ allPosts, setAllPosts, boardData, userInfo, boardRefetch,
return (
{window.innerWidth > 500 ? (
-
+
{posts.post.title}
{bodyText}
@@ -167,7 +187,7 @@ function RecentPost({ allPosts, setAllPosts, boardData, userInfo, boardRefetch,
width={24}
height={24}
alt=""
- className="hidden md:block" // 500px 이상에서만 보이도록 설정
+ className="hidden md:block rounded-[4.5rem] w-[2.4rem] h-[2.4rem]" // 500px 이상에서만 보이도록 설정
/>
{posts.member.nickName}
{/*
{formattedDate} */}
@@ -190,8 +210,8 @@ function RecentPost({ allPosts, setAllPosts, boardData, userInfo, boardRefetch,
:
(
-
-
+
+
- {posts.member.nickName}
+ {posts.member.nickName}
{formattedDate}
-
+
{posts.post.title}
{bodyText}
@@ -238,7 +258,7 @@ function RecentPost({ allPosts, setAllPosts, boardData, userInfo, boardRefetch,
);
})}
-
+
{Array.from({ length: totalPages }, (_, index) => (
{
router.push("/login");
};
- if (!isTabInitialized || isLoading) return null;
- if (loading || isLoading) return null;
+
+ const [showSkeleton, setShowSkeleton] = useState(true);
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') { // 클라이언트에서만 실행되도록 체크
+ const delay = setTimeout(() => {
+ setShowSkeleton(false);
+ }, 1000); // 스켈레톤을 1초 동안 유지
+
+ return () => clearTimeout(delay);
+ }
+}, [isLoading]);
+
+ if (loading || showSkeleton || isLoading) {
+ return ;
+ }
+
return (
@@ -195,20 +213,26 @@ const RecentOotdPost: React.FC = () => {
)}
-
+
+
+ {userInfo && (
+
handleTabChange("ALL")}
>
전체글
- {userInfo && (
-
handleTabChange("FOLLOWING")}
- >
- 팔로잉
-
+
+ )}
+ { userInfo && (
+
handleTabChange('FOLLOWING')}
+ >
+ 팔로잉
+
+
)}
{
/>
+
{/* 게시글이 있을 때와 없을 때 조건부 렌더링 */}
0 ? "grid grid-cols-2 sm-700:grid-cols-3 lg:grid-cols-4 gap-8" : "flex justify-center items-center"}`}
diff --git a/src/components/pages/ootd/RecommendOotdPost.tsx b/src/components/pages/ootd/RecommendOotdPost.tsx
index 1535b161..d5b28218 100644
--- a/src/components/pages/ootd/RecommendOotdPost.tsx
+++ b/src/components/pages/ootd/RecommendOotdPost.tsx
@@ -18,6 +18,7 @@ import 'swiper/swiper-bundle.css';
import SwiperLeftButton from '../../../../public/SwiperLeftBtn.svg';
import SwiperRightButton from '../../../../public/SwiperRightBtn.svg';
import { useUserStore } from '@/store/useUserStore'; // Zustand 전역 상태 사용
+import SkeletonRecommendOotdPost from './SkeletonRecommendOotdPost';
const TagContainer: React.FC
= ({ item }) => {
const containerRef = useRef(null);
@@ -62,7 +63,6 @@ const TagContainer: React.FC = ({ item }) => {
const RecommendOotdPost = () => {
- // Zustand에서 유저 정보와 로딩 상태를 가져옵니다
const { userInfo, loading } = useUserStore((state) => ({
userInfo: state.userInfo,
loading: state.loading,
@@ -77,11 +77,16 @@ const RecommendOotdPost = () => {
const scrollRef = useRef(null);
const swiperRef = useRef(null);
- const { data, isLoading, error } = useQuery(['recommendOotdPost', selectedInterest], () => fetchRecommendOotdPost(selectedInterest), {
- keepPreviousData: true,
- });
+ const { data, isLoading } = useQuery(
+ ['recommendOotdPost', selectedInterest],
+ () => fetchRecommendOotdPost(selectedInterest),
+ {
+ keepPreviousData: true
+ }
+ );
- const totalCount = data?.result.totalCnt;
+
+ const totalCount = data?.result?.totalCnt ?? 0;
useEffect(() => {
if (userInfo) {
@@ -110,26 +115,29 @@ const RecommendOotdPost = () => {
}
};
- // 화면 크기에 따라 itemsPerSlide를 설정하는 함수
const updateItemsPerSlide = () => {
- const width = window.innerWidth;
- if (width < 700) {
- setItemsPerSlide(2);
- } else if (width < 1000) {
- setItemsPerSlide(3);
- } else {
- setItemsPerSlide(4);
+ if (typeof window !== 'undefined') {
+ const width = window.innerWidth;
+ if (width < 700) {
+ setItemsPerSlide(2);
+ } else if (width < 1000) {
+ setItemsPerSlide(3);
+ } else {
+ setItemsPerSlide(4);
+ }
}
};
useEffect(() => {
- // 페이지가 로드될 때, 그리고 창 크기가 변경될 때마다 슬라이드 개수 업데이트
- updateItemsPerSlide();
- window.addEventListener('resize', updateItemsPerSlide);
-
- return () => {
- window.removeEventListener('resize', updateItemsPerSlide);
- };
+ if (typeof window !== 'undefined') {
+ // 페이지가 로드될 때, 그리고 창 크기가 변경될 때마다 슬라이드 개수 업데이트
+ updateItemsPerSlide();
+ window.addEventListener('resize', updateItemsPerSlide);
+
+ return () => {
+ window.removeEventListener('resize', updateItemsPerSlide);
+ };
+ }
}, []);
const handleScroll = (direction: string) => {
@@ -174,10 +182,6 @@ const RecommendOotdPost = () => {
router.push("/editProfile");
};
- // 로딩 시에는 null을 반환
- if (loading || isLoading) return null;
- if (error) return null;
-
// 데이터 슬라이드 생성
const slides = [];
if (data?.result?.ootdList) {
@@ -196,40 +200,54 @@ const RecommendOotdPost = () => {
}
};
+ const [showSkeleton, setShowSkeleton] = useState(true);
+ useEffect(() => {
+ if (typeof window !== 'undefined') { // 클라이언트에서만 실행되도록 체크
+ const delay = setTimeout(() => {
+ setShowSkeleton(false);
+ }, 1000); // 스켈레톤을 1초 동안 유지
+
+ return () => clearTimeout(delay);
+ }
+ }, [isLoading]);
+
+ if (loading || showSkeleton || isLoading) {
+ return ;
+ }
+
return (
- {!userInfo && (
-
- 관심분야에 따른 OOTD를 확인해보세요!
-
+ {!userInfo && (
+
+ 관심분야에 따른 OOTD를 확인해보세요!
+
)}
- {userInfo && (
-
- {userName}님, 이런 스타일 어때요?
-
+ {userInfo && (
+
+ {userName}님, 이런 스타일 어때요?
+
)}
-
{!userInfo && (
-
handleScroll('left')}
- style={{
- width: '25px',
- height: '25px',
- position: 'absolute',
- top: '50%',
- transform: 'translateY(-50%)',
- left: '-30px',
- zIndex: 999,
- }}
- />
+ handleScroll('left')}
+ style={{
+ width: '25px',
+ height: '25px',
+ position: 'absolute',
+ top: '50%',
+ transform: 'translateY(-50%)',
+ left: '-30px',
+ zIndex: 999,
+ }}
+ />
)}
- {
{filteredInterests.map(interest => (
- setSelectedInterest(interest)}
>
{interest}
@@ -248,37 +266,37 @@ const RecommendOotdPost = () => {
))}
- {userInfo && (
+ {userInfo && (
-
관심 키워드 설정
-
-
+
관심 키워드 설정
+
+
)}
+
-
- {!userInfo && (
-
handleScroll('right')}
- style={{
- width: '25px',
- height: '25px',
- position: 'absolute',
- top: '50%',
- transform: 'translateY(-50%)',
- right: '-30px',
- zIndex: 999,
- }}
- />
+ {!userInfo && (
+ handleScroll('right')}
+ style={{
+ width: '25px',
+ height: '25px',
+ position: 'absolute',
+ top: '50%',
+ transform: 'translateY(-50%)',
+ right: '-30px',
+ zIndex: 999,
+ }}
+ />
)}
{itemsPerSlide < totalCount && (
@@ -291,6 +309,7 @@ const RecommendOotdPost = () => {
className="absolute left-[-30px] top-[60%] transform -translate-y-1/2 z-10"
/>
)}
+
{
{item.member.nickName}
@@ -362,8 +381,7 @@ const RecommendOotdPost = () => {
))
) : (
-
-
+
{selectedInterest} {"\u00A0"}관련 OOTD가 없어요!
diff --git a/src/components/pages/ootd/SkeletonRecentOotdPost.tsx b/src/components/pages/ootd/SkeletonRecentOotdPost.tsx
new file mode 100644
index 00000000..a4795773
--- /dev/null
+++ b/src/components/pages/ootd/SkeletonRecentOotdPost.tsx
@@ -0,0 +1,61 @@
+import React, { useState, useEffect } from 'react';
+
+const SkeletonRecentOotdPost: React.FC = () => {
+ const getItemCount = (width: number) => {
+ if (width < 700) {
+ return 2;
+ } else if (width < 1000) {
+ return 3;
+ } else {
+ return 4;
+ }
+ };
+
+ const [itemCount, setItemCount] = useState
(() => {
+ if (typeof window !== 'undefined') {
+ return getItemCount(window.innerWidth);
+ }
+ return 4; // 서버 측에서는 기본값을 반환
+ });
+
+ // 화면 크기에 따라 표시할 아이템 수를 설정하는 함수
+ const updateItemCount = () => {
+ if (typeof window !== 'undefined') {
+ const width = window.innerWidth;
+ setItemCount(getItemCount(width));
+ }
+ };
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ updateItemCount(); // 처음 페이지 로드 시 실행
+ window.addEventListener('resize', updateItemCount); // 창 크기 변경 시 실행
+ return () => window.removeEventListener('resize', updateItemCount); // 이벤트 리스너 제거
+ }
+ }, []);
+
+
+ return (
+
+
+
+
+ {/* 스켈레톤 카드들 */}
+
+
+ {[...Array(itemCount)].map((_, index) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default SkeletonRecentOotdPost;
\ No newline at end of file
diff --git a/src/components/pages/ootd/SkeletonRecommendOotdPost.tsx b/src/components/pages/ootd/SkeletonRecommendOotdPost.tsx
new file mode 100644
index 00000000..db6c6ef8
--- /dev/null
+++ b/src/components/pages/ootd/SkeletonRecommendOotdPost.tsx
@@ -0,0 +1,60 @@
+import React, { useState, useEffect } from 'react';
+
+const SkeletonRecommendOotdPost: React.FC = () => {
+ const getItemCount = (width: number) => {
+ if (width < 700) {
+ return 2;
+ } else if (width < 1000) {
+ return 3;
+ } else {
+ return 4;
+ }
+ };
+ const [itemCount, setItemCount] = useState(() => {
+ if (typeof window !== 'undefined') {
+ return getItemCount(window.innerWidth);
+ }
+ return 4; // 서버 측에서는 기본값을 반환
+ });
+
+ // 화면 크기에 따라 표시할 아이템 수를 설정하는 함수
+ const updateItemCount = () => {
+ if (typeof window !== 'undefined') {
+ const width = window.innerWidth;
+ setItemCount(getItemCount(width));
+ }
+ };
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ updateItemCount(); // 처음 페이지 로드 시 실행
+ window.addEventListener('resize', updateItemCount); // 창 크기 변경 시 실행
+ return () => window.removeEventListener('resize', updateItemCount); // 이벤트 리스너 제거
+ }
+ }, []);
+
+
+ return (
+
+
+
+
+ {/* 스켈레톤 카드들 */}
+
+
+ {[...Array(itemCount)].map((_, index) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default SkeletonRecommendOotdPost;
\ No newline at end of file
diff --git a/src/components/search/searchBar.tsx b/src/components/search/searchBar.tsx
index 8ffb8238..853e516a 100644
--- a/src/components/search/searchBar.tsx
+++ b/src/components/search/searchBar.tsx
@@ -23,8 +23,10 @@ const SearchBar: React.FC = () => {
type="text"
value={query}
onChange={handleInputChange}
+
placeholder="찾으시는 게시물을 입력하세요"
className="w-full min-w-[100px] h-[32px] pl-4 pr-10 text-gray-800 border-none rounded-[16px] outline-none focus:ring-2 focus:ring-gray-300 text-[1.6rem]"
+
style={{
backgroundColor: "#F5F5F5",
}}
@@ -34,13 +36,14 @@ const SearchBar: React.FC = () => {
}
}}
/>
-
+
);
diff --git a/src/components/shared/header/Header.tsx b/src/components/shared/header/Header.tsx
index 556a0bd0..ce0aaba0 100644
--- a/src/components/shared/header/Header.tsx
+++ b/src/components/shared/header/Header.tsx
@@ -71,6 +71,9 @@ const Header = () => {
const handleModalToggle = () => {
setModalVisible(!modalVisible);
+ if (isDropdownOpen) {
+ setIsDropdownOpen(false);
+ }
};
const handleNotificationsToggle = () => {
@@ -138,12 +141,12 @@ const Header = () => {
<>
{userInfo && accessToken ? (
-
+
@@ -154,7 +157,7 @@ const Header = () => {
)}
-
+
{ handleModalToggle(); setIsDropdownOpen(false); }}>
{
userInfo={userInfo}
style={{
position: "absolute",
- bottom: "-260px",
- left: "-290px",
+ bottom: "-200px",
+ left: "-210px",
}}
handleLogout={async () => {
Cookies.remove("accessToken");
@@ -195,13 +198,18 @@ const Header = () => {
{isDropdownOpen && (
setIsDropdownOpen(true)} // 드롭다운에 마우스가 올라가면 열려있도록 유지
+
+ className="absolute w-[27rem] mt-[1rem] ml-[1rem] top-[3.6rem] rounded-[0.8rem] bg-white shadowalltop rounded-lg animate-dropdown z-20"
+ style={{ opacity: 0, transform: 'translateY(-10px)' }}
+ onMouseEnter={() => {
+ setIsDropdownOpen(true);
+
+ }} // 드롭다운에 마우스가 올라가면 열려있도록 유지
+
onMouseLeave={() => setIsDropdownOpen(false)}
>
-
+
{
alt=""
/>
-
- 블로그 티켓 글쓰기
-
-
- 여행에서 겪었던 이야기를 기록해 보세요.
-
+
+ 블로그 티켓 글쓰기
+ 여행에서 겪었던 이야기를 기록해 보세요.
+
-
+
{
alt=""
/>
-
- OOTD 글쓰기
-
-
- 여행 중 나의 특별한 OOTD를 공유해보세요.
-
+
+ OOTD 글쓰기
+ 여행 중 나의 특별한 OOTD를 공유해보세요.
+
@@ -250,7 +254,7 @@ const Header = () => {
)}
setIsDropdownOpen(true)}
+ onMouseEnter={() => { setIsDropdownOpen(true); setModalVisible(false); }}
>
글쓰기
diff --git a/src/components/testEditor/textEditor2.tsx b/src/components/testEditor/textEditor2.tsx
index c6199003..2c404dba 100644
--- a/src/components/testEditor/textEditor2.tsx
+++ b/src/components/testEditor/textEditor2.tsx
@@ -7,7 +7,7 @@ interface editorProps {
setPostRequest: any;
}
-const inputAPI = process.env.INPUT_TEXT_API_KEY;
+const inputAPI = process.env.NEXT_PUBLIC_INPUT_TEXT_API_KEY
const MyTinyMCEEditor = ({ postRequest, setPostRequest }: editorProps) => {
@@ -46,7 +46,7 @@ const MyTinyMCEEditor = ({ postRequest, setPostRequest }: editorProps) => {
-
+
-
+
-
+
{userInfo.nickName}
-
+
{userInfo.email}
-
+
팔로워 {userData?.followerCnt}
-
+
-
+
팔로잉 {userData?.followingCnt}
-
+
-
+
{userInfo.socialType === "naver" && (
-
+
마이페이지
로그아웃
@@ -127,12 +127,12 @@ const UserModal: React.FC<
-
-
-
+
+
+
{userInfo.blogName}
-
+
{userInfo.blogIntroduce}
diff --git a/src/dummy/backbutton.svg b/src/dummy/backbutton.svg
new file mode 100644
index 00000000..fbdcd95f
--- /dev/null
+++ b/src/dummy/backbutton.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/services/ootd.ts/ootdGet.ts b/src/services/ootd.ts/ootdGet.ts
index 01f1855e..45113110 100644
--- a/src/services/ootd.ts/ootdGet.ts
+++ b/src/services/ootd.ts/ootdGet.ts
@@ -210,15 +210,29 @@ export const fetchRecommendOotdPost = async (interestType: string) => {
try {
const response = await axios.get(`${backendUrl}/api/recommend/interest`, {
params: {
- interestedType: interestType, // No need to encode here
+ interestedType: interestType,
postType: 'OOTD'
}
});
console.log('Received data:', response.data);
- return response.data;
+ return response.data; // Always return data
} catch (error) {
console.error(`Error fetching OOTD posts for interest ${interestType}:`, error);
- throw error;
+
+ if (axios.isAxiosError(error)) {
+ // AxiosError 타입 체크 후 처리
+ const message = error.response?.data?.message || 'Unknown error occurred';
+ return {
+ isSuccess: false,
+ message: message
+ };
+ } else {
+ // 일반 오류 처리
+ return {
+ isSuccess: false,
+ message: 'Error occurred while fetching data'
+ };
+ }
}
};
diff --git a/src/utils/getCroppedImg.ts b/src/utils/getCroppedImg.ts
new file mode 100644
index 00000000..c3d579f6
--- /dev/null
+++ b/src/utils/getCroppedImg.ts
@@ -0,0 +1,43 @@
+import { Area } from "react-easy-crop";
+
+// utils/cropImage.ts
+export const getCroppedImg = (imageSrc: string, cropArea: Area): Promise
=> {
+ const image = new Image();
+ image.src = imageSrc;
+
+ return new Promise((resolve, reject) => {
+ image.onload = () => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ if (!ctx) {
+ return reject('Canvas context not available');
+ }
+
+ canvas.width = cropArea.width;
+ canvas.height = cropArea.height;
+
+ ctx.drawImage(
+ image,
+ cropArea.x,
+ cropArea.y,
+ cropArea.width,
+ cropArea.height,
+ 0,
+ 0,
+ cropArea.width,
+ cropArea.height
+ );
+
+ canvas.toBlob((blob) => {
+ if (blob) {
+ resolve(blob);
+ } else {
+ reject('Blob generation failed');
+ }
+ }, 'image/jpeg');
+ };
+
+ image.onerror = reject;
+ });
+ };
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 58efec60..af8255d6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2622,6 +2622,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+normalize-wheel@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz"
+ integrity sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==
+
oauth@^0.9.15:
version "0.9.15"
resolved "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz"
@@ -3017,7 +3022,7 @@ react-datepicker@^7.3.0:
prop-types "^15.7.2"
react-onclickoutside "^6.13.0"
-"react-dom@^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", "react-dom@^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^15.5.x || ^16.x || ^17.x || ^18.x", "react-dom@^16.8 || ^17 || ^18", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.5 || ^17.0.0 || ^18.0.0", "react-dom@^16.9.0 || ^17 || ^18", "react-dom@^17.0.2 || ^18", react-dom@^18, react-dom@^18.0.0, "react-dom@^18.0.0 || ^17.0.1 || ^16.7.0", react-dom@^18.2.0, react-dom@>=16.14.0, react-dom@>=16.6.0, react-dom@>=16.8.0:
+"react-dom@^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", "react-dom@^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^15.5.x || ^16.x || ^17.x || ^18.x", "react-dom@^16.8 || ^17 || ^18", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.5 || ^17.0.0 || ^18.0.0", "react-dom@^16.9.0 || ^17 || ^18", "react-dom@^17.0.2 || ^18", react-dom@^18, react-dom@^18.0.0, "react-dom@^18.0.0 || ^17.0.1 || ^16.7.0", react-dom@^18.2.0, react-dom@>=16.14.0, react-dom@>=16.4.0, react-dom@>=16.6.0, react-dom@>=16.8.0:
version "18.2.0"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
@@ -3025,6 +3030,14 @@ react-datepicker@^7.3.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
+react-easy-crop@^5.0.8:
+ version "5.0.8"
+ resolved "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.0.8.tgz"
+ integrity sha512-KjulxXhR5iM7+ATN2sGCum/IyDxGw7xT0dFoGcqUP+ysaPU5Ka7gnrDa2tUHFHUoMNyPrVZ05QA+uvMgC5ym/g==
+ dependencies:
+ normalize-wheel "^1.0.1"
+ tslib "^2.0.1"
+
react-hook-form@^7.51.3:
version "7.51.3"
resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.3.tgz"
@@ -3035,6 +3048,11 @@ react-icons@^5.3.0:
resolved "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz"
integrity sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==
+react-image-crop@^11.0.7:
+ version "11.0.7"
+ resolved "https://registry.npmjs.org/react-image-crop/-/react-image-crop-11.0.7.tgz"
+ integrity sha512-ZciKWHDYzmm366JDL18CbrVyjnjH0ojufGDmScfS4ZUqLHg4nm6ATY+K62C75W4ZRNt4Ii+tX0bSjNk9LQ2xzQ==
+
react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
@@ -3122,7 +3140,7 @@ react-transition-group@^4.3.0, react-transition-group@^4.4.5:
loose-envify "^1.4.0"
prop-types "^15.6.2"
-react@*, "react@^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", "react@^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^15.5.x || ^16.x || ^17.x || ^18.x", "react@^16.8 || ^17 || ^18", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", "react@^16.8.3 || ^17 || ^18", "react@^16.8.5 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17 || ^18", "react@^17.0.2 || ^18", react@^18, react@^18.0.0, "react@^18.0.0 || ^17.0.1 || ^16.7.0", react@^18.2.0, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=0.14.0, react@>=15.0.0, react@>=16.13.1, react@>=16.14.0, react@>=16.6.0, react@>=16.8, react@>=16.8.0:
+react@*, "react@^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", "react@^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^15.5.x || ^16.x || ^17.x || ^18.x", "react@^16.8 || ^17 || ^18", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", "react@^16.8.3 || ^17 || ^18", "react@^16.8.5 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17 || ^18", "react@^17.0.2 || ^18", react@^18, react@^18.0.0, "react@^18.0.0 || ^17.0.1 || ^16.7.0", react@^18.2.0, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=0.14.0, react@>=15.0.0, react@>=16.13.1, react@>=16.14.0, react@>=16.4.0, react@>=16.6.0, react@>=16.8, react@>=16.8.0:
version "18.2.0"
resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
@@ -3672,7 +3690,7 @@ tsconfig-paths@^3.15.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
-tslib@^2.4.0:
+tslib@^2.0.1, tslib@^2.4.0:
version "2.6.2"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==