로그인 중...
;
+};
diff --git a/judy/Week6/LP/src/pages/HomePage.tsx b/judy/Week6/LP/src/pages/HomePage.tsx
new file mode 100644
index 00000000..80724d26
--- /dev/null
+++ b/judy/Week6/LP/src/pages/HomePage.tsx
@@ -0,0 +1,119 @@
+import { useEffect, useRef, useState } from "react";
+import { apiClient } from "../api/apiClient";
+import { useInfiniteQuery } from "@tanstack/react-query";
+import { LpCard } from "../components/homePage/LpCard";
+import { LpCardSkeleton } from "../components/homePage/LpCardSkeleton";
+
+export interface IFLpContent {
+ id: number;
+ content: string;
+ thumbnail: string;
+ title: string;
+ likeCount: number;
+ created: string;
+}
+
+export const HomePage = () => {
+ const [order, setOrder] = useState<"asc" | "desc">("desc");
+ const [isScrollAtBottom, setIsScrollAtBottom] = useState(false);
+
+ const getLpList = async ({ pageParam = 0 }) => {
+ const response = await apiClient.get("/lps", {
+ params: {
+ cursor: pageParam,
+ limit: 25,
+ order,
+ },
+ });
+ return response.data;
+ };
+
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
+ useInfiniteQuery({
+ queryKey: ["lpList", order],
+ queryFn: getLpList,
+ initialPageParam: 0,
+ getNextPageParam: (lastPage) =>
+ lastPage.data.hasNext ? lastPage.data.nextCursor : undefined,
+ });
+
+ // 스크롤 이벤트
+ useEffect(() => {
+ const handleScroll = () => {
+ // 화면 높이 + 스크롤된 높이가 전체 문서 높이에 가까우면
+ const isBottom =
+ window.innerHeight + window.scrollY >=
+ document.documentElement.scrollHeight - 100;
+
+ setIsScrollAtBottom(isBottom);
+ };
+
+ window.addEventListener("scroll", handleScroll);
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, []);
+
+ // 스크롤이 하단에 도달하면 다음 페이지 로드
+ useEffect(() => {
+ if (isScrollAtBottom && hasNextPage && !isFetchingNextPage) {
+ fetchNextPage();
+ // 페이지 로드 시작 후 상태 초기화
+ setIsScrollAtBottom(false);
+ }
+ }, [isScrollAtBottom, hasNextPage, isFetchingNextPage, fetchNextPage]);
+
+ return (
+
+
+ {
+ if (order !== "asc") {
+ setOrder("asc");
+ }
+ }}
+ >
+ 오래된 순
+ {" "}
+ {
+ if (order !== "desc") {
+ setOrder("desc");
+ }
+ }}
+ >
+ 최신순
+
+
+
+ {!data
+ ? Array.from({ length: 20 }).map((_, i) => )
+ : data?.pages.map((page) =>
+ page.data.data.map((item) => {
+ const content = {
+ id: item.id,
+ content: item.content,
+ thumbnail: item.thumbnail,
+ title: item.title,
+ likeCount: item.likes.length,
+ created: item.createdAt,
+ };
+ return ;
+ })
+ )}
+
+
+ {isFetchingNextPage && (
+
+ {Array.from({ length: 10 }).map((_, i) => (
+
+ ))}
+
+ )}
+
+ );
+};
diff --git a/judy/Week6/LP/src/pages/LoginPage.tsx b/judy/Week6/LP/src/pages/LoginPage.tsx
new file mode 100644
index 00000000..c6ab3356
--- /dev/null
+++ b/judy/Week6/LP/src/pages/LoginPage.tsx
@@ -0,0 +1,119 @@
+import clsx from "clsx";
+
+import { IoIosArrowBack } from "react-icons/io";
+import { Link, useNavigate } from "react-router-dom";
+import { UserSigninInfomation, validateSignin } from "../utils/regex";
+import useForm from "../hooks/useForm";
+import { apiClient } from "../api/apiClient";
+import { LoginRequest } from "../types/LoginType";
+import { useEffect } from "react";
+
+export const LoginPage = () => {
+ const navigate = useNavigate();
+ const accessToken = window.localStorage.getItem("accessToken");
+ const { values, errors, touched, getInputProps } =
+ useForm