@uniai-fe/uds-templates 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -1
- package/dist/styles.css +2051 -2266
- package/package.json +5 -3
- package/src/auth/common/complete/Template.tsx +47 -0
- package/src/auth/common/complete/img/circle-check-complete.svg +4 -0
- package/src/auth/common/complete/index.scss +38 -0
- package/src/auth/common/complete/types.ts +15 -0
- package/src/auth/common/container/header/StageHeader.tsx +61 -0
- package/src/auth/common/container/header/index.tsx +5 -0
- package/src/auth/common/container/header/stage-header.scss +50 -0
- package/src/{components/auth → auth/common}/container/index.tsx +2 -0
- package/src/auth/common/find/hooks/useFindAccountForm.ts +79 -0
- package/src/auth/common/find/markup/CodeStep.tsx +166 -0
- package/src/auth/common/find/markup/Header.tsx +46 -0
- package/src/auth/common/find/markup/InfoStep.tsx +109 -0
- package/src/auth/common/find/styles/email.scss +55 -0
- package/src/auth/common/find/styles/find-account.scss +4 -0
- package/src/auth/common/find/styles/layout.scss +19 -0
- package/src/auth/common/find/styles/password.scss +39 -0
- package/src/auth/common/find/styles/result.scss +78 -0
- package/src/auth/common/find/types/forms.ts +30 -0
- package/src/auth/common/find/types/index.ts +121 -0
- package/src/auth/common/find/utils/composeFieldProps.ts +45 -0
- package/src/auth/common/password/constants.ts +19 -0
- package/src/auth/common/password/hooks/useCheckPassword.ts +133 -0
- package/src/auth/common/password/img/check-password.svg +3 -0
- package/src/auth/common/password/markup/PasswordSetField.tsx +250 -0
- package/src/auth/common/password/styles/password-set-field.scss +49 -0
- package/src/auth/common/password/types.ts +142 -0
- package/src/auth/common/password/utils/composePasswordFieldProps.ts +44 -0
- package/src/auth/find-account.ts +28 -0
- package/src/auth/find-id/hooks/index.ts +1 -0
- package/src/auth/find-id/index.scss +1 -0
- package/src/auth/find-id/index.ts +23 -0
- package/src/auth/find-id/markup/StepComplete.tsx +58 -0
- package/src/auth/find-id/markup/StepIdentify.tsx +46 -0
- package/src/auth/find-id/markup/StepVerifyCode.tsx +48 -0
- package/src/auth/find-id/types/index.ts +66 -0
- package/src/auth/find-password/index.scss +1 -0
- package/src/auth/find-password/index.ts +30 -0
- package/src/auth/find-password/markup/StepComplete.tsx +30 -0
- package/src/auth/find-password/markup/StepIdentify.tsx +45 -0
- package/src/auth/find-password/markup/StepResetPassword.tsx +150 -0
- package/src/auth/find-password/markup/StepVerifyCode.tsx +48 -0
- package/src/auth/index.tsx +41 -0
- package/src/{components/auth → auth}/login/index.tsx +1 -7
- package/src/{components/auth → auth}/login/markup/Container.tsx +1 -1
- package/src/{components/auth → auth}/login/markup/FormField.tsx +2 -2
- package/src/{components/auth → auth}/login/types/props.ts +13 -13
- package/src/auth/login/types.ts +2 -0
- package/src/auth/signup/hooks/index.ts +3 -0
- package/src/auth/signup/hooks/useSignupAccountForm.ts +101 -0
- package/src/auth/signup/hooks/useSignupUserInfoForm.ts +88 -0
- package/src/auth/signup/hooks/useSignupVerificationForm.ts +77 -0
- package/src/auth/signup/img/check-agree.svg +3 -0
- package/src/auth/signup/img/chevron-open-detail.svg +3 -0
- package/src/auth/signup/index.ts +27 -0
- package/src/auth/signup/markup/AccountForm.tsx +113 -0
- package/src/auth/signup/markup/Complete.tsx +59 -0
- package/src/auth/signup/markup/Template.tsx +110 -0
- package/src/auth/signup/markup/UserInfoForm.tsx +107 -0
- package/src/auth/signup/markup/VerificationForm.tsx +285 -0
- package/src/auth/signup/markup/index.ts +5 -0
- package/src/auth/signup/styles/signup.scss +187 -0
- package/src/auth/signup/types/hooks.ts +86 -0
- package/src/auth/signup/types/index.ts +2 -0
- package/src/auth/signup/types/props.ts +145 -0
- package/src/auth/signup/utils/composeFieldProps.ts +50 -0
- package/src/auth/signup/utils/getSignupFieldDefaultValue.ts +40 -0
- package/src/index.scss +5 -3
- package/src/index.tsx +3 -2
- package/src/modal/core/components/Container.tsx +41 -0
- package/src/modal/core/components/FooterButtons.tsx +132 -0
- package/src/modal/core/components/Provider.tsx +28 -0
- package/src/modal/core/components/Root.tsx +93 -0
- package/src/modal/core/hooks/useModal.ts +136 -0
- package/src/modal/core/jotai/atoms.ts +10 -0
- package/src/modal/index.scss +4 -0
- package/src/modal/index.tsx +16 -0
- package/src/modal/styles/animations.scss +24 -0
- package/src/modal/styles/base.scss +45 -0
- package/src/modal/styles/container.scss +138 -0
- package/src/modal/styles/dimmer.scss +23 -0
- package/src/modal/templates/Alert.tsx +104 -0
- package/src/modal/templates/Dialog.tsx +112 -0
- package/src/modal/types/footer.ts +36 -0
- package/src/modal/types/index.ts +21 -0
- package/src/modal/types/options.ts +6 -0
- package/src/modal/types/state.ts +31 -0
- package/src/modal/types/templates.ts +32 -0
- package/src/page-frame/mobile/header/PageFrameMobileHeader.tsx +52 -0
- package/src/page-frame/mobile/header/index.ts +4 -0
- package/src/page-frame/mobile/header/page-frame-mobile-header.scss +48 -0
- package/src/page-frame/mobile/img/chevron-backward.svg +3 -0
- package/src/components/auth/index.tsx +0 -20
- package/src/components/auth/login/types.ts +0 -2
- /package/src/{components/auth → auth/common}/container/AuthContainer.tsx +0 -0
- /package/src/{components/auth → auth/common}/container/index.scss +0 -0
- /package/src/{components/auth → auth/common}/container/types.ts +0 -0
- /package/src/{components/auth → auth}/login/data/valid-options.ts +0 -0
- /package/src/{components/auth → auth}/login/hooks/index.ts +0 -0
- /package/src/{components/auth → auth}/login/hooks/useAuthLoginForm.ts +0 -0
- /package/src/{components/auth → auth}/login/index.scss +0 -0
- /package/src/{components/auth → auth}/login/markup/LinkButtons.tsx +0 -0
- /package/src/{components/auth → auth}/login/styles/login.scss +0 -0
- /package/src/{components/auth → auth}/login/types/form.ts +0 -0
- /package/src/{components/auth → auth}/login/types/hooks.ts +0 -0
- /package/src/{components/page-frame → page-frame}/container/PageFrameContainer.tsx +0 -0
- /package/src/{components/page-frame → page-frame}/container/index.scss +0 -0
- /package/src/{components/page-frame → page-frame}/container/index.tsx +0 -0
- /package/src/{components/page-frame → page-frame}/container/types.ts +0 -0
- /package/src/{components/page-frame → page-frame}/index.tsx +0 -0
- /package/src/{components/page-frame → page-frame}/mobile/PageFrameMobile.tsx +0 -0
- /package/src/{components/page-frame → page-frame}/mobile/index.scss +0 -0
- /package/src/{components/page-frame → page-frame}/mobile/index.tsx +0 -0
- /package/src/{components/page-frame → page-frame}/mobile/types.ts +0 -0
- /package/src/{components/page-frame → page-frame}/navigation/PageFrameNavigation.tsx +0 -0
- /package/src/{components/page-frame → page-frame}/navigation/index.scss +0 -0
- /package/src/{components/page-frame → page-frame}/navigation/index.tsx +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useWatch } from "react-hook-form";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import type {
|
|
5
|
+
AuthSignupFieldProps,
|
|
6
|
+
AuthSignupUserInfoFields,
|
|
7
|
+
AuthSignupUserInfoValues,
|
|
8
|
+
UseSignupUserInfoFormOptions,
|
|
9
|
+
UseSignupUserInfoFormReturn,
|
|
10
|
+
} from "../types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 회원가입 기본 정보 훅 옵션
|
|
14
|
+
* @typedef {UseSignupUserInfoFormOptions}
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* 회원가입 Step1 훅; 사용자 기본 정보
|
|
18
|
+
* @hook
|
|
19
|
+
* @template TFields
|
|
20
|
+
* @param {UseSignupUserInfoFormOptions<TFields>} options 훅 옵션
|
|
21
|
+
* @desc
|
|
22
|
+
* - 1) form init → 2) register merge → 3) helper/state → 4) submit 순서
|
|
23
|
+
*/
|
|
24
|
+
export function useSignupUserInfoForm<
|
|
25
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
26
|
+
AuthSignupUserInfoFields,
|
|
27
|
+
>({
|
|
28
|
+
fields,
|
|
29
|
+
form,
|
|
30
|
+
onSubmit,
|
|
31
|
+
activeFields,
|
|
32
|
+
}: UseSignupUserInfoFormOptions<TFields>): UseSignupUserInfoFormReturn<TFields> {
|
|
33
|
+
const values = useWatch({ control: form.control }) as
|
|
34
|
+
| AuthSignupUserInfoValues
|
|
35
|
+
| undefined;
|
|
36
|
+
|
|
37
|
+
const register = useMemo(() => {
|
|
38
|
+
return (Object.keys(fields) as Array<keyof TFields>).reduce(
|
|
39
|
+
(acc, fieldKey) => {
|
|
40
|
+
const config = fields[fieldKey] as AuthSignupFieldProps;
|
|
41
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
42
|
+
acc[fieldKey] = form.register(fieldName);
|
|
43
|
+
return acc;
|
|
44
|
+
},
|
|
45
|
+
{} as UseSignupUserInfoFormReturn<TFields>["register"],
|
|
46
|
+
);
|
|
47
|
+
}, [fields, form]);
|
|
48
|
+
|
|
49
|
+
const helpers = useMemo(() => {
|
|
50
|
+
return (Object.keys(fields) as Array<keyof TFields>).reduce(
|
|
51
|
+
(acc, fieldKey) => {
|
|
52
|
+
const config = fields[fieldKey] as AuthSignupFieldProps;
|
|
53
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
54
|
+
const state = form.getFieldState(fieldName);
|
|
55
|
+
acc[fieldKey] = {
|
|
56
|
+
text:
|
|
57
|
+
(state.error?.message as ReactNode | undefined) ?? config.helper,
|
|
58
|
+
state: state.invalid ? "error" : undefined,
|
|
59
|
+
};
|
|
60
|
+
return acc;
|
|
61
|
+
},
|
|
62
|
+
{} as UseSignupUserInfoFormReturn<TFields>["helpers"],
|
|
63
|
+
);
|
|
64
|
+
}, [fields, form]);
|
|
65
|
+
|
|
66
|
+
const targetKeys =
|
|
67
|
+
activeFields ?? (Object.keys(fields) as Array<keyof TFields>);
|
|
68
|
+
|
|
69
|
+
const trimmedFilled =
|
|
70
|
+
values &&
|
|
71
|
+
targetKeys.every(fieldKey => {
|
|
72
|
+
const fieldName = (fields[fieldKey].attr?.name ??
|
|
73
|
+
String(fieldKey)) as keyof AuthSignupUserInfoValues;
|
|
74
|
+
const value = values[fieldName];
|
|
75
|
+
return typeof value === "string" ? value.trim().length > 0 : false;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const disabled = form.formState.isSubmitting || !trimmedFilled;
|
|
79
|
+
|
|
80
|
+
const onSubmitHandler = form.handleSubmit(onSubmit);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
register,
|
|
84
|
+
helpers,
|
|
85
|
+
disabled,
|
|
86
|
+
onSubmit: onSubmitHandler,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useWatch } from "react-hook-form";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import type {
|
|
5
|
+
AuthSignupFieldProps,
|
|
6
|
+
AuthSignupVerificationFields,
|
|
7
|
+
AuthSignupVerificationValues,
|
|
8
|
+
UseSignupVerificationFormOptions,
|
|
9
|
+
UseSignupVerificationFormReturn,
|
|
10
|
+
} from "../types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 회원가입 Step2 훅; 약관 + 이메일 인증
|
|
14
|
+
* @hook
|
|
15
|
+
* @template TFields
|
|
16
|
+
* @param {UseSignupVerificationFormOptions<TFields>} options
|
|
17
|
+
* @desc
|
|
18
|
+
* - 1) form init → 2) register merge → 3) helper/state → 4) submit 순서
|
|
19
|
+
*/
|
|
20
|
+
export function useSignupVerificationForm<
|
|
21
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
22
|
+
AuthSignupVerificationFields,
|
|
23
|
+
>({
|
|
24
|
+
fields,
|
|
25
|
+
form,
|
|
26
|
+
onSubmit,
|
|
27
|
+
}: UseSignupVerificationFormOptions<TFields>): UseSignupVerificationFormReturn<TFields> {
|
|
28
|
+
const values = useWatch({ control: form.control }) as
|
|
29
|
+
| AuthSignupVerificationValues
|
|
30
|
+
| undefined;
|
|
31
|
+
|
|
32
|
+
const register = useMemo(() => {
|
|
33
|
+
return (Object.keys(fields) as Array<keyof TFields>).reduce(
|
|
34
|
+
(acc, fieldKey) => {
|
|
35
|
+
const config = fields[fieldKey] as AuthSignupFieldProps;
|
|
36
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
37
|
+
acc[fieldKey] = form.register(fieldName);
|
|
38
|
+
return acc;
|
|
39
|
+
},
|
|
40
|
+
{} as UseSignupVerificationFormReturn<TFields>["register"],
|
|
41
|
+
);
|
|
42
|
+
}, [fields, form]);
|
|
43
|
+
|
|
44
|
+
const helpers = useMemo(() => {
|
|
45
|
+
return (Object.keys(fields) as Array<keyof TFields>).reduce(
|
|
46
|
+
(acc, fieldKey) => {
|
|
47
|
+
const config = fields[fieldKey] as AuthSignupFieldProps;
|
|
48
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
49
|
+
const state = form.getFieldState(fieldName);
|
|
50
|
+
acc[fieldKey] = {
|
|
51
|
+
text:
|
|
52
|
+
(state.error?.message as ReactNode | undefined) ?? config.helper,
|
|
53
|
+
state: state.invalid ? "error" : undefined,
|
|
54
|
+
};
|
|
55
|
+
return acc;
|
|
56
|
+
},
|
|
57
|
+
{} as UseSignupVerificationFormReturn<TFields>["helpers"],
|
|
58
|
+
);
|
|
59
|
+
}, [fields, form]);
|
|
60
|
+
|
|
61
|
+
const trimmedFilled =
|
|
62
|
+
values &&
|
|
63
|
+
Object.values(values).every(value =>
|
|
64
|
+
typeof value === "string" ? value.trim().length > 0 : false,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const disabled = form.formState.isSubmitting || !trimmedFilled;
|
|
68
|
+
|
|
69
|
+
const onSubmitHandler = form.handleSubmit(onSubmit);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
register,
|
|
73
|
+
helpers,
|
|
74
|
+
disabled,
|
|
75
|
+
onSubmit: onSubmitHandler,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M5.87377 3.74906L10.1164 7.9917L5.87377 12.2343C5.56135 12.5468 5.56135 13.0533 5.87377 13.3657C6.18619 13.6781 6.69272 13.6781 7.00514 13.3657L11.8135 8.55739C12.1259 8.24497 12.1259 7.73843 11.8135 7.42601L7.00514 2.61769C6.69272 2.30527 6.18619 2.30527 5.87377 2.61769C5.56135 2.93011 5.56135 3.43664 5.87377 3.74906Z" fill="#CACBCE"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthSignupUserInfoForm,
|
|
3
|
+
AuthSignupVerificationForm,
|
|
4
|
+
AuthSignupAccountForm,
|
|
5
|
+
AuthSignupComplete,
|
|
6
|
+
AuthSignupTemplate,
|
|
7
|
+
} from "./markup";
|
|
8
|
+
|
|
9
|
+
import "./styles/signup.scss";
|
|
10
|
+
|
|
11
|
+
export type * from "./types";
|
|
12
|
+
export * from "./hooks";
|
|
13
|
+
export {
|
|
14
|
+
AuthSignupUserInfoForm,
|
|
15
|
+
AuthSignupVerificationForm,
|
|
16
|
+
AuthSignupAccountForm,
|
|
17
|
+
AuthSignupComplete,
|
|
18
|
+
AuthSignupTemplate,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const AuthSignup = {
|
|
22
|
+
Template: AuthSignupTemplate,
|
|
23
|
+
StepUserInfo: AuthSignupUserInfoForm,
|
|
24
|
+
StepVerification: AuthSignupVerificationForm,
|
|
25
|
+
StepAccount: AuthSignupAccountForm,
|
|
26
|
+
StepComplete: AuthSignupComplete,
|
|
27
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { FormProvider, useForm } from "react-hook-form";
|
|
3
|
+
import { Button, Input, type InputProps } from "@uniai-fe/uds-primitives";
|
|
4
|
+
import type { AuthSignupAccountProps, AuthSignupAccountValues } from "../types";
|
|
5
|
+
import { useSignupAccountForm } from "../hooks";
|
|
6
|
+
import { composeSignupFieldProps } from "../utils/composeFieldProps";
|
|
7
|
+
import { getSignupFieldDefaultValue } from "../utils/getSignupFieldDefaultValue";
|
|
8
|
+
import AuthPasswordSetField from "../../common/password/markup/PasswordSetField";
|
|
9
|
+
import { DEFAULT_PASSWORD_RULES } from "../../common/password/constants";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_SUBMIT_LABEL = "가입 요청";
|
|
12
|
+
/**
|
|
13
|
+
* 회원가입 Step3; 계정 정보 입력(아이디/비밀번호)
|
|
14
|
+
* @component
|
|
15
|
+
* @param {AuthSignupAccountProps} props account props
|
|
16
|
+
* @param {AuthSignupAccountProps["fields"]} props.fields 아이디/비밀번호 필드
|
|
17
|
+
* @param {AuthSignupPasswordRule[]} [props.passwordRules] 비밀번호 규칙 상태
|
|
18
|
+
* @param {React.FormHTMLAttributes<HTMLFormElement>} [props.formAttr] form attr
|
|
19
|
+
* @param {import("react").ReactNode} [props.submitLabel] CTA 라벨
|
|
20
|
+
*/
|
|
21
|
+
export function AuthSignupAccountForm({
|
|
22
|
+
fields,
|
|
23
|
+
passwordRules,
|
|
24
|
+
formAttr,
|
|
25
|
+
submitLabel,
|
|
26
|
+
onSubmit,
|
|
27
|
+
}: AuthSignupAccountProps) {
|
|
28
|
+
// 필드 구성에서 name attr을 추출해 RHF default 값을 정의한다.
|
|
29
|
+
const defaultValues = useMemo(
|
|
30
|
+
() =>
|
|
31
|
+
(Object.keys(fields) as Array<keyof typeof fields>).reduce(
|
|
32
|
+
(acc, fieldKey) => {
|
|
33
|
+
const config = fields[fieldKey];
|
|
34
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
35
|
+
acc[fieldName] = getSignupFieldDefaultValue(config);
|
|
36
|
+
return acc;
|
|
37
|
+
},
|
|
38
|
+
{} as AuthSignupAccountValues,
|
|
39
|
+
),
|
|
40
|
+
[fields],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const form = useForm<AuthSignupAccountValues>({
|
|
44
|
+
mode: "onChange",
|
|
45
|
+
defaultValues,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const {
|
|
49
|
+
register,
|
|
50
|
+
helpers,
|
|
51
|
+
disabled,
|
|
52
|
+
onSubmit: handleSubmit,
|
|
53
|
+
} = useSignupAccountForm({
|
|
54
|
+
fields,
|
|
55
|
+
form,
|
|
56
|
+
onSubmit,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const passwordFieldName =
|
|
60
|
+
(fields.password.attr?.name as keyof AuthSignupAccountValues | undefined) ??
|
|
61
|
+
("password" as keyof AuthSignupAccountValues);
|
|
62
|
+
const confirmFieldName =
|
|
63
|
+
(fields.confirmPassword.attr?.name as
|
|
64
|
+
| keyof AuthSignupAccountValues
|
|
65
|
+
| undefined) ?? ("confirmPassword" as keyof AuthSignupAccountValues);
|
|
66
|
+
|
|
67
|
+
const resolvedPasswordRules =
|
|
68
|
+
passwordRules && passwordRules.length > 0
|
|
69
|
+
? passwordRules
|
|
70
|
+
: DEFAULT_PASSWORD_RULES;
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<FormProvider {...form}>
|
|
74
|
+
<form
|
|
75
|
+
className="auth-signup-form auth-signup-form--account"
|
|
76
|
+
{...formAttr}
|
|
77
|
+
onSubmit={handleSubmit}
|
|
78
|
+
>
|
|
79
|
+
<div className="auth-signup-fields">
|
|
80
|
+
<Input
|
|
81
|
+
{...composeSignupFieldProps<InputProps>(
|
|
82
|
+
fields.accountId,
|
|
83
|
+
helpers.accountId,
|
|
84
|
+
)}
|
|
85
|
+
register={register.accountId}
|
|
86
|
+
/>
|
|
87
|
+
<AuthPasswordSetField
|
|
88
|
+
passwordField={fields.password}
|
|
89
|
+
confirmPasswordField={fields.confirmPassword}
|
|
90
|
+
passwordFieldName={passwordFieldName}
|
|
91
|
+
confirmPasswordFieldName={confirmFieldName}
|
|
92
|
+
passwordRules={resolvedPasswordRules}
|
|
93
|
+
messages={{
|
|
94
|
+
missing: "비밀번호를 먼저 입력해 주세요.",
|
|
95
|
+
mismatch: "비밀번호 불일치",
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
<Button.Default
|
|
100
|
+
type="submit"
|
|
101
|
+
scale="solid-xlarge"
|
|
102
|
+
priority="primary"
|
|
103
|
+
block
|
|
104
|
+
disabled={disabled}
|
|
105
|
+
>
|
|
106
|
+
{submitLabel ?? DEFAULT_SUBMIT_LABEL}
|
|
107
|
+
</Button.Default>
|
|
108
|
+
</form>
|
|
109
|
+
</FormProvider>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default AuthSignupAccountForm;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { Button } from "@uniai-fe/uds-primitives";
|
|
3
|
+
import AuthCompleteTemplate from "../../common/complete/Template";
|
|
4
|
+
import type { AuthSignupCompleteProps } from "../types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 회원가입 Step4; 가입 완료/승인 대기 안내
|
|
8
|
+
* @component
|
|
9
|
+
* @param {AuthSignupCompleteProps} props complete props
|
|
10
|
+
* @param {React.ReactNode} props.title 완료 메시지
|
|
11
|
+
* @param {React.ReactNode} [props.description] 부가 설명
|
|
12
|
+
* @param {React.ReactNode} [props.illustration] 일러스트 영역
|
|
13
|
+
* @param {AuthSignupCompleteAction} props.primaryAction 주요 CTA
|
|
14
|
+
* @param {AuthSignupCompleteAction} [props.secondaryAction] 보조 CTA
|
|
15
|
+
*/
|
|
16
|
+
export function AuthSignupComplete({
|
|
17
|
+
title,
|
|
18
|
+
description,
|
|
19
|
+
illustration,
|
|
20
|
+
primaryAction,
|
|
21
|
+
secondaryAction,
|
|
22
|
+
}: AuthSignupCompleteProps) {
|
|
23
|
+
const secondaryButton = secondaryAction ? (
|
|
24
|
+
<div className="auth-signup-complete-actions">
|
|
25
|
+
<Button.Default
|
|
26
|
+
type="button"
|
|
27
|
+
scale="solid-xlarge"
|
|
28
|
+
priority="secondary"
|
|
29
|
+
block
|
|
30
|
+
disabled={secondaryAction.disabled}
|
|
31
|
+
onClick={secondaryAction.onClick}
|
|
32
|
+
>
|
|
33
|
+
{secondaryAction.label}
|
|
34
|
+
</Button.Default>
|
|
35
|
+
</div>
|
|
36
|
+
) : null;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<AuthCompleteTemplate
|
|
40
|
+
className={clsx("auth-signup-complete")}
|
|
41
|
+
title={title}
|
|
42
|
+
description={description}
|
|
43
|
+
cta={{
|
|
44
|
+
label: primaryAction.label,
|
|
45
|
+
buttonProps: {
|
|
46
|
+
onClick: primaryAction.onClick,
|
|
47
|
+
disabled: primaryAction.disabled,
|
|
48
|
+
},
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
{illustration ? (
|
|
52
|
+
<div className="auth-signup-complete-illustration">{illustration}</div>
|
|
53
|
+
) : null}
|
|
54
|
+
{secondaryButton}
|
|
55
|
+
</AuthCompleteTemplate>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default AuthSignupComplete;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { AuthContainer } from "../../common/container";
|
|
4
|
+
import type {
|
|
5
|
+
AuthSignupStepIndicatorItem,
|
|
6
|
+
AuthSignupTemplateProps,
|
|
7
|
+
} from "../types";
|
|
8
|
+
import type { AuthSignupStepId } from "../types";
|
|
9
|
+
import { AuthSignupUserInfoForm } from "./UserInfoForm";
|
|
10
|
+
import { AuthSignupVerificationForm } from "./VerificationForm";
|
|
11
|
+
import { AuthSignupAccountForm } from "./AccountForm";
|
|
12
|
+
import { AuthSignupComplete } from "./Complete";
|
|
13
|
+
import { AuthStageHeader } from "../../common/container/header";
|
|
14
|
+
|
|
15
|
+
const DEFAULT_STEPS: AuthSignupStepIndicatorItem[] = [
|
|
16
|
+
{ id: "userInfo", label: "기본 정보" },
|
|
17
|
+
{ id: "verifyAgreement", label: "약관 · 인증" },
|
|
18
|
+
{ id: "generateAccount", label: "계정 생성" },
|
|
19
|
+
{ id: "complete", label: "완료" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const STEP_INDEX: Record<AuthSignupStepId, number> = {
|
|
23
|
+
userInfo: 0,
|
|
24
|
+
verifyAgreement: 1,
|
|
25
|
+
generateAccount: 2,
|
|
26
|
+
complete: 3,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 회원가입 템플릿; Step1~4 화면을 AuthContainer 위에서 전환한다.
|
|
31
|
+
* @component
|
|
32
|
+
* @param {AuthSignupTemplateProps} props 템플릿 props
|
|
33
|
+
* @param {AuthSignupHeaderProps} props.header 헤더 구성(타이틀/뒤로가기 등)
|
|
34
|
+
* @param {AuthSignupStepId} props.step 현재 step
|
|
35
|
+
* @param {AuthSignupStepIndicatorProps} [props.stepIndicator] step indicator override
|
|
36
|
+
* @param {React.ReactNode} [props.footer] footer slot
|
|
37
|
+
*/
|
|
38
|
+
export function AuthSignupTemplate({
|
|
39
|
+
className,
|
|
40
|
+
header,
|
|
41
|
+
step,
|
|
42
|
+
stepIndicator,
|
|
43
|
+
footer,
|
|
44
|
+
userInfo,
|
|
45
|
+
verification,
|
|
46
|
+
account,
|
|
47
|
+
complete,
|
|
48
|
+
}: AuthSignupTemplateProps) {
|
|
49
|
+
const steps = stepIndicator?.items ?? DEFAULT_STEPS;
|
|
50
|
+
const currentStep = stepIndicator?.current ?? step;
|
|
51
|
+
const defaultStepIndex = Math.max(
|
|
52
|
+
0,
|
|
53
|
+
steps.findIndex(item => item.id === currentStep),
|
|
54
|
+
);
|
|
55
|
+
const fallbackTotal = steps.length || 1;
|
|
56
|
+
const providedTotal = stepIndicator?.total ?? fallbackTotal;
|
|
57
|
+
const resolvedTotal = Math.max(1, providedTotal);
|
|
58
|
+
const providedIndex = stepIndicator?.currentIndex ?? defaultStepIndex + 1;
|
|
59
|
+
const resolvedIndex = Math.min(Math.max(1, providedIndex), resolvedTotal);
|
|
60
|
+
|
|
61
|
+
const indicator =
|
|
62
|
+
resolvedTotal > 0
|
|
63
|
+
? {
|
|
64
|
+
total: resolvedTotal,
|
|
65
|
+
current: resolvedIndex,
|
|
66
|
+
}
|
|
67
|
+
: undefined;
|
|
68
|
+
|
|
69
|
+
const headerNode = (
|
|
70
|
+
<AuthStageHeader
|
|
71
|
+
className="auth-signup-header"
|
|
72
|
+
navigationTitle={header.navigationTitle}
|
|
73
|
+
headline={header.headline}
|
|
74
|
+
description={header.description}
|
|
75
|
+
backIcon={header.backIcon}
|
|
76
|
+
onBack={header.onBack}
|
|
77
|
+
indicator={indicator}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
let content: ReactNode = null;
|
|
82
|
+
switch (currentStep) {
|
|
83
|
+
case "userInfo":
|
|
84
|
+
content = <AuthSignupUserInfoForm {...userInfo} />;
|
|
85
|
+
break;
|
|
86
|
+
case "verifyAgreement":
|
|
87
|
+
content = <AuthSignupVerificationForm {...verification} />;
|
|
88
|
+
break;
|
|
89
|
+
case "generateAccount":
|
|
90
|
+
content = <AuthSignupAccountForm {...account} />;
|
|
91
|
+
break;
|
|
92
|
+
case "complete":
|
|
93
|
+
content = <AuthSignupComplete {...complete} />;
|
|
94
|
+
break;
|
|
95
|
+
default:
|
|
96
|
+
content = null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<AuthContainer
|
|
101
|
+
className={clsx("auth-signup-template", className)}
|
|
102
|
+
header={headerNode}
|
|
103
|
+
footer={footer}
|
|
104
|
+
>
|
|
105
|
+
{content}
|
|
106
|
+
</AuthContainer>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default AuthSignupTemplate;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useForm } from "react-hook-form";
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
Input,
|
|
6
|
+
PhoneInput,
|
|
7
|
+
type InputProps,
|
|
8
|
+
type PhoneInputProps,
|
|
9
|
+
} from "@uniai-fe/uds-primitives";
|
|
10
|
+
import type {
|
|
11
|
+
AuthSignupUserInfoProps,
|
|
12
|
+
AuthSignupUserInfoValues,
|
|
13
|
+
} from "../types";
|
|
14
|
+
import { useSignupUserInfoForm } from "../hooks/useSignupUserInfoForm";
|
|
15
|
+
import { composeSignupFieldProps } from "../utils/composeFieldProps";
|
|
16
|
+
import { getSignupFieldDefaultValue } from "../utils/getSignupFieldDefaultValue";
|
|
17
|
+
|
|
18
|
+
const DEFAULT_SUBMIT_LABEL = "다음";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 회원가입 Step1; 사용자 기본 정보(이름/휴대폰) 입력 폼
|
|
22
|
+
* @component
|
|
23
|
+
* @param {AuthSignupUserInfoProps} props form props
|
|
24
|
+
* @param {AuthSignupUserInfoFields} props.fields 필드 설정
|
|
25
|
+
* @param {React.FormHTMLAttributes<HTMLFormElement>} [props.formAttr] form attr
|
|
26
|
+
* @param {import("react").ReactNode} [props.submitLabel] CTA 라벨
|
|
27
|
+
* @param {SubmitHandler<AuthSignupUserInfoValues>} props.onSubmit 제출 핸들러
|
|
28
|
+
*/
|
|
29
|
+
export function AuthSignupUserInfoForm({
|
|
30
|
+
fields,
|
|
31
|
+
visibleFields,
|
|
32
|
+
formAttr,
|
|
33
|
+
submitLabel,
|
|
34
|
+
onSubmit,
|
|
35
|
+
}: AuthSignupUserInfoProps) {
|
|
36
|
+
// 필드 name attr을 기준으로 RHF defaultValues를 생성한다.
|
|
37
|
+
const defaultValues = useMemo(
|
|
38
|
+
() =>
|
|
39
|
+
(Object.keys(fields) as Array<keyof typeof fields>).reduce(
|
|
40
|
+
(acc, fieldKey) => {
|
|
41
|
+
const config = fields[fieldKey];
|
|
42
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
43
|
+
acc[fieldName] = getSignupFieldDefaultValue(config);
|
|
44
|
+
return acc;
|
|
45
|
+
},
|
|
46
|
+
{} as AuthSignupUserInfoValues,
|
|
47
|
+
),
|
|
48
|
+
[fields],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const form = useForm<AuthSignupUserInfoValues>({
|
|
52
|
+
mode: "onChange",
|
|
53
|
+
defaultValues,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const renderedFields =
|
|
57
|
+
visibleFields ?? (Object.keys(fields) as Array<keyof typeof fields>);
|
|
58
|
+
|
|
59
|
+
const {
|
|
60
|
+
register,
|
|
61
|
+
helpers,
|
|
62
|
+
disabled,
|
|
63
|
+
onSubmit: handleSubmit,
|
|
64
|
+
} = useSignupUserInfoForm({
|
|
65
|
+
fields,
|
|
66
|
+
form,
|
|
67
|
+
onSubmit,
|
|
68
|
+
activeFields: renderedFields,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<form
|
|
73
|
+
className="auth-signup-form auth-signup-form--user-info"
|
|
74
|
+
{...formAttr}
|
|
75
|
+
onSubmit={handleSubmit}
|
|
76
|
+
>
|
|
77
|
+
<div className="auth-signup-fields">
|
|
78
|
+
{renderedFields.includes("name") ? (
|
|
79
|
+
<Input
|
|
80
|
+
{...composeSignupFieldProps<InputProps>(fields.name, helpers.name)}
|
|
81
|
+
register={register.name}
|
|
82
|
+
/>
|
|
83
|
+
) : null}
|
|
84
|
+
{renderedFields.includes("phone") ? (
|
|
85
|
+
<PhoneInput
|
|
86
|
+
{...composeSignupFieldProps<PhoneInputProps>(
|
|
87
|
+
fields.phone,
|
|
88
|
+
helpers.phone,
|
|
89
|
+
)}
|
|
90
|
+
register={register.phone}
|
|
91
|
+
/>
|
|
92
|
+
) : null}
|
|
93
|
+
</div>
|
|
94
|
+
<Button.Default
|
|
95
|
+
type="submit"
|
|
96
|
+
scale="solid-xlarge"
|
|
97
|
+
priority="primary"
|
|
98
|
+
block
|
|
99
|
+
disabled={disabled}
|
|
100
|
+
>
|
|
101
|
+
{submitLabel ?? DEFAULT_SUBMIT_LABEL}
|
|
102
|
+
</Button.Default>
|
|
103
|
+
</form>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default AuthSignupUserInfoForm;
|