Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/web/src/apis/Auth/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export interface EmailLoginRequest {
password: string;
}

export type AuthRedirectOptions = {
redirectPath?: string;
};

export interface EmailVerificationResponse {
signUpToken: string;
}
Expand Down
9 changes: 5 additions & 4 deletions apps/web/src/apis/Auth/postAppleAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import type { AxiosError } from "axios";
import { useRouter } from "next/navigation";
import { showIconToast } from "@/lib/toast/showIconToast";
import useAuthStore from "@/lib/zustand/useAuthStore";
import { type AppleAuthRequest, type AppleAuthResponse, authApi } from "./api";
import { buildSignUpPath, getCommunityRedirectOrFallback } from "@/utils/authRedirect";
import { type AppleAuthRequest, type AppleAuthResponse, type AuthRedirectOptions, authApi } from "./api";

/**
* @description 애플 로그인을 위한 useMutation 커스텀 훅
*/
const usePostAppleAuth = () => {
const usePostAppleAuth = ({ redirectPath }: AuthRedirectOptions = {}) => {
const router = useRouter();

return useMutation<AppleAuthResponse, AxiosError, AppleAuthRequest>({
Expand All @@ -23,11 +24,11 @@ const usePostAppleAuth = () => {
showIconToast("logo", "로그인에 성공했습니다.");

setTimeout(() => {
router.push("/");
router.push(getCommunityRedirectOrFallback(redirectPath));
}, 100);
} else {
// 새로운 회원일 시 - 회원가입 페이지로 이동
router.push(`/sign-up?token=${data.signUpToken}`);
router.push(buildSignUpPath({ signUpToken: data.signUpToken, redirectPath }));
}
},
onError: () => {
Expand Down
7 changes: 4 additions & 3 deletions apps/web/src/apis/Auth/postEmailLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import type { AxiosError } from "axios";
import { useRouter } from "next/navigation";
import { showIconToast } from "@/lib/toast/showIconToast";
import useAuthStore from "@/lib/zustand/useAuthStore";
import { authApi, type EmailLoginRequest, type EmailLoginResponse } from "./api";
import { getCommunityRedirectOrFallback } from "@/utils/authRedirect";
import { type AuthRedirectOptions, authApi, type EmailLoginRequest, type EmailLoginResponse } from "./api";

/**
* @description 이메일 로그인을 위한 useMutation 커스텀 훅
*/
const usePostEmailAuth = () => {
const usePostEmailAuth = ({ redirectPath }: AuthRedirectOptions = {}) => {
const { setAccessToken } = useAuthStore();
const router = useRouter();

Expand All @@ -27,7 +28,7 @@ const usePostEmailAuth = () => {
// Zustand persist middleware가 localStorage에 저장할 시간을 보장
// 토큰 저장 후 리다이렉트하여 타이밍 이슈 방지
setTimeout(() => {
router.push("/");
router.push(getCommunityRedirectOrFallback(redirectPath));
}, 100);
},
});
Expand Down
9 changes: 5 additions & 4 deletions apps/web/src/apis/Auth/postKakaoAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import type { AxiosError } from "axios";
import { useRouter } from "next/navigation";
import { showIconToast } from "@/lib/toast/showIconToast";
import useAuthStore from "@/lib/zustand/useAuthStore";
import { authApi, type KakaoAuthRequest, type KakaoAuthResponse } from "./api";
import { buildSignUpPath, getCommunityRedirectOrFallback } from "@/utils/authRedirect";
import { type AuthRedirectOptions, authApi, type KakaoAuthRequest, type KakaoAuthResponse } from "./api";

/**
* @description 카카오 로그인을 위한 useMutation 커스텀 훅
*/
const usePostKakaoAuth = () => {
const usePostKakaoAuth = ({ redirectPath }: AuthRedirectOptions = {}) => {
const { setAccessToken } = useAuthStore();
const router = useRouter();

Expand All @@ -23,11 +24,11 @@ const usePostKakaoAuth = () => {

showIconToast("logo", "로그인에 성공했습니다.");
setTimeout(() => {
router.push("/");
router.push(getCommunityRedirectOrFallback(redirectPath));
}, 100);
} else {
// 새로운 회원일 시 - 회원가입 페이지로 이동
router.push(`/sign-up?token=${data.signUpToken}`);
router.push(buildSignUpPath({ signUpToken: data.signUpToken, redirectPath }));
}
},
onError: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { useGetPostList } from "@/apis/community";
import { COMMUNITY_INITIAL_CATEGORY } from "@/apis/community/postListQuery";
import ButtonTab from "@/components/ui/ButtonTab";
import { COMMUNITY_BOARDS, COMMUNITY_CATEGORIES } from "@/constants/community";
import useAuthStore from "@/lib/zustand/useAuthStore";
import useReportedPostsStore from "@/lib/zustand/useReportedPostsStore";
import { buildLoginPathWithRedirect } from "@/utils/authRedirect";
import { CommunityPostListSkeleton } from "./CommunityPageSkeleton";
import CommunityRegionSelector from "./CommunityRegionSelector";
import PostCards from "./PostCards";
Expand All @@ -22,6 +24,10 @@ const CommunityPageContent = ({ boardCode }: CommunityPageContentProps) => {
const reportedPostIds = useReportedPostsStore((state) => state.reportedPostIds);
const blockedUserIds = useReportedPostsStore((state) => state.blockedUserIds);
const blockedPostIds = useReportedPostsStore((state) => state.blockedPostIds);
const accessToken = useAuthStore((state) => state.accessToken);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const isInitialized = useAuthStore((state) => state.isInitialized);
const refreshStatus = useAuthStore((state) => state.refreshStatus);

const { data: posts = [], isPending } = useGetPostList({
boardCode,
Expand Down Expand Up @@ -57,7 +63,15 @@ const CommunityPageContent = ({ boardCode }: CommunityPageContentProps) => {
};

const postWriteHandler = () => {
router.push(`/community/${boardCode}/create`);
const createPath = `/community/${boardCode}/create`;

if (!isInitialized || refreshStatus === "refreshing") {
return;
}

const nextPath = isAuthenticated && accessToken ? createPath : buildLoginPathWithRedirect(createPath);

router.push(nextPath);
};

const getBoardNameKo = (code: string) => {
Expand Down
22 changes: 22 additions & 0 deletions apps/web/src/app/community/[boardCode]/create/PostForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import { useEffect, useRef, useState } from "react";
import { useCreatePost } from "@/apis/community";
import useCommunityImageUpload from "@/app/community/_hooks/useCommunityImageUpload";
import TopDetailNavigation from "@/components/layout/TopDetailNavigation";
import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage";
import { showIconToast } from "@/lib/toast/showIconToast";
import useAuthStore from "@/lib/zustand/useAuthStore";
import { IconImage, IconPostCheckboxFilled, IconPostCheckboxOutlined } from "@/public/svgs";
import { buildLoginPathWithRedirect } from "@/utils/authRedirect";

type PostFormProps = {
boardCode: string;
Expand All @@ -18,6 +21,10 @@ const PostForm = ({ boardCode }: PostFormProps) => {
const [content, setContent] = useState<string>("");
const router = useRouter();
const [isQuestion, setIsQuestion] = useState<boolean>(false);
const accessToken = useAuthStore((state) => state.accessToken);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const isInitialized = useAuthStore((state) => state.isInitialized);
const refreshStatus = useAuthStore((state) => state.refreshStatus);
const {
maxImages,
imageUploadRef,
Expand All @@ -34,6 +41,17 @@ const PostForm = ({ boardCode }: PostFormProps) => {
} = useCommunityImageUpload();

const createPostMutation = useCreatePost();
const createPath = `/community/${boardCode}/create`;

useEffect(() => {
if (!isInitialized || refreshStatus === "refreshing") {
return;
}

if (!isAuthenticated || !accessToken) {
router.replace(buildLoginPathWithRedirect(createPath));
}
}, [accessToken, createPath, isAuthenticated, isInitialized, refreshStatus, router]);

useEffect(() => {
const textarea = textareaRef.current;
Expand All @@ -58,6 +76,10 @@ const PostForm = ({ boardCode }: PostFormProps) => {
return () => {};
}, []);

if (!isInitialized || refreshStatus === "refreshing" || !isAuthenticated || !accessToken) {
return <CloudSpinnerPage />;
}

const submitPost = async () => {
const titleValue = titleRef.current?.querySelector("textarea")?.value.trim() || "";
const trimmedContent = content.trim();
Expand Down
19 changes: 13 additions & 6 deletions apps/web/src/app/login/LoginContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { usePostEmailAuth } from "@/apis/Auth";
import { IconSolidConnectionFullBlackLogo } from "@/public/svgs";
import { IconAppleLogo, IconEmailIcon, IconKakaoLogo } from "@/public/svgs/auth";
import {
AUTH_REDIRECT_PARAM,
buildSignUpEmailPathWithRedirect,
getSafeCommunityRedirectPath,
} from "@/utils/authRedirect";
import { appleLogin, kakaoLogin } from "@/utils/authUtils";
import useInputHandler from "./_hooks/useInputHandler";

Expand All @@ -21,8 +26,10 @@ type LoginFormData = z.infer<typeof loginSchema>;

const LoginContent = () => {
const router = useRouter();
const searchParams = useSearchParams();
const redirectPath = getSafeCommunityRedirectPath(searchParams?.get(AUTH_REDIRECT_PARAM)) ?? undefined;

const { mutate: postEmailAuth, isPending } = usePostEmailAuth();
const { mutate: postEmailAuth, isPending } = usePostEmailAuth({ redirectPath });
const { showPasswordField, handleEmailChange } = useInputHandler();

const {
Expand All @@ -38,7 +45,7 @@ const LoginContent = () => {
});

const onSubmit = async (data: LoginFormData) => {
postEmailAuth(data, {});
postEmailAuth(data);
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -110,7 +117,7 @@ const LoginContent = () => {
<div className="mt-3 flex flex-col gap-3">
<div className="mx-5 transition active:scale-95">
<button
onClick={kakaoLogin}
onClick={() => kakaoLogin(redirectPath)}
type="button"
className="flex h-11 w-full items-center justify-center gap-[5px] rounded-lg bg-accent-custom-yellow p-2.5"
>
Expand All @@ -120,7 +127,7 @@ const LoginContent = () => {
</div>
<div className="mx-5 transition active:scale-95">
<button
onClick={() => router.push("/sign-up/email")}
onClick={() => router.push(buildSignUpEmailPathWithRedirect(redirectPath))}
type="button"
className="flex h-11 w-full items-center justify-center gap-[5px] rounded-lg bg-secondary p-2.5"
>
Expand All @@ -130,7 +137,7 @@ const LoginContent = () => {
</div>
<div className="mx-5 transition active:scale-95">
<button
onClick={appleLogin}
onClick={() => appleLogin(redirectPath)}
type="button"
className="flex h-11 w-full items-center justify-center gap-[5px] rounded-lg bg-black p-2.5"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { useSearchParams } from "next/navigation";
import { useEffect } from "react";
import { usePostAppleAuth } from "@/apis/Auth";
import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage";
import { AUTH_REDIRECT_PARAM, getSafeCommunityRedirectPath } from "@/utils/authRedirect";

const attemptedAppleAuthCodes = new Set<string>();

const AppleLoginCallbackPage = () => {
const searchParams = useSearchParams();
const { mutate: postAppleAuth } = usePostAppleAuth();
const redirectPath = getSafeCommunityRedirectPath(searchParams?.get(AUTH_REDIRECT_PARAM)) ?? undefined;
const { mutate: postAppleAuth } = usePostAppleAuth({ redirectPath });

useEffect(() => {
const code = searchParams?.get("code");
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/app/login/apple/callback/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Suspense } from "react";

import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage";
import { NO_INDEX_ROBOTS } from "@/utils/seo";
import AppleLoginCallbackPage from "./AppleLoginCallbackPage";

Expand All @@ -11,7 +12,7 @@ export const metadata: Metadata = {

const Page = () => (
<div className="w-full px-5">
<Suspense fallback={<div>Loading...</div>}>
<Suspense fallback={<CloudSpinnerPage />}>
<AppleLoginCallbackPage />
</Suspense>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import { useSearchParams } from "next/navigation";
import { useEffect } from "react";
import { usePostKakaoAuth } from "@/apis/Auth";
import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage";
import { AUTH_REDIRECT_PARAM, getSafeCommunityRedirectPath } from "@/utils/authRedirect";

const attemptedKakaoAuthCodes = new Set<string>();

const KakaoLoginCallbackPage = () => {
const searchParams = useSearchParams();
const { mutate: postKakaoAuth } = usePostKakaoAuth();
const redirectPath =
getSafeCommunityRedirectPath(searchParams?.get("state") ?? searchParams?.get(AUTH_REDIRECT_PARAM)) ?? undefined;
const { mutate: postKakaoAuth } = usePostKakaoAuth({ redirectPath });
const code = searchParams?.get("code");

useEffect(() => {
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/app/login/kakao/callback/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Suspense } from "react";

import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage";
import { NO_INDEX_ROBOTS } from "@/utils/seo";
import KakaoLoginCallbackPage from "./KakaoLoginCallbackPage";

Expand All @@ -11,7 +12,7 @@ export const metadata: Metadata = {

const Page = () => (
<div className="w-full px-5">
<Suspense fallback={<div>Loading...</div>}>
<Suspense fallback={<CloudSpinnerPage />}>
<KakaoLoginCallbackPage />
</Suspense>
</div>
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { Metadata } from "next";
import { Suspense } from "react";
import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage";
import KakaoScriptLoader from "@/lib/ScriptLoader/KakaoScriptLoader";
import { NO_INDEX_ROBOTS } from "@/utils/seo";
import LoginContent from "./LoginContent";
Expand All @@ -12,7 +14,9 @@ const LoginPage = () => {
return (
<div className="w-full px-5">
<KakaoScriptLoader />
<LoginContent />
<Suspense fallback={<CloudSpinnerPage />}>
<LoginContent />
</Suspense>
</div>
);
};
Expand Down
7 changes: 5 additions & 2 deletions apps/web/src/app/sign-up/email/EmailSignUpForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useRouter } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import { usePostEmailSignUp } from "@/apis/Auth";
import BlockBtn from "@/components/button/BlockBtn";
Expand All @@ -9,9 +9,12 @@ import { Label } from "@/components/ui/Label";
import { Progress } from "@/components/ui/Progress";
import { showIconToast } from "@/lib/toast/showIconToast";
import { IconCheckBlue, IconExpRed, IconEyeOff, IconEyeOn } from "@/public/svgs/ui";
import { AUTH_REDIRECT_PARAM, buildSignUpPath, getSafeCommunityRedirectPath } from "@/utils/authRedirect";

const EmailSignUpForm = () => {
const router = useRouter();
const searchParams = useSearchParams();
const redirectPath = getSafeCommunityRedirectPath(searchParams?.get(AUTH_REDIRECT_PARAM)) ?? undefined;
const [currentStep, setCurrentStep] = useState<number>(0);
const [email, setEmail] = useState<string>("");
const [showPassword, setShowPassword] = useState<boolean>(false);
Expand Down Expand Up @@ -69,7 +72,7 @@ const EmailSignUpForm = () => {
{ email, password },
{
onSuccess: (data) => {
router.push(`/sign-up?token=${data.signUpToken}`);
router.push(buildSignUpPath({ signUpToken: data.signUpToken, redirectPath }));
},
},
);
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/app/sign-up/email/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Metadata } from "next";
import { Suspense } from "react";

import TopDetailNavigation from "@/components/layout/TopDetailNavigation";
import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage";
import { NO_INDEX_ROBOTS } from "@/utils/seo";

import EmailSignUpForm from "./EmailSignUpForm";
Expand All @@ -15,7 +17,9 @@ const EmailSignUpPage = () => {
<>
<TopDetailNavigation title="이메일로 시작하기" />
<div className="w-full px-5">
<EmailSignUpForm />
<Suspense fallback={<CloudSpinnerPage />}>
<EmailSignUpForm />
</Suspense>
</div>
</>
);
Expand Down
Loading
Loading