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

Sign up form/#7 #50

Merged
merged 50 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
c6c9926
feat: Add Alert and AlertDestructive components
seonghunYang Mar 8, 2024
b99e407
refactor: Refactor form state structure
seonghunYang Mar 8, 2024
3ce84ad
feat: Add alert component to form
seonghunYang Mar 8, 2024
2eb2eff
feat: Add mock action function in form stories
seonghunYang Mar 8, 2024
d7d31d3
rename: Rename auth domain to user domain
seonghunYang Mar 8, 2024
3fb7609
feat: Update user command and add sign-up form
seonghunYang Mar 8, 2024
ad7663e
feat: Add user API endpoint to API_PATH
seonghunYang Mar 11, 2024
94acb42
feat: Add mock database
seonghunYang Mar 11, 2024
0778dd6
refactor: Refactor mock handlers structure
seonghunYang Mar 11, 2024
7d14ef1
chore: Add experimental serverActions configuration to next.config.mjs
seonghunYang Mar 11, 2024
13eb3da
refactor: Refactor user command and user type files
seonghunYang Mar 11, 2024
820bc23
feat: set Content-Type header in createUser function
seonghunYang Mar 11, 2024
66defae
feat: Fix createUser function to prevent duplicate user creation
seonghunYang Mar 11, 2024
1ffb3cf
feat: Refactor user creation and error handling
seonghunYang Mar 16, 2024
fa05b5b
feat: Add http error handling logic
seonghunYang Mar 16, 2024
138e06c
refactor: Refactor user command and sign-up form
seonghunYang Mar 16, 2024
fc60afa
fix: Fix select root component
seonghunYang Mar 16, 2024
03e870a
refactor: Update form field names and translations
seonghunYang Mar 16, 2024
39ddab6
refactor: update error message
seonghunYang Mar 16, 2024
c888fd9
fix: Update English level options in sign-up form
seonghunYang Mar 16, 2024
2779ee5
feat: Add msw-storybook-addon dependency
seonghunYang Mar 16, 2024
4198e04
chore: setting msw addon to storybook
seonghunYang Mar 16, 2024
7ba5b37
fix: Update API base URL in api-path.ts
seonghunYang Mar 16, 2024
37665b8
chore: Add test-storybook script to package.json
seonghunYang Mar 16, 2024
dd127e0
feat: Add sign-up form component and stories
seonghunYang Mar 16, 2024
c6e8e9e
test: Add aliases and mock files for testing in Storybook
seonghunYang Mar 16, 2024
366f2e7
feat: Update user-handler.mock.ts and sign-up-form.stories.tsx
seonghunYang Mar 17, 2024
f71e81f
feat: Add isSuccess property to FormState
seonghunYang Mar 17, 2024
11c308b
feat: Add sign-up page component
seonghunYang Mar 17, 2024
b22027a
feat: Add useFunnel hook for managing multi-step processes
seonghunYang Mar 17, 2024
e862446
refactor: Refactor SignUpForm component to include onNext prop
seonghunYang Mar 17, 2024
00ed836
feat: Add sign-up terms component
seonghunYang Mar 17, 2024
dd44e7e
feat: Add sign-up success component
seonghunYang Mar 17, 2024
77873f7
feat: Add sign-up container component
seonghunYang Mar 17, 2024
4b1ff85
refactor: Refactor sign-up page component
seonghunYang Mar 17, 2024
4a7308c
test: Add success and failure scenarios test for sign-up form
seonghunYang Mar 17, 2024
70cd343
refactor: Refactor app-router context provider and remove unused file
seonghunYang Mar 17, 2024
414f300
chore: Update sign-up components with mock content and styles
seonghunYang Mar 17, 2024
e20d8fc
refactor: Update SignUpFormSchema to SignUpFormMockSchema
seonghunYang Mar 17, 2024
74f5861
refactor: Remove console.log statement in SelectRoot component
seonghunYang Mar 17, 2024
73f94bc
Update app/hooks/useFunnel.tsx
seonghunYang Mar 19, 2024
2e109ec
fix: Fix typo in useFunnel hook
seonghunYang Mar 19, 2024
812c794
feat: Add ContentContainer component to sign-up page
seonghunYang Mar 19, 2024
ffbde27
chore: Remove revenue chart from dashboard page
seonghunYang Mar 19, 2024
e5a783d
refactor: Refactor useFunnel hook to include options for step query k…
seonghunYang Mar 19, 2024
fa2a5ad
feat: Refactor sign-up page to use Suspense and LoadingSpinner
seonghunYang Mar 19, 2024
9353625
refactor: Refactor useFunnel hook to use defaultStep instead of defau…
seonghunYang Mar 19, 2024
9c49969
chore: update initialization in preview.ts and remove userHandlers in…
seonghunYang Mar 19, 2024
397f150
refactor: Refactor user command module and add form validation
seonghunYang Mar 19, 2024
f6e7028
chore: Merge branch 'main' into sign-up-form/#7
seonghunYang Mar 27, 2024
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
1 change: 1 addition & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ const config: StorybookConfig = {
docs: {
autodocs: 'tag',
},
staticDirs: ['../public'],
};
export default config;
5 changes: 5 additions & 0 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { Preview } from '@storybook/react';
import '../app/globals.css';
import { handlers } from '../app/mocks/handlers';
import { initialize, mswDecorator } from 'msw-storybook-addon';

initialize({}, [...handlers]);

const preview: Preview = {
decorators: [mswDecorator],
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
Expand Down
34 changes: 34 additions & 0 deletions app/(sub-page)/sign-up/components/sign-up-container.tsx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

router둜 λ”°λ‘œ κ΄€λ¦¬ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— νšŒμ›κ°€μž… νΌμ—μ„œ λ’€λ‘œκ°€κΈ° λ²„νŠΌ 클릭 μ‹œ μ•½κ΄€ λͺ¨λ‹¬μ΄ λ‚˜μ˜€μ§€ μ•ŠλŠ” κ±Έ ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€!
λ”°λ‘œ μ΄μœ κ°€ μžˆμ„κΉŒμš©?

Copy link
Collaborator Author

@seonghunYang seonghunYang Mar 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 였늘 μ‹œκ°„μ΄ μ’€ μƒκ²¨μ„œ historyλ₯Ό 관리할 수 있게 κ°œμ„ ν•΄λ΄€μ–΄μš”. 이 뢀뢄에 λŒ€ν•΄ λ‹€μ‹œ ν•œλ²ˆ μžμ„Ένžˆ 리뷰 λΆ€νƒλ“œλ €μš”.
  • PR λ©”μ‹œμ§€μ— μž‘μ—… 쀑 λ°œμƒν•œ μ΄μŠˆλ“€λ„ 기둝해 λ‘μ—ˆμœΌλ‹ˆ 확인 λΆ€νƒν•΄μš”.
  • e5a783d
  • fa2a5ad

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ν™•μΈν–ˆκ΅¬ 리뷰 λ‚¨κ²ΌμŠ΅λ‹ˆλ‹€!

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client';
import useFunnel from '@/app/hooks/useFunnel';
import SignUpForm from '@/app/ui/user/sign-up-form/sign-up-form';
import SignUpTerm from './sign-up-terms';
import SignUpSuccess from './sign-up-success';
import ContentContainer from '@/app/ui/view/atom/content-container';

export default function SignUpContainer() {
const { Funnel, setStep } = useFunnel<'terms' | 'form' | 'success'>('terms');
yougyung marked this conversation as resolved.
Show resolved Hide resolved

return (
<div className="p-6">
<Funnel>
<Funnel.Step name="terms">
<SignUpTerm
onNext={() => {
setStep('form');
}}
/>
</Funnel.Step>
<Funnel.Step name="form">
<SignUpForm
onNext={() => {
setStep('success');
}}
/>
</Funnel.Step>
<Funnel.Step name="success">
<SignUpSuccess />
</Funnel.Step>
</Funnel>
</div>
);
}
25 changes: 25 additions & 0 deletions app/(sub-page)/sign-up/components/sign-up-success.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Button from '@/app/ui/view/atom/button/button';
import Link from 'next/link';

// λ‚΄μš©μ΄λž‘ μŠ€νƒ€μΌμ€ mock인 μƒνƒœμž…λ‹ˆλ‹€.
export default function SignUpSuccess() {
return (
<div className="min-h-screen bg-gray-100 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">
<h2 className="text-3xl font-extrabold tracking-tight">Youre all set.</h2>
<p className="text-gray-500">
Thanks for signing up! We just need to verify your email address to complete the process.
</p>
</div>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<Link className="inline-block w-full" href="/login">
<Button className="w-full" label={'둜그인 ν•˜κΈ°'} />
</Link>
</div>
</div>
</div>
</div>
);
}
56 changes: 56 additions & 0 deletions app/(sub-page)/sign-up/components/sign-up-terms.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Button from '@/app/ui/view/atom/button/button';

interface SignUpTermProps {
onNext?: () => void;
}

// μ•½κ΄€ λ‚΄μš©μ΄λž‘ μŠ€νƒ€μΌμ€ mock인 μƒνƒœμž…λ‹ˆλ‹€.
export default function SignUpTerm({ onNext }: SignUpTermProps) {
const handleAgreeButtonClick = () => {
onNext?.();
};

return (
<div className="max-w-2xl mx-auto my-8 p-6 bg-white rounded-lg shadow-md">
<h1 className="text-3xl font-bold text-center mb-6">μ•Œλ¦Όλ…μ˜ μ•ˆλ‚΄λ¬Έ</h1>
<ul className="list-disc space-y-4 text-sm">
<li>
ν˜„μž¬ 저희 κΈ°λŠ₯은 ν•œκ΅­-ν•œκ΅­μ€ μ•„λ‹ˆλΌ ν•œκ΅­-외ꡭ도 κ°€λŠ₯ν•©λ‹ˆλ‹€. κ°μ‚¬λ³„μ—μ„œ μˆ˜ν•˜μΈ 없더라도 저희가 λ°›λŠ”λ°κΉŒμ§€λŠ”
λ¬΄κ΄€ν•©λ‹ˆλ‹€λ§Œ, κΌ­ κ΄€μ„Έμ‚¬λ¬΄μ†Œμ™€ ν˜‘μ˜ν•˜μ„Έμš”!
<ul className="list-disc ml-6 mt-2">
<li>λŒ€μƒ: κ΅­μ™Έλ°œμ†‘, λΆλΆ€λ°œμ†‘, μ‚¬νšŒλ³΅μ§€λŒ€μƒ, ICTμš©ν’ˆλŒ€μƒ, μΌλ°˜λŒ€μƒ, λ―Έλž˜μš©ν’ˆλŒ€μƒ(확인)</li>
<li>λ°œμ†‘: 16 ~ 22μ‹œλ°œ</li>
</ul>
</li>
<li>
ꡐ직, λ””μžμΈ, μ—°κ³„κ°œλ°œ, λ¬Όν’ˆ, μ „μž, μžμ›κ΄€λ¦¬/νšŒκ³„μ§€μ›μ— ν•΄λ‹Ήν•˜λŠ” μ‚¬μš©μžλŠ” 각사 기쀀에 λ”°λ₯Έ μ„ μ •λ˜μ§€ μ•Šμ•„ 각사
별 κ΄€λ¦¬ν•˜λŠ”λ°μš”.
</li>
<li>검사λ₯Ό μœ„ν•΄μ„œ μ„ μ ν’ˆμ„ 직접 μ—°λ½λ“œλ €μ•Όλ§Œ PCν™”λ©΄μ—μ„œ μ§„ν–‰ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€.</li>
<li>
검사 기쀀은 μ΅œμ‹ λ²„μ „ 확인내역(2023.07.24) λ°˜μ˜ν•˜μ—¬ μ„ μ •λ˜μ—ˆμœΌλ©°, 학사내역은 맀년 κ°œνŽΈλ˜λ―€λ‘œ μžμ‚¬μ΄ μ™Έκ³  μžˆλŠ”
ꡬ버전과 λ‹€λ₯Ό 수 μžˆμŠ΅λ‹ˆλ‹€.
<ul className="list-disc ml-6 mt-2">
<li>λ¬ΈμžλŒ€ν•­: 학사내역은 확인 클릭</li>
</ul>
</li>
<li>
λ³Έ μ„œλΉ„μŠ€ μ •λ³΄λŠ” 곡식적인 확인을 μ „μ œ μ•ŠμœΌλ©°, μ •ν™•ν•œ 증상쑰사결과λ₯Ό μœ„ν•΄ μ„œλ₯˜ λ˜λŠ” λ‹΄λ‹Ήκ³Ό ꡐλ₯˜ν•΄μ•Όν•  사항을
μžŠμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
</li>
<li>
μ „μžλ¬Έ μ„œν™”μ§€ λ°μ΄ν„°λ² μ΄μŠ€λŠ” μ˜λ¬΄ν™”λ˜μ–΄ 저희가 κ³ μœ μΆ•μ  및 κ΅μœ‘κ³Όμ • λ“±μ—μ„œ μ‚¬μš©λ˜λ©°, μ–΄λ–€ λ‹€λ₯Έ μš©λ„λ‘œ μ‚¬μš©
λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
</li>
<li>νŠΉν—ˆμš”κ±΄ 기쀀이 μ „λ¬Έ μ„ μ •λ˜μ—ˆκ±°λ‚˜, 였λ₯˜λ°œμƒ μ‹œ 우츑 ν•˜λ‹¨ μ±„νŒ…μ°½μœΌλ‘œ λ˜λŠ” λ‹΄λ‹Ή λΆ€μ„œλ‘œλ¬Έμ˜ν•©λ‹ˆλ‹€.</li>
</ul>
<div className="mt-8 flex justify-center">
<Button
onClick={handleAgreeButtonClick}
className="ml-4 bg-blue-500 text-white py-2 px-4 rounded-full"
label={'μ•Œλ¦Όλ…μ˜ μ•ˆλ‚΄λ¬Έ'}
/>
</div>
</div>
);
}
21 changes: 21 additions & 0 deletions app/(sub-page)/sign-up/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ContentContainer from '@/app/ui/view/atom/content-container';
import SignUpContainer from './components/sign-up-container';
import { Suspense } from 'react';
import LoadingSpinner from '@/app/ui/view/atom/loading-spinner';

// Refactor: fallback μŠ€μΌˆλ ˆν†€μœΌλ‘œ λŒ€μ²΄
export default function Page() {
return (
<ContentContainer className="md:w-[768px]">
<Suspense
fallback={
<div className="h-96">
<LoadingSpinner />
</div>
}
>
<SignUpContainer />
</Suspense>
</ContentContainer>
);
}
14 changes: 0 additions & 14 deletions app/__test__/ui/invoice/revenu-chart.test.tsx

This file was deleted.

3 changes: 2 additions & 1 deletion app/business/api-path.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const BASE_URL = process.env.API_MOCKING === 'enable' ? 'http://localhost:9090' : 'http://mock.api.com';
const BASE_URL = process.env.API_MOCKING === 'enable' ? 'http://localhost:9090' : 'https://mock.api.com';

export const API_PATH = {
revenue: `${BASE_URL}/revenue`,
registerUserGrade: `${BASE_URL}/registerUserGrade`,
parsePDFtoText: `${BASE_URL}/parsePDFtoText`,
takenLectures: `${BASE_URL}/taken-lectures`,
user: `${BASE_URL}/users`,
};
67 changes: 0 additions & 67 deletions app/business/auth/user.command.ts

This file was deleted.

2 changes: 1 addition & 1 deletion app/business/lecture/taken-lecture.query.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LectureInfo } from '@/app/type/lecture';
import { API_PATH } from '../api-path';

interface TakenLectures {
export interface TakenLectures {
totalCredit: number;
takenLectures: LectureInfo[];
}
Expand Down
69 changes: 69 additions & 0 deletions app/business/user/user.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use server';

import { FormState } from '@/app/ui/view/molecule/form/form-root';
import { API_PATH } from '../api-path';
import { SignUpRequestBody } from './user.type';
import { httpErrorHandler } from '@/app/utils/http/http-error-handler';
import { BadRequestError } from '@/app/utils/http/http-error';
import { SignUpFormSchema } from './user.validation';

export async function createUser(prevState: FormState, formData: FormData): Promise<FormState> {
const validatedFields = SignUpFormSchema.safeParse({
authId: formData.get('authId'),
password: formData.get('password'),
confirmPassword: formData.get('confirmPassword'),
studentNumber: formData.get('studentNumber'),
engLv: formData.get('engLv'),
});

if (!validatedFields.success) {
return {
isSuccess: false,
isFailure: true,
validationError: validatedFields.error.flatten().fieldErrors,
message: '양식에 맞좰 λ‹€μ‹œ μž…λ ₯ν•΄μ£Όμ„Έμš”.',
};
}

const { authId, password, studentNumber, engLv } = validatedFields.data;
const body: SignUpRequestBody = {
authId,
password,
studentNumber,
engLv,
};

try {
const response = await fetch(`${API_PATH.user}/sign-up`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});

const result = await response.json();

httpErrorHandler(response, result);
} catch (error) {
if (error instanceof BadRequestError) {
// 잘λͺ»λœ μš”μ²­ 처리 둜직
return {
isSuccess: false,
isFailure: true,
validationError: {},
message: error.message,
};
} else {
// λ‚˜λ¨Έμ§€ μ—λŸ¬λŠ” 더 μƒμœ„ μˆ˜μ€€μ—μ„œ 처리
throw error;
}
}

return {
isSuccess: true,
isFailure: false,
validationError: {},
message: 'νšŒμ›κ°€μž…μ΄ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.',
};
}
9 changes: 9 additions & 0 deletions app/business/user/user.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// https://stackoverflow.com/questions/76957592/error-only-async-functions-are-allowed-to-be-exported-in-a-use-server-file
// server action νŒŒμΌμ—μ„œλŠ” async function만 export κ°€λŠ₯

export interface SignUpRequestBody {
authId: string;
password: string;
studentNumber: string;
engLv: string;
}
36 changes: 36 additions & 0 deletions app/business/user/user.validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { z } from 'zod';

export const SignUpFormSchema = z
.object({
authId: z
.string()
.min(6, {
message: 'μ•„μ΄λ””λŠ” 6자 이상 20자 μ΄ν•˜μ—¬μ•Ό ν•©λ‹ˆλ‹€.',
})
.max(20, {
message: 'User ID must be at most 20 characters',
}),
password: z
.string()
.min(8, { message: 'λΉ„λ°€λ²ˆν˜ΈλŠ” 8자 이상이어야 ν•©λ‹ˆλ‹€.' })
.regex(/^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$/, {
message: 'λΉ„λ°€λ²ˆν˜ΈλŠ” 문자, 숫자, 특수문자(!@#$%^&*)λ₯Ό 포함해야 ν•©λ‹ˆλ‹€.',
})
.max(20, { message: 'λΉ„λ°€λ²ˆν˜ΈλŠ” 20자 μ΄ν•˜μ—¬μ•Ό ν•©λ‹ˆλ‹€.' }),
confirmPassword: z.string(),
studentNumber: z.string().length(8, { message: 'ν•™λ²ˆμ€ 8μžλ¦¬μ—¬μ•Ό ν•©λ‹ˆλ‹€.' }).startsWith('60', {
message: 'ν•™λ²ˆμ€ 60으둜 μ‹œμž‘ν•΄μ•Ό ν•©λ‹ˆλ‹€.',
}),
engLv: z.enum(['basic', 'ENG12', 'ENG34', 'bypass'], {
invalid_type_error: 'μ˜¬λ°”λ₯Έ μ˜μ–΄ λ ˆλ²¨μ„ μ„ νƒν•΄μ£Όμ„Έμš”.',
}),
})
.superRefine(({ confirmPassword, password }, ctx) => {
if (confirmPassword !== password) {
ctx.addIssue({
code: 'custom',
message: 'λΉ„λ°€λ²ˆν˜Έκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.',
path: ['confirmPassword'],
});
}
});
6 changes: 1 addition & 5 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ export default async function Page() {
return (
<main>
<h1 className={'mb-4 text-xl md:text-2xl'}>Dashboard</h1>
<div className="mt-6">
<Suspense fallback={<RevenueChartSkeleton />}>
<RevenueChart />
</Suspense>
</div>
<div className="mt-6"></div>
</main>
);
}
Loading
Loading