Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#68-update_user_api #69

Closed
wants to merge 17 commits into from
Closed
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
Binary file removed public/no-image.png
Binary file not shown.
41 changes: 29 additions & 12 deletions src/__tests__/pages/MainPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';

import NotFound from '@/app/not-found';
import { MantineProvider } from '@mantine/core';
import { fireEvent } from '@testing-library/react';
import mockRouter from 'next-router-mock';
import { useRouter } from 'next/router';

Object.defineProperty(window, 'matchMedia', {
writable: true,
Expand All @@ -28,17 +29,33 @@ export function mockFetch(data: unknown) {
);
}

describe('Error', () => {
it('renders the Components', () => {
window.fetch = mockFetch({});
render(
<MantineProvider>
<NotFound />
</MantineProvider>,
);
jest.mock('next/router', () => jest.requireActual('next-router-mock'));

const text = screen.getByText(/home/i);
const ExampleComponent = ({ href = '' }) => {
const router = useRouter();
return (
<button onClick={() => router.push(href)}>
The current route is: "{router.asPath}"
</button>
);
};

describe('next-router-mock', () => {
it('mocks the useRouter hook', () => {
// Set the initial url:
mockRouter.push('/initial-path');

render(<ExampleComponent href='/foo?bar=baz' />);
expect(screen.getByRole('button')).toBeInTheDocument();

// Click the button:
fireEvent.click(screen.getByRole('button'));

expect(text).toBeInTheDocument();
// Ensure the router was updated:
expect(mockRouter).toMatchObject({
asPath: '/foo?bar=baz',
pathname: '/foo',
query: { bar: 'baz' },
});
});
});
36 changes: 21 additions & 15 deletions src/app/api/auth/[...nextauth]/auth.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import NextAuth from 'next-auth';
import NextAuth, { User } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import GithubProvider from 'next-auth/providers/github';

// import GithubProvider from 'next-auth/providers/github';
import { apiRoutes } from '@/constants';
import { omit } from '@/utils/shared';
import { consoleLog } from '@/utils/shared/console-log';
Expand Down Expand Up @@ -30,10 +30,10 @@ export const {
auth,
} = NextAuth({
providers: [
GithubProvider({
clientId: process.env.NEXT_PUBLIC_AUTH_GITHUB_ID,
clientSecret: process.env.NEXT_PUBLIC_AUTH_GITHUB_SECRET,
}),
// GithubProvider({
// clientId: process.env.NEXT_PUBLIC_AUTH_GITHUB_ID,
// clientSecret: process.env.NEXT_PUBLIC_AUTH_GITHUB_SECRET,
// }),
CredentialsProvider({
name: 'Credentials',
credentials: {
Expand Down Expand Up @@ -61,16 +61,22 @@ export const {
method: 'POST',
}),
);
const user = {
...res?.member,
...res?.tokenInfo,
provider: 'Credentials',
};
if (res) {

if (res.id) {
const userInfo = await Promise.resolve(
getApiResponse<AuthResponse | null>({
headers: { 'Content-Type': 'application/json' },
apiEndpoint: `${apiRoutes.userInfoSlash}${res?.id}`,
}),
);
const user = {
id: res?.id,
...userInfo,
accessToken: res?.accessToken,
provider: 'Credentials',
} as unknown as User;
return user;
} else {
return null;
}
} else return res;
} catch (e) {
consoleLog(`${e}`);
}
Expand Down
16 changes: 5 additions & 11 deletions src/app/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import * as React from 'react';

import NotFound from '@/components/NotFound';

import { consoleLog } from '@/utils/shared/console-log';

export default function ErrorPage({
Expand All @@ -14,20 +16,12 @@ export default function ErrorPage({
React.useEffect(() => {
consoleLog('error.tsx', error);
}, [error]);
const title = 'Oops, something went wrong!';
const { message } = error;

return (
<main>
<section>
<div>
<h1>Oops, something went wrong!</h1>
<h5>change this in app/error.tsx</h5>
<h4>{error.message}</h4>
<div>
<button onClick={reset}>Try again</button>
</div>
<a href='/'>Back to home</a>
</div>
</section>
<NotFound title={title} message={message} reset={reset} />
</main>
);
}
3 changes: 2 additions & 1 deletion src/app/mypage/@info/components/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useEffect, useState } from 'react';
import ProfileSkeleton from '@/components/shared/ProfileSkeleton';

import { apiRoutes, fail, IS_PROD, siteRoutes } from '@/constants';
import { randomAvartars } from '@/constants/default/avatars';
import { getApiResponse } from '@/utils/shared/get-api-response';

import UpdateAvatarForm from './forms/Avatar';
Expand Down Expand Up @@ -103,7 +104,7 @@ const UserData = () => {
<Avatar
src={
userInfo?.userData?.avatarUrl ||
'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-7.png'
randomAvartars(userInfo?.userData?.id)
}
className='hvr'
radius='100%'
Expand Down
22 changes: 13 additions & 9 deletions src/app/mypage/@info/components/forms/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { notifications } from '@mantine/notifications';
import { IconCheck, IconPhotoPlus, IconX } from '@tabler/icons-react';
import { useQueryClient } from '@tanstack/react-query';
import { JWT } from 'next-auth/jwt';
import { useSession } from 'next-auth/react';
import { signIn, useSession } from 'next-auth/react';
import { useCallback, useMemo, useState } from 'react';

import { apiRoutes, success } from '@/constants';
import { apiRoutes, fail, success } from '@/constants';
import { getApiResponse } from '@/utils/shared/get-api-response';
import { getItem, removeItem, setItem } from '@/utils/shared/localStorage';

import { ErrorResponse } from './UserInfo';

const UpdateAvatarForm = ({
close,
userId,
Expand Down Expand Up @@ -57,12 +59,12 @@ const UpdateAvatarForm = ({
});
}, [files, thumbnail]);

const onSubmitRoadmap = useCallback(async () => {
const onSubmitAvatar = useCallback(async () => {
if (!formData || !files || !thumbnail) {
return;
}
const [response] = await Promise.all([
getApiResponse<undefined>({
getApiResponse<ErrorResponse | null>({
requestData: formData,
apiEndpoint: `${apiRoutes.memberAvatarUpdate}`,
method: 'POST',
Expand All @@ -72,25 +74,27 @@ const UpdateAvatarForm = ({
}),
]);

if (!response || response.error) {
if (response?.errorCode) {
const { httpStatus, message, errorCode } = response as ErrorResponse;
notifications.show({
id: 'update-profile-image-fail',
withCloseButton: true,
autoClose: 5000,
title: '내 프로필 이미지 변경 실패',
message: '🥲 내 프로필 이미지 변경에 실패했습니다',
color: 'red',
message: `🥲 내 프로필 이미지 변경에 실패했습니다\n${message}`,
color: fail[httpStatus].color,
icon: <IconX style={{ width: '20rem', height: '20rem' }} />,
className: 'my-notification-class',
loading: false,
});
removeItem('my-update-profile');
setThumbnail('');
setFiles([]);
if (errorCode === 'UnAuthenticated') signIn();
return;
}

if (response?.url) {
if (response?.avatarUrl) {
notifications.show({
id: success.roadmaps.id,
withCloseButton: false,
Expand Down Expand Up @@ -156,7 +160,7 @@ const UpdateAvatarForm = ({
>
<Button
disabled={!files || !thumbnail || !formData}
onClick={() => onSubmitRoadmap()}
onClick={() => onSubmitAvatar()}
>
이미지 변경하기
</Button>
Expand Down
48 changes: 27 additions & 21 deletions src/app/mypage/@info/components/forms/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ import { JWT } from 'next-auth/jwt';
import { useSession } from 'next-auth/react';
import { useCallback, useMemo, useState } from 'react';

import { apiRoutes, success } from '@/constants';
import { apiRoutes, fail, FailKeys, success } from '@/constants';
import { getApiResponse } from '@/utils/shared/get-api-response';

export interface ErrorResponse {
httpStatus: FailKeys;
message: string;
errorCode: string;
}

const UpdateMemberProfileForm = ({
close,
userNickName,
Expand Down Expand Up @@ -43,7 +49,7 @@ const UpdateMemberProfileForm = ({
return;
}
const [response] = await Promise.all([
getApiResponse<undefined>({
getApiResponse<ErrorResponse | null>({
requestData: JSON.stringify({
memberId: userId,
nickname,
Expand All @@ -58,14 +64,15 @@ const UpdateMemberProfileForm = ({
}),
]);

if (!response || response.error) {
if (response?.errorCode !== undefined) {
const { httpStatus, message } = response as ErrorResponse;
notifications.show({
id: 'update-my-info-fail',
withCloseButton: true,
autoClose: 5000,
title: '내 프로필 정보 변경 실패',
message: '🥲 내 프로필 정보 변경에 실패했습니다',
color: 'red',
message: `🥲 내 프로필 정보 변경에 실패했습니다\n${message}`,
color: fail[httpStatus].color,
icon: <IconX style={{ width: '20rem', height: '20rem' }} />,
className: 'my-notification-class',
loading: false,
Expand All @@ -74,22 +81,21 @@ const UpdateMemberProfileForm = ({
return;
}

if (response) {
notifications.show({
id: success.user.id,
withCloseButton: false,
autoClose: 800,
title: success.user.title,
message: `내 프로필 변경에 성공했습니다 🎉`,
color: success.roadmaps.color,
icon: <IconCheck style={{ width: '20rem', height: '20rem' }} />,
className: 'my-notification-class notification',
loading: true,
});
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: [`mypage-info-${userId}`] });
}, 700);
}
notifications.show({
id: success.user.id,
withCloseButton: false,
autoClose: 800,
title: success.user.title,
message: `내 프로필 변경에 성공했습니다 🎉`,
color: success.roadmaps.color,
icon: <IconCheck style={{ width: '20rem', height: '20rem' }} />,
className: 'my-notification-class notification',
loading: true,
});
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: [`mypage-info-${userId}`] });
}, 700);

close();
}, [accessToken, bio, close, nickname, queryClient, userId, userNickName]);

Expand Down
18 changes: 6 additions & 12 deletions src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import { Metadata } from 'next';
import { RiAlarmWarningFill } from 'react-icons/ri';

import NotFound from '@/components/NotFound';

export const metadata: Metadata = {
title: 'Not Found',
};

export default function NotFoundPage() {
const title = 'Something is not right...';
const message =
'Page you are trying to open does not exist. You may have mistyped the address, or the page has been moved to another URL. If you think this is an error contact support.';
return (
<main>
<div>
<div className='layout flex min-h-screen flex-col items-center justify-center text-center text-black'>
<RiAlarmWarningFill
size={60}
className='drop-shadow-glow animate-flicker text-red-500'
/>
<h1>Page Not Found</h1>
<h5>change this in app/not-found.tsx</h5>
<a href='/'>Back to home</a>
</div>
</div>
<NotFound title={title} message={message} />
</main>
);
}
2 changes: 1 addition & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Mainpage from '@/components/MainPage';
import { SITE_CONFIG } from '@/constants';

export const metadata: Metadata = {
metadataBase: new URL('http://roadmaker.site/'),
metadataBase: new URL(`${SITE_CONFIG.url}`),
title: {
default: `${SITE_CONFIG.title}💜💜💜💜`,
template: `%s | ${SITE_CONFIG.title}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const DetailContentEditor = ({
inline: false,
ccLanguage: 'ko',
interfaceLanguage: 'ko',
enableIFrameApi: true,
origin: process.env.SITE_URL,
}),
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
Expand Down
2 changes: 1 addition & 1 deletion src/app/roadmap/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default function RoadmapEditorPage() {
ml='1rem'
icon={<IconCircleChevronRight size={26} stroke={1.5} />}
/>
<Drawer.Body p='1rem' style={{ height: '100vh' }}>
<Drawer.Body p='1rem' style={{ height: '100vh', width: '27.6rem' }}>
{clickedNode && (
<Box pb='sm'>
<Box pb='sm'>
Expand Down
Loading
Loading