Skip to content

Commit

Permalink
[SNP-64] Google 로그인 연동 #21
Browse files Browse the repository at this point in the history
[SNP-64] Google 로그인 연동
  • Loading branch information
raymondanythings authored Dec 19, 2023
2 parents 3fc6f7c + 7a89222 commit 7d360c6
Show file tree
Hide file tree
Showing 20 changed files with 580 additions and 47 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ yarn-error.log*

# local env files
.env*.local
.env

# vercel
.vercel
Expand Down
63 changes: 63 additions & 0 deletions apis/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Session, User } from "@/type";
import axios from "axios";
const api = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_URL });
export const getGoogleCode = () => {
const authUrl = new URL(
"https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount",
);

authUrl.searchParams.set(
"client_id",
process.env.NEXT_PUBLIC_GOOGLE_AUTH_CLIENT_ID,
);
authUrl.searchParams.set(
"redirect_uri",
process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URL,
);
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set(
"scope",
"https://www.googleapis.com/auth/userinfo.email",
);
authUrl.searchParams.set("access_type", "offline");
window.location.href = authUrl.toString();
};

export const getUser = async (code: string) => {
const userResponse = await api.get<Session["user"]>("/oauth/google/user", {
params: {
code,
},
});
return userResponse.data;
};

export const getNewToken = async (refresh: string) => {
const newToken = await api.post<Session["token"]>("/auth/refresh", {
refresh,
});
return newToken.data;
};

export const getMe = async (access: string) => {
const res = await api.get<User>(`/user/me`, {
headers: {
Authorization: `Bearer ${access}`,
},
});
return res.data;
};

export const setNickName = async (param: { nickname: string }) => {
const res = await api.post<boolean>("/user/nickname", param);
return res.data;
};

const APIs = {
getGoogleCode,
getUser,
getMe,
setNickName,
};

export default APIs;
18 changes: 15 additions & 3 deletions app/(root)/(home)/[userId]/nickname/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import React from "react";
import * as z from "zod";
import { useRouter } from "next/navigation";

import { ArrowLeft } from "lucide-react";
import { useForm } from "react-hook-form";

import { userStore } from "@/store/user";
import { zodResolver } from "@hookform/resolvers/zod";
import useSetNickName from "@/hooks/use-set-nickname";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
Expand All @@ -18,6 +19,7 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { ArrowLeft } from "lucide-react";

const formSchema = z.object({
nickname: z.string().min(1, {
Expand All @@ -28,6 +30,16 @@ const formSchema = z.object({
const NicknamePage = () => {
const router = useRouter();

const { mutate } = useSetNickName({
onSuccess(data, variables) {
if (data) {
router.replace(`/${id}/post`);
}
},
});
const { userInfo } = userStore();
const { id } = userInfo || {};

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
Expand All @@ -36,7 +48,7 @@ const NicknamePage = () => {
});

const handleSubmit = async (values: z.infer<typeof formSchema>) => {
router.push("/userId/post");
mutate(values);
};

const isNicknameEntered = !!form.getValues("nickname");
Expand Down
49 changes: 16 additions & 33 deletions app/(root)/(route)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
"use client";
import React, { useState } from "react";
import { useRouter } from "next/navigation";

import { cn } from "@/lib/utils";

import React from "react";
import Link from "next/link";
import APIs from "@/apis";
import { Button } from "@/components/ui/button";
import Link from 'next/link';
import { useSession } from "@/components/provider/session-provider";

const OnBoardingPage = () => {
const router = useRouter();
const [login, setLogin] = useState(false);
const [nickname, setNickname] = useState("");
const { data } = useSession();
const { user } = data || {};

const backgroundStyle = {
backgroundImage: `url('/bg-shinnyang.png')`,
Expand All @@ -19,27 +16,8 @@ const OnBoardingPage = () => {
backgroundRepeat: "no-repeat",
};

const nicknameCheckHandler = () => {
nickname ? router.push("/userId/post") : router.push("userId/nickname");
};

return (
<div className="theme-responsive" style={backgroundStyle}>
{!login ? (
<Button
className="h-10 w-20 rounded-md bg-red-400 p-1 text-center text-white"
onClick={() => setLogin(true)}
>
비회원
</Button>
) : (
<Button
className="h-10 w-20 rounded-md bg-blue-400 p-1 text-center text-white"
onClick={() => setLogin(false)}
>
회원
</Button>
)}
<div className="flex h-full w-full flex-col">
<div className="mt-[4dvh] flex flex-1 flex-col justify-start text-center text-white">
<div className="text-2xl font-medium">
Expand All @@ -50,13 +28,18 @@ const OnBoardingPage = () => {
<span className="text-md mt-2">신년카드 대신 전달해드립니다.</span>
</div>
<div className="items-end">
{!login ? (
<Button variant="kakao" onClick={() => setLogin(true)}>
카카오 로그인
</Button>
{!user ? (
<Button variant="kakao" onClick={() => APIs.getGoogleCode()}>
구글 로그인
</Button>
) : (
// <SignInButton />
<div className="mb-8">
<Link href={nickname ? "/userId/post" : "/userId/nickname"}>
<Link
href={
user.nickname ? `/${user.id}/post` : `/${user.id}/nickname`
}
>
<Button variant={"primary"}>우체국 방문하기</Button>
</Link>
</div>
Expand Down
50 changes: 50 additions & 0 deletions app/api/auth/[serviceName]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Session } from "@/type";
import { NextRequest, NextResponse } from "next/server";

export async function GET(
req: NextRequest,
{ params: { serviceName } }: { params: { serviceName: string } },
) {
const { searchParams, origin } = new URL(req.url);

const response = NextResponse.redirect(origin);
try {
const code = searchParams.get("code");
const scope = searchParams.get("scope");

if (code) {
const apiUrl = new URL(process.env.NEXT_PUBLIC_API_URL);
apiUrl.pathname = `/oauth/${serviceName}/user`;
apiUrl.searchParams.set("code", code);
apiUrl.searchParams.set("scope", scope || "");
const fetcher = await fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const token = (await fetcher.json()) as Session["token"];
if (fetcher.status >= 400) {
throw new Error("invalid request - " + JSON.stringify(token));
}
response.cookies.set({
name: "access",
value: token?.access || "",
httpOnly: true,
// 하루 - 24시간
maxAge: 60 * 60 * 24,
});
response.cookies.set({
name: "refresh",
value: token?.refresh || "",
httpOnly: true,
// 30일
maxAge: 60 * 60 * 24 * 30,
});
}
} catch (error: any) {
console.error(`[AUTH ERROR]: ${error?.message}`);
}

return response;
}
35 changes: 28 additions & 7 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,41 @@ import "./globals.css";
import { cn } from "@/lib/utils";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import QueryProvider from "@/components/provider/query-provider";
import { cookies } from "next/headers";
import { getMe } from "@/apis";
import { Session } from "@/type";
import { SessionProvider } from "@/components/provider/session-provider";

const fontSans = Roboto({
weight: ["300", "400", "500", "700"],
subsets: ["latin"],
variable: "--font-sans",
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "신냥이우체국",
description: "",
};

export default function RootLayout({
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const cookie = cookies();
const access = cookie.get("access");
const refresh = cookie.get("refresh");
let session: Session = {
token: {
access: access?.value,
refresh: refresh?.value,
},
user: null,
};
if (access?.value) {
const user = await getMe(access.value);
session.user = user;
}

return (
<html lang="ko" suppressHydrationWarning>
<body
Expand All @@ -28,10 +47,12 @@ export default function RootLayout({
fontSans.variable,
)}
>
<QueryProvider>
{children}
<ReactQueryDevtools />
</QueryProvider>
<SessionProvider session={session}>
<QueryProvider>
{children}
<ReactQueryDevtools />
</QueryProvider>
</SessionProvider>
</body>
</html>
);
Expand Down
8 changes: 8 additions & 0 deletions app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"use client";
import React from "react";

const NotFoundPage = () => {
return <div>NotFound</div>;
};

export default NotFoundPage;
5 changes: 5 additions & 0 deletions components/pages/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const ErrorPage = () => {
return <div>ErrorPage</div>;
};

export default ErrorPage;
5 changes: 5 additions & 0 deletions components/pages/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const LoadingPage = () => {
return <div>LoadingPage</div>;
};

export default LoadingPage;
Loading

0 comments on commit 7d360c6

Please sign in to comment.