로딩 중...
+
+
마이페이지
+ {user ? (
+ editMode ? (
+
+
fileInputRef.current?.click()}
+ >
+ {avatarInput ? (
+

+ ) : (
+ "👤"
+ )}
+
{
+ const file = e.target.files?.[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = () => {
+ if (reader.result)
+ setAvatarInput(reader.result.toString());
+ };
+ reader.readAsDataURL(file);
+ }
+ }}
+ />
+
+
setNameInput(e.target.value)}
+ />
+
setBioInput(e.target.value)}
+ />
+
+
+
+
+
+ ) : (
+
+ {user.avatar ? (
+

+ ) : (
+
+ 👤
+
+ )}
+
+
{user.name}
+
{user.email}
+
+
+
+ )
+ ) : (
+
로딩 중...
+ )}
-
-
-
-
+
+
+
+
);
};
-export default Mypage;
\ No newline at end of file
+export default Mypage;
diff --git a/LP/src/pages/SearchResult.tsx b/LP/src/pages/SearchResult.tsx
new file mode 100644
index 0000000..d82217a
--- /dev/null
+++ b/LP/src/pages/SearchResult.tsx
@@ -0,0 +1,177 @@
+import { useSearchParams } from "react-router-dom";
+import { useState, useEffect } from "react";
+import { useInfiniteQuery } from "@tanstack/react-query";
+import api from "../api/axios";
+import LPCard from "../components/LPCard";
+import { useDebounce } from "../hooks/useDebounce";
+import { useThrottle } from "../hooks/useThrottle";
+import { FiSearch } from "react-icons/fi";
+import { BiSortUp, BiSortDown } from "react-icons/bi";
+
+const SearchResult = () => {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const search = searchParams.get("search") || "";
+ const sort = searchParams.get("sort") || "desc";
+
+ const [inputValue, setInputValue] = useState(search);
+ const [searchType, setSearchType] = useState<"title" | "tag">(
+ searchParams.get("type") === "tag" ? "tag" : "title"
+ );
+ const [sortOrder, setSortOrder] = useState<"latest" | "oldest">(
+ sort === "asc" ? "oldest" : "latest"
+ );
+ const debouncedSearch = useDebounce(inputValue, 500);
+
+ const handleSearch = () => {
+ const newParams = new URLSearchParams();
+ newParams.set("search", inputValue);
+ newParams.set("sort", sortOrder === "latest" ? "desc" : "asc");
+ newParams.set("type", searchType);
+ setSearchParams(newParams);
+ };
+
+ useEffect(() => {
+ const newParams = new URLSearchParams();
+ newParams.set("search", debouncedSearch);
+ newParams.set("sort", sortOrder === "latest" ? "desc" : "asc");
+ newParams.set("type", searchType);
+ setSearchParams(newParams);
+ }, [debouncedSearch, sortOrder, searchType]);
+
+ const {
+ data,
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ isLoading,
+ isError,
+ } = useInfiniteQuery({
+ queryKey: ["search", debouncedSearch, sortOrder, searchType],
+ queryFn: async ({ pageParam = 0 }) => {
+ console.log("fetching LPs...");
+ const res = await api.get("/lps", {
+ params: {
+ search: debouncedSearch,
+ order: sortOrder === "latest" ? "desc" : "asc",
+ type: searchType,
+ cursor: pageParam,
+ limit: 12,
+ },
+ });
+ console.log(res.data);
+ return res.data.data;
+ },
+ getNextPageParam: (lastPage) => {
+ return lastPage.hasNext ? lastPage.nextCursor : undefined;
+ },
+ });
+
+ const throttledFetchNext = useThrottle(() => {
+ if (hasNextPage && !isFetchingNextPage) {
+ fetchNextPage();
+ }
+ }, 3000);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ const scrollTop = window.scrollY;
+ const viewportHeight = window.innerHeight;
+ const fullHeight = document.documentElement.scrollHeight;
+
+ if (scrollTop + viewportHeight >= fullHeight - 300) {
+ throttledFetchNext();
+ }
+ };
+
+ window.addEventListener("scroll", handleScroll);
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, [throttledFetchNext, hasNextPage, isFetchingNextPage]);
+
+ return (
+
+
🔍 검색 결과
+
+
+
+
+
+
setInputValue(e.target.value)}
+ className="mb-4 w-full px-4 py-2 rounded-md bg-[#2e2e2e] text-white"
+ placeholder="검색어를 입력하세요"
+ />
+ {isLoading ? (
+
검색 중...
+ ) : isError ? (
+
+ 검색 결과를 불러오지 못했습니다.
+
+ ) : (
+ <>
+
+ {data?.pages.flatMap((page) =>
+ page.data.map((lp: any) => {
+ return (
+
+
(window.location.href = `/lp/${lp.id}`)}
+ />
+
+ {lp.tags.map((tag: any) => (
+
+ #{tag.name}
+
+ ))}
+
+
+ );
+ })
+ )}
+
+ >
+ )}
+
+ );
+};
+
+export default SearchResult;
diff --git a/LP/src/pages/SignupPage.tsx b/LP/src/pages/SignupPage.tsx
index 5e1a607..f49e9f5 100644
--- a/LP/src/pages/SignupPage.tsx
+++ b/LP/src/pages/SignupPage.tsx
@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
-import { Link, useLocation } from "react-router-dom";
+import { Link, useLocation, useNavigate } from "react-router-dom";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -34,6 +34,7 @@ const SignupPage = () => {
const [confirmPasswordVisible, setConfirmPasswordVisible] = useState(false); // 추가
const [confirmError, setConfirmError] = useState("");
const [nickname, setNickname] = useState("");
+ const navigate = useNavigate();
const {
register,
handleSubmit,
@@ -81,16 +82,17 @@ const SignupPage = () => {
const handleSignup = async () => {
try {
const res = await signup({
- name: nickname, // nickname을 name으로 매핑
+ name: nickname, // nickname을 name으로 매핑
email: getValues("email"),
password: getValues("password"),
- bio: "", // 선택값: 필요 시 다른 입력 필드로 받아도 됨
- avatar: "", // 선택값: 추후 URL을 받을 수 있음
+ bio: "", // 선택값: 필요 시 다른 입력 필드로 받아도 됨
+ avatar: "", // 선택값: 추후 URL을 받을 수 있음
});
const { accessToken, refreshToken } = res.data.data;
setAccessToken(accessToken);
setRefreshToken(refreshToken);
alert("회원가입 완료!");
+ navigate("/login");
} catch (err: any) {
alert("회원가입 실패: " + (err.response?.data?.message || err.message));
}
@@ -157,7 +159,8 @@ const SignupPage = () => {
{!!errors.password && !!touchedFields.password && (
-
diff --git a/LP/yarn.lock b/LP/yarn.lock
index 6567712..7b04e70 100644
--- a/LP/yarn.lock
+++ b/LP/yarn.lock
@@ -1407,11 +1407,21 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
+lodash.debounce@^4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
+ integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
+
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+lucide-react@^0.510.0:
+ version "0.510.0"
+ resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.510.0.tgz#933b19d893b30ac5cb355e4b91eeefc13088caa3"
+ integrity sha512-p8SQRAMVh7NhsAIETokSqDrc5CHnDLbV29mMnzaXx+Vc/hnqQzwI2r0FMWCcoTXnbw2KEjy48xwpGdEL+ck06Q==
+
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz"