Skip to content

Commit

Permalink
Merge pull request #133 from Myongji-Graduate/find-auth/#126
Browse files Browse the repository at this point in the history
  • Loading branch information
yougyung authored Jul 27, 2024
2 parents cafe2ba + b21a9bf commit 2fde0f6
Show file tree
Hide file tree
Showing 34 changed files with 829 additions and 46 deletions.
4 changes: 4 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,9 @@ const config: StorybookConfig = {
autodocs: 'tag',
},
staticDirs: ['../public'],
env: (config) => ({
...config,
STORY_BOOK: 'true',
}),
};
export default config;
28 changes: 28 additions & 0 deletions app/(sub-page)/find-id/components/find-id-container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';
import useFunnel from '@/app/hooks/useFunnel';
import FindIdForm from '@/app/ui/user/find-id-form/find-id-form';
import { useState } from 'react';
import { FormState } from '@/app/ui/view/molecule/form/form-root';
import FindIdSuccess from './find-id-success';

export default function FindIdContainer() {
const { Funnel, setStep } = useFunnel<'form' | 'success'>('form');
const [authId, setAuthId] = useState<string | undefined>(undefined);
return (
<div className="p-6">
<Funnel>
<Funnel.Step name="form">
<FindIdForm
onNext={(formState?: FormState) => {
if (formState?.value) setAuthId(formState.value.authId);
setStep('success');
}}
/>
</Funnel.Step>
<Funnel.Step name="success">
<FindIdSuccess authId={authId} />
</Funnel.Step>
</Funnel>
</div>
);
}
27 changes: 27 additions & 0 deletions app/(sub-page)/find-id/components/find-id-success.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Button from '@/app/ui/view/atom/button/button';
import Link from 'next/link';
interface FindIdSuccessProps {
authId: string | undefined;
}
export default function FindIdSuccess({ authId }: FindIdSuccessProps) {
return (
<div className=" flex items-center justify-center px-4 sm:px-6">
<div className="max-w-md w-full space-y-8">
<div className="space-y-2">
<p className="text-gray-500 text-center">입력하신 정보와 일치하는 아이디입니다.</p>
</div>
<div className="p-8 px-20 bg-light-blue-1 text-point-blue rounded-lg text-center">{authId}</div>
<div className="space-y-4">
<div className="flex justify-center gap-4">
<Link href="/sign-in">
<Button size="sm" label={'로그인 하기'} />
</Link>
<Link href="/find-password">
<Button size="sm" label={'비밀번호 바꾸기'} />
</Link>
</div>
</div>
</div>
</div>
);
}
16 changes: 16 additions & 0 deletions app/(sub-page)/find-id/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import ContentContainer from '@/app/ui/view/atom/content-container/content-container';
import TitleBox from '@/app/ui/view/molecule/title-box/title-box';
import FindIdContainer from './components/find-id-container';
import { Suspense } from 'react';
import FindIdFormSkeleton from '@/app/ui/user/find-id-form/find-id-form.skeleton';

export default function FindIdPage() {
return (
<ContentContainer className="p-4 pb-0">
<TitleBox title="아이디 찾기" />
<Suspense fallback={<FindIdFormSkeleton />}>
<FindIdContainer />
</Suspense>
</ContentContainer>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';

import useFunnel from '@/app/hooks/useFunnel';
import FindPasswordForm from '@/app/ui/user/find-password-from/find-password-form';
import FindPasswordValidateForm from '@/app/ui/user/find-password-from/find-password-validate-form';
import { FormState } from '@/app/ui/view/molecule/form/form-root';
import { useState } from 'react';

function FindPasswordContainer() {
const { Funnel, setStep } = useFunnel<'validate' | 'form'>('validate');
const [authId, setAuthId] = useState<string>('');

return (
<div className="p-6">
<Funnel>
<Funnel.Step name="validate">
<FindPasswordValidateForm
onNext={(formState?: FormState) => {
setStep('form');
if (formState?.value) setAuthId(formState?.value.authId);
}}
/>
</Funnel.Step>
<Funnel.Step name="form">
<FindPasswordForm authId={authId} />
</Funnel.Step>
</Funnel>
</div>
);
}

export default FindPasswordContainer;
18 changes: 18 additions & 0 deletions app/(sub-page)/find-password/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import ContentContainer from '@/app/ui/view/atom/content-container/content-container';
import TitleBox from '@/app/ui/view/molecule/title-box/title-box';
import FindPasswordContainer from './components/find-password-container';
import { Suspense } from 'react';
import FindIdFormSkeleton from '../../ui/user/find-id-form/find-id-form.skeleton';

function FindPasswordPage() {
return (
<ContentContainer className="p-4 pb-0">
<TitleBox title="비밀번호 재설정" />
<Suspense fallback={<FindIdFormSkeleton />}>
<FindPasswordContainer />
</Suspense>
</ContentContainer>
);
}

export default FindPasswordPage;
17 changes: 17 additions & 0 deletions app/(sub-page)/sign-in/components/auth-option-container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Link from 'next/link';

function AuthOptionContainer() {
return (
<div className="text-gray-6 flex gap-2">
<div className="after:content-['|'] after:pl-2">
<Link href="/find-id">아이디 찾기</Link>
</div>
<div className="after:content-['|'] after:pl-2">
<Link href="/find-password">비밀번호 재설정</Link>
</div>
<Link href="/sign-up">회원가입하기</Link>
</div>
);
}

export default AuthOptionContainer;
1 change: 1 addition & 0 deletions app/(sub-page)/sign-in/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SignInForm from '@/app/ui/user/sign-in-form/sign-in-form';
import ContentContainer from '@/app/ui/view/atom/content-container/content-container';

import TitleBox from '@/app/ui/view/molecule/title-box/title-box';
import Image from 'next/image';
import MaruImage from '@/public/assets/mju-maru.jpg';
Expand Down
1 change: 1 addition & 0 deletions app/(sub-page)/sign-up/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ContentContainer from '@/app/ui/view/atom/content-container/content-conta
import SignUpFormSkeleton from '@/app/ui/user/sign-up-form/sign-up-form.skeleton';
import type { Metadata } from 'next';


export const metadata: Metadata = {
title: '회원가입',
description: '졸업을 부탁해에 회원가입 하고 졸업요건을 간편하게 검사해 보세요.',
Expand Down
1 change: 1 addition & 0 deletions app/__test__/ui/user/user-info-navigator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ describe('UserInfoNavigator', () => {
expect(await screen.findByText(//i)).toBeInTheDocument();
expect(await screen.findByText(//i)).toBeInTheDocument();
expect(await screen.findByText(/60181666/i)).toBeInTheDocument();
// expect(await screen.findByText(/디지털콘텐츠디자인학과/i)).toBeInTheDocument();
});
});
67 changes: 65 additions & 2 deletions app/business/services/user/user.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@

import { FormState } from '@/app/ui/view/molecule/form/form-root';
import { API_PATH } from '../../api-path';
import { SignUpRequestBody, SignInRequestBody, ValidateTokenResponse, UserDeleteRequestBody } from './user.type';
import {
SignUpRequestBody,
SignInRequestBody,
ValidateTokenResponse,
UserDeleteRequestBody,
ResetPasswordRequestBody,
} from './user.type';
import { httpErrorHandler } from '@/app/utils/http/http-error-handler';
import { BadRequestError, UnauthorizedError } from '@/app/utils/http/http-error';
import {
SignUpFormSchema,
SignInFormSchema,
SignInResponseSchema,
ValidateTokenResponseSchema,
ResetPasswordFormSchema,
} from './user.validation';
import { cookies } from 'next/headers';
import { isValidation } from '@/app/utils/zod/validation.util';
import { redirect } from 'next/navigation';
import { auth } from './user.query';

function deleteCookies() {
cookies().delete('accessToken');
Expand Down Expand Up @@ -60,7 +68,6 @@ export async function deleteUser(prevState: FormState, formData: FormData): Prom
throw error;
}
}

deleteCookies();
redirect('/sign-in');
}
Expand Down Expand Up @@ -126,6 +133,13 @@ export async function authenticate(prevState: FormState, formData: FormData): Pr
}

redirect('/my');

return {
isSuccess: true,
isFailure: false,
validationError: {},
message: '로그인 완료 완료되었습니다.',
};
}

export async function refreshToken(): Promise<ValidateTokenResponse | false> {
Expand Down Expand Up @@ -218,3 +232,52 @@ export async function createUser(prevState: FormState, formData: FormData): Prom
message: '회원가입이 완료되었습니다.',
};
}

export async function resetPassword(prevState: FormState, formData: FormData): Promise<FormState> {
const validatedFields = ResetPasswordFormSchema.safeParse({
authId: formData.get('authId'),
newPassword: formData.get('newPassword'),
passwordCheck: formData.get('passwordCheck'),
});
if (!validatedFields.success) {
return {
isSuccess: false,
isFailure: true,
validationError: validatedFields.error.flatten().fieldErrors,
message: '양식에 맞춰 다시 입력해주세요.',
};
}
const { newPassword, passwordCheck, authId } = validatedFields.data;
const body: ResetPasswordRequestBody = {
authId,
newPassword,
passwordCheck,
};
try {
const response = await fetch(`${API_PATH.user}/password`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});

return {
isSuccess: true,
isFailure: false,
validationError: {},
message: '',
};
} catch (error) {
if (error instanceof BadRequestError) {
return {
isSuccess: false,
isFailure: true,
validationError: {},
message: '비밀번호 변경에 실패했습니다.',
};
} else {
throw error;
}
}
}
Loading

0 comments on commit 2fde0f6

Please sign in to comment.