-
Notifications
You must be signed in to change notification settings - Fork 0
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
Sign up form/#7 #50
Changes from 40 commits
c6c9926
b99e407
3ce84ad
2eb2eff
d7d31d3
3fb7609
ad7663e
94acb42
0778dd6
7d14ef1
13eb3da
820bc23
66defae
1ffb3cf
fa05b5b
138e06c
fc60afa
03e870a
39ddab6
c888fd9
2779ee5
4198e04
7ba5b37
37665b8
dd127e0
c6e8e9e
366f2e7
f71e81f
11c308b
b22027a
e862446
00ed836
dd44e7e
77873f7
4b1ff85
4a7308c
70cd343
414f300
e20d8fc
74f5861
73f94bc
2e109ec
812c794
ffbde27
e5a783d
fa2a5ad
9353625
9c49969
397f150
f6e7028
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. routerλ‘ λ°λ‘ κ΄λ¦¬νμ§ μκΈ° λλ¬Έμ νμκ°μ
νΌμμ λ€λ‘κ°κΈ° λ²νΌ ν΄λ¦ μ μ½κ΄ λͺ¨λ¬μ΄ λμ€μ§ μλ κ±Έ νμΈνμ΅λλ€! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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,33 @@ | ||
'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'; | ||
|
||
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="w-96"> | ||
<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> | ||
); | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sign-up-successλ sign-up-terms κ° νμ΄μ§ μ»΄ν¬λνΈμ μλ μ΄μ κ° λ°μ΄ν°κ° μ μ λλ©μΈμ μμ‘΄νκΈ° λ³΄λ¨ νμ΄μ§μ μμ‘΄ν΄μκ° λ§μκΉμ!? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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,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> | ||
); | ||
} |
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> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import SignUpContainer from './components/sign-up-container'; | ||
|
||
export default async function Page() { | ||
return ( | ||
<main> | ||
yougyung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<SignUpContainer /> | ||
</main> | ||
); | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
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`, | ||
takenLectures: `${BASE_URL}/taken-lectures`, | ||
user: `${BASE_URL}/users`, | ||
}; |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
'use server'; | ||
|
||
import { FormState } from '@/app/ui/view/molecule/form/form-root'; | ||
import { z } from 'zod'; | ||
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 { redirect } from 'next/navigation'; | ||
|
||
const SignUpFormSchema = z | ||
yougyung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.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'], | ||
}); | ||
} | ||
}); | ||
|
||
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: 'νμκ°μ μ΄ μλ£λμμ΅λλ€.', | ||
}; | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. μ λ type μ type ν΄λμ λΆλ¦¬νλλ° business ν΄λμ λΆλ¦¬νμ μ΄μ κ° μμκΉμ!? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. μ΄ λΆλΆμ λν΄μλ λ κ°μ§ μ΄μ κ° μμ΅λλ€.
μ 리νλ©΄, μ κ° μ μν νμ νμΌμ μλ²μ μμ²κ³Ό μλ΅μ λν νμ μ΄λ©°, μ΄λ query λλ commandμ κ°νκ² μ°κ²°λμ΄ μμΌλ―λ‘ 'colocate' μ² νμ λ°λΌ κ°μ ν΄λμ μμΉνλ κ²μ΄ μ’λ€κ³ μκ°ν©λλ€. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
κ²°κ΅ typeμ λν λ³κ²½μ΄ μΌμ΄λ¬μλ νμ§λ§ μ»΄ν¬λνΈμ μμ μ±μ λμΌ μ μλ€λ μ견μ λμνμ¬ ν΄λΉ ꡬ쑰λ₯Ό μ±ννκ³ μΆμ΅λλ€. λλΆμ΄ μμ μ± λ° μ μ°μ±μ λͺ©μ μΌλ‘ μ¬μ©νλ λ§νΌ colocateλ°©μμ ν΅ν response typeκ΄λ¦¬κ° μ μ©ν κ² κ°λ€λ μ견μλ λμν©λλ€. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. μλ΅κ° νμ
μ Responseλ₯Ό λΆμ΄λ κ²κ³Ό ν¨κ» λ³νλ κ²μ λν΄ κ·Όμ νκ² λλ κ² μ’μ κ² κ°μ΅λλ€! |
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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import React from 'react'; | ||
import { useState } from 'react'; | ||
|
||
export default function useFunnel<Steps>(defaultStae: Steps) { | ||
seonghunYang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const [step, setStep] = useState<Steps>(defaultStae); | ||
|
||
const Step = ({ name, children }: React.PropsWithChildren<{ name: Steps }>) => { | ||
return <>{children}</>; | ||
}; | ||
|
||
const FunnelRoot = ({ children }: React.PropsWithChildren) => { | ||
const targetStep = React.Children.toArray(children).find((childStep) => { | ||
return React.isValidElement(childStep) && childStep.props.name === step; | ||
}); | ||
|
||
return <>{targetStep}</>; | ||
}; | ||
|
||
const Funnel = Object.assign(FunnelRoot, { Step }); | ||
|
||
return { Funnel, setStep }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import { setupWorker } from 'msw/browser'; | ||
import { handlers } from './handlers.mock'; | ||
import { handlers } from './handlers'; | ||
|
||
export const worker = setupWorker(...handlers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://storybook.js.org/addons/msw-storybook-addon
parameters λμ initializeμμ ν λ²μ handlers λ₯Ό μ€μ ν΄μ€ μ μμ κ² κ°μμ!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
9c49969
μ’λ€μ. κ³ λ§μμ