diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index da4a3f68..fb561511 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -32,15 +32,17 @@ jobs: run: npm install # 아래 단계에서 .env 파일을 생성하고 시크릿 값을 설정합니다. - # - name: Set Environment Variables - # env: - # NEXT_PUBLIC_API_ADDRESS: ${{ secrets.NEXT_PUBLIC_API_ADDRESS }} - # NEXT_PUBLIC_CHANNEL_ID: ${{ secrets.NEXT_PUBLIC_CHANNEL_ID }} - # NEXT_PUBLIC_DISLIKE_CHANNEL_ID: ${{ secrets.NEXT_PUBLIC_DISLIKE_CHANNEL_ID }} - # run: | - # echo "NEXT_PUBLIC_API_ADDRESS=$NEXT_PUBLIC_API_ADDRESS" >> .env - # echo "NEXT_PUBLIC_CHANNEL_ID=$NEXT_PUBLIC_CHANNEL_ID" >> .env - # echo "NEXT_PUBLIC_DISLIKE_CHANNEL_ID=$NEXT_PUBLIC_DISLIKE_CHANNEL_ID" >> .env + - name: Set Environment Variables + env: + NEXT_PUBLIC_API_ADDRESS: ${{ secrets.NEXT_PUBLIC_API_ADDRESS }} + NEXT_PUBLIC_API_MOCKING: ${{ secrets.NEXT_PUBLIC_API_MOCKING }} + NEXT_PUBLIC_API_MOCKING_ADDRESS: ${{ secrets.NEXT_PUBLIC_API_MOCKING_ADDRESS }} + CHROMATIC_TOKEN: ${{ secrets.CHROMATIC_TOKEN }} + run: | + echo "NEXT_PUBLIC_API_ADDRESS=$NEXT_PUBLIC_API_ADDRESS" >> .env + echo "NEXT_PUBLIC_API_MOCKING=$NEXT_PUBLIC_API_MOCKING" >> .env + echo "NEXT_PUBLIC_API_MOCKING_ADDRESS=$NEXT_PUBLIC_API_MOCKING_ADDRESS" >> .env + echo "CHROMATIC_TOKEN=$CHROMATIC_TOKEN" >> .env # 빌드를 수행합니다. - name: Build @@ -61,12 +63,7 @@ jobs: await github.pulls.createReview({ ...context.repo, pull_number, - body : "테스트코드를 다시 확인해주세요. ", + body : "actions 결과를 다시 확인해주세요. -자동으로 작성됨-", event : "REQUEST_CHANGES" }) - await github.pulls.update({ - ...context.repo, - pull_number, - state: "closed" - }) if: failure() diff --git a/package.json b/package.json index a2c67fdc..2d9b1989 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4", @@ -27,6 +28,7 @@ "class-variance-authority": "^0.7.0", "classnames": "^2.3.2", "clsx": "^2.0.0", + "lucide-react": "^0.291.0", "date-fns": "^2.30.0", "next": "13.5.6", "next-themes": "^0.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7a9fe24..cd99f8b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,9 @@ dependencies: version: 1.3.0(react@18.0.0) '@radix-ui/react-label': specifier: ^2.0.2 + version: 2.0.2(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-select': + specifier: ^2.0.0 version: 2.0.2(@types/react-dom@18.0.0)(@types/react@18.2.33)(react-dom@18.0.0)(react@18.0.0) '@radix-ui/react-slot': specifier: ^1.0.2 @@ -44,6 +47,9 @@ dependencies: clsx: specifier: ^2.0.0 version: 2.0.0 + lucide-react: + specifier: ^0.291.0 + version: 0.291.0(react@18.0.0) date-fns: specifier: ^2.30.0 version: 2.30.0 @@ -2186,7 +2192,6 @@ packages: resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} dependencies: '@babel/runtime': 7.23.2 - dev: true /@radix-ui/primitive@1.0.1: resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} @@ -2763,6 +2768,46 @@ packages: react-remove-scroll: 2.5.5(@types/react@18.2.33)(react@18.0.0) dev: true + /@radix-ui/react-select@2.0.0(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0): + resolution: {integrity: sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@radix-ui/number': 1.0.1 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-popper': 1.1.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-use-previous': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@types/react': 18.0.0 + '@types/react-dom': 18.0.0 + aria-hidden: 1.2.3 + react: 18.0.0 + react-dom: 18.0.0(react@18.0.0) + react-remove-scroll: 2.5.5(@types/react@18.0.0)(react@18.0.0) + dev: false /@radix-ui/react-separator@1.0.3(@types/react-dom@18.0.0)(@types/react@18.2.33)(react-dom@18.0.0)(react@18.0.0): resolution: {integrity: sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==} peerDependencies: @@ -3044,7 +3089,6 @@ packages: '@types/react-dom': 18.0.0 react: 18.0.0 react-dom: 18.0.0(react@18.0.0) - dev: true /@radix-ui/rect@1.0.1: resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} @@ -9221,6 +9265,14 @@ packages: yallist: 4.0.0 dev: true + /lucide-react@0.291.0(react@18.0.0): + resolution: {integrity: sha512-79jHlT9Je2PXSvXIBGDkItCK7In2O9iKnnSJ/bJxvIBOFaX2Ex0xEcC4fRS/g0F2uQGFejjmn2qWhwdc5wicMQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.0.0 + dev: false + /lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true diff --git a/src/app/(root)/(routes)/(home)/components/TestBlock.tsx b/src/app/(root)/(routes)/(home)/components/TestBlock.tsx index cb336899..431a4e03 100644 --- a/src/app/(root)/(routes)/(home)/components/TestBlock.tsx +++ b/src/app/(root)/(routes)/(home)/components/TestBlock.tsx @@ -1,25 +1,28 @@ -'use client' - -import React, { useEffect } from 'react' +// 'use client' +import React from 'react' import { getTest } from '@/services/test/test' -// async function getTestValue() { -// const res = await getTest() -// const data = await res.json() -// return data -// } +async function getTestValue(): Promise<{ message: string } | null> { + try { + const res = await getTest() + const data = await res.json() + return data + } catch (e) { + console.log(e) + return null + } +} -export default function TestBlock() { - // const data = await getTestValue() - // console.log(data.message) +export default async function TestBlock() { + const data = await getTestValue() - useEffect(() => { - async function fetchData() { - const data = await getTest() - console.log(await data.json()) - } - fetchData() - }, []) + // useEffect(() => { + // async function fetchData() { + // const data = await getTest() + // console.log(await data.json()) + // } + // fetchData() + // }, []) - return
{'index '}
+ return
{'index ' + data?.message}
} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c0add4dc..6f9dd813 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,9 +1,11 @@ import { Suspense } from 'react' import type { Metadata } from 'next' import Header from '@/components/domain/Header' +import { Environment } from '@/config/environment' import MSWWrapper from '@/contexts/MSWWrapper' import TanstackQueryContext from '@/contexts/TanstackQueryContext' import ThemeProviderContext from '@/contexts/ThemeProviderContext' +import { initMockApi } from '@/lib/msw/initMockApi' import '@/styles/globals.css' export const metadata: Metadata = { @@ -12,6 +14,11 @@ export const metadata: Metadata = { viewport: 'width=device-width, initial-scale=1.0', } +if (Environment.apiMocking() === 'enabled') { + console.log('Mocking enabled') + initMockApi() +} + export default function RootLayout({ children, authModal, diff --git a/src/components/domain/Header/Header.tsx b/src/components/domain/Header/Header.tsx index e0848a85..0961432c 100644 --- a/src/components/domain/Header/Header.tsx +++ b/src/components/domain/Header/Header.tsx @@ -37,11 +37,11 @@ const Header = ({ isLogin = false }: HeaderProps) => { {/** TODO: 아바타 컴포넌트로 변경 */} ) : ( - <> - - + )} diff --git a/src/components/domain/LoginForm/LoginForm.stories.tsx b/src/components/domain/LoginForm/LoginForm.stories.tsx deleted file mode 100644 index 4a67a72d..00000000 --- a/src/components/domain/LoginForm/LoginForm.stories.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react' -import LoginForm from './LoginForm' - -const meta = { - title: 'DOMAIN/LoginForm', - component: LoginForm, - tags: ['autodocs'], - argTypes: {}, -} satisfies Meta - -export default meta -type Story = StoryObj - -export const Normal: Story = { - args: {}, -} diff --git a/src/components/domain/LoginForm/LoginForm.tsx b/src/components/domain/LoginForm/LoginForm.tsx index 4e47338c..b8e6aa44 100644 --- a/src/components/domain/LoginForm/LoginForm.tsx +++ b/src/components/domain/LoginForm/LoginForm.tsx @@ -1,12 +1,50 @@ +'use client' + import Image from 'next/image' +import { useRouter } from 'next/navigation' +import AppPath from '@/config/appPath' import Assets from '@/config/assets' +import { getGoogleLogin, getKakaoLogin } from '@/services/auth/auth' import LoginButtons from './section/LoginButtons' const LoginForm = () => { + const router = useRouter() + const kakaoLoginHandler = async () => { + try { + await getKakaoLogin() + const res = await getKakaoLogin() + const data = await res.json() + console.log(data) + alert('로그인 성공') + router.back() + } catch (e) { + console.log(e) + alert('로그인 실패') + router.push(AppPath.login(), { scroll: false }) + } + } + const googleLoginHandler = async () => { + try { + await getKakaoLogin() + const res = await getGoogleLogin() + const data = await res.json() + console.log(data) + alert('로그인 성공') + router.back() + } catch (e) { + console.log(e) + alert('로그인 실패') + router.push(AppPath.login(), { scroll: false }) + } + } + return (
nabi-logo - +
) } diff --git a/src/components/domain/LoginForm/section/LoginButtons.tsx b/src/components/domain/LoginForm/section/LoginButtons.tsx index 5b80d764..e11f9ede 100644 --- a/src/components/domain/LoginForm/section/LoginButtons.tsx +++ b/src/components/domain/LoginForm/section/LoginButtons.tsx @@ -3,11 +3,19 @@ import React from 'react' import { KakaoLoginButton, GoogleLoginButton } from '../../buttons/LoginButtons' -const LoginButtons = () => { +type LoginButtonsProps = { + kakaoLoginHandler: () => void + googleLoginHandler: () => void +} + +const LoginButtons = ({ + kakaoLoginHandler, + googleLoginHandler, +}: LoginButtonsProps) => { return (
- alert('k')} /> - alert('g')} /> + +
) } diff --git a/src/components/ui/Select/Select.stories.tsx b/src/components/ui/Select/Select.stories.tsx new file mode 100644 index 00000000..dad27c12 --- /dev/null +++ b/src/components/ui/Select/Select.stories.tsx @@ -0,0 +1,41 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from './Select' + +const meta = { + title: 'UI/Select', + component: Select, + tags: ['autodocs'], + argTypes: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Normal: Story = { + args: {}, + render: () => { + return ( + + ) + }, +} diff --git a/src/components/ui/Select/Select.tsx b/src/components/ui/Select/Select.tsx new file mode 100644 index 00000000..81eea9c8 --- /dev/null +++ b/src/components/ui/Select/Select.tsx @@ -0,0 +1,94 @@ +'use client' + +import * as React from 'react' +import * as SelectPrimitive from '@radix-ui/react-select' +import { Check, ChevronDown } from 'lucide-react' +import { cn } from '@/utils' + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = 'popper', ...props }, ref) => ( + + + + {children} + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectItem, +} diff --git a/src/components/ui/Select/index.tsx b/src/components/ui/Select/index.tsx new file mode 100644 index 00000000..da0623d8 --- /dev/null +++ b/src/components/ui/Select/index.tsx @@ -0,0 +1,3 @@ +import { Select } from './Select' + +export default Select diff --git a/src/config/apiEndPoint.ts b/src/config/apiEndPoint.ts index 65357596..852201fe 100644 --- a/src/config/apiEndPoint.ts +++ b/src/config/apiEndPoint.ts @@ -1,7 +1,8 @@ import { GetItems } from '@/services/item/item' const ApiEndPoint = { - login: () => '/login', + kakaoLogin: () => '/oauth2/authorize/kakao/login', + googleLogin: () => '/oauth2/authorize/google/login', test: () => '/test', items: (cursorId: number) => `/items?cursorId=${cursorId}`, } as const diff --git a/src/config/environment.ts b/src/config/environment.ts index 9e21e787..51aef80b 100644 --- a/src/config/environment.ts +++ b/src/config/environment.ts @@ -1,5 +1,8 @@ export const Environment = { - apiAddress: () => process.env.NEXT_PUBLIC_API_ADDRESS, + apiAddress: () => + process.env.NEXT_PUBLIC_API_MOCKING === 'enabled' + ? process.env.NEXT_PUBLIC_API_MOCKING_ADDRESS + : process.env.NEXT_PUBLIC_API_ADDRESS, apiMocking: () => process.env.NEXT_PUBLIC_API_MOCKING === 'enabled' ? 'enabled' : 'disabled', } diff --git a/src/lib/msw/mocks/authHandlers.ts b/src/lib/msw/mocks/authHandlers.ts new file mode 100644 index 00000000..8afc7e18 --- /dev/null +++ b/src/lib/msw/mocks/authHandlers.ts @@ -0,0 +1,46 @@ +import { rest } from 'msw' +import ApiEndPoint from '@/config/apiEndPoint' +import { Environment } from '@/config/environment' + +const baseUrl = Environment.apiAddress() + +const authHandlers = [ + rest.get(`${baseUrl}${ApiEndPoint.kakaoLogin()}`, async (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + data: { + userInfo: { + userId: 1, + nickname: '병원에 간 미어캣', + imageUrl: 'http://asdf~', + }, + token: { + accessToken: 'asdfasdf', + refreshToken: 'asdfa', + }, + }, + }), + ) + }), + rest.get(`${baseUrl}${ApiEndPoint.googleLogin()}`, async (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + data: { + userInfo: { + userId: 1, + nickname: '병원에 간 미어캣', + imageUrl: 'http://asdf~', + }, + token: { + accessToken: 'asdfasdf', + refreshToken: 'asdfa', + }, + }, + }), + ) + }), +] + +export default authHandlers diff --git a/src/lib/msw/mocks/handlers.ts b/src/lib/msw/mocks/handlers.ts index 4b32ebff..d40939e1 100644 --- a/src/lib/msw/mocks/handlers.ts +++ b/src/lib/msw/mocks/handlers.ts @@ -1,4 +1,5 @@ import { itemHandlers } from './itemHandler' +import authHandlers from './authHandlers' import { testHandlers } from './testHandler' -export const handlers = [...testHandlers, ...itemHandlers] +export const handlers = [...testHandlers, ...authHandlers, itemHandlers] diff --git a/src/services/auth/auth.ts b/src/services/auth/auth.ts index 758b38db..201db899 100644 --- a/src/services/auth/auth.ts +++ b/src/services/auth/auth.ts @@ -1,15 +1,16 @@ import ApiEndPoint from '@/config/apiEndPoint' -import { Environment } from '@/config/environment' - -const postLogin = async () => { - const response = await fetch(Environment.apiAddress() + ApiEndPoint.login(), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - }) - const data = await response.json() - return data +import apiClient from '../apiClient' + +const getKakaoLogin = async () => { + const response = await apiClient.get(ApiEndPoint.kakaoLogin()) + + return response +} + +const getGoogleLogin = async () => { + const response = await apiClient.get(ApiEndPoint.googleLogin()) + + return response } -export { postLogin } +export { getKakaoLogin, getGoogleLogin }