@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.
Files changed (119) hide show
  1. package/README.md +88 -1
  2. package/dist/styles.css +2051 -2266
  3. package/package.json +5 -3
  4. package/src/auth/common/complete/Template.tsx +47 -0
  5. package/src/auth/common/complete/img/circle-check-complete.svg +4 -0
  6. package/src/auth/common/complete/index.scss +38 -0
  7. package/src/auth/common/complete/types.ts +15 -0
  8. package/src/auth/common/container/header/StageHeader.tsx +61 -0
  9. package/src/auth/common/container/header/index.tsx +5 -0
  10. package/src/auth/common/container/header/stage-header.scss +50 -0
  11. package/src/{components/auth → auth/common}/container/index.tsx +2 -0
  12. package/src/auth/common/find/hooks/useFindAccountForm.ts +79 -0
  13. package/src/auth/common/find/markup/CodeStep.tsx +166 -0
  14. package/src/auth/common/find/markup/Header.tsx +46 -0
  15. package/src/auth/common/find/markup/InfoStep.tsx +109 -0
  16. package/src/auth/common/find/styles/email.scss +55 -0
  17. package/src/auth/common/find/styles/find-account.scss +4 -0
  18. package/src/auth/common/find/styles/layout.scss +19 -0
  19. package/src/auth/common/find/styles/password.scss +39 -0
  20. package/src/auth/common/find/styles/result.scss +78 -0
  21. package/src/auth/common/find/types/forms.ts +30 -0
  22. package/src/auth/common/find/types/index.ts +121 -0
  23. package/src/auth/common/find/utils/composeFieldProps.ts +45 -0
  24. package/src/auth/common/password/constants.ts +19 -0
  25. package/src/auth/common/password/hooks/useCheckPassword.ts +133 -0
  26. package/src/auth/common/password/img/check-password.svg +3 -0
  27. package/src/auth/common/password/markup/PasswordSetField.tsx +250 -0
  28. package/src/auth/common/password/styles/password-set-field.scss +49 -0
  29. package/src/auth/common/password/types.ts +142 -0
  30. package/src/auth/common/password/utils/composePasswordFieldProps.ts +44 -0
  31. package/src/auth/find-account.ts +28 -0
  32. package/src/auth/find-id/hooks/index.ts +1 -0
  33. package/src/auth/find-id/index.scss +1 -0
  34. package/src/auth/find-id/index.ts +23 -0
  35. package/src/auth/find-id/markup/StepComplete.tsx +58 -0
  36. package/src/auth/find-id/markup/StepIdentify.tsx +46 -0
  37. package/src/auth/find-id/markup/StepVerifyCode.tsx +48 -0
  38. package/src/auth/find-id/types/index.ts +66 -0
  39. package/src/auth/find-password/index.scss +1 -0
  40. package/src/auth/find-password/index.ts +30 -0
  41. package/src/auth/find-password/markup/StepComplete.tsx +30 -0
  42. package/src/auth/find-password/markup/StepIdentify.tsx +45 -0
  43. package/src/auth/find-password/markup/StepResetPassword.tsx +150 -0
  44. package/src/auth/find-password/markup/StepVerifyCode.tsx +48 -0
  45. package/src/auth/index.tsx +41 -0
  46. package/src/{components/auth → auth}/login/index.tsx +1 -7
  47. package/src/{components/auth → auth}/login/markup/Container.tsx +1 -1
  48. package/src/{components/auth → auth}/login/markup/FormField.tsx +2 -2
  49. package/src/{components/auth → auth}/login/types/props.ts +13 -13
  50. package/src/auth/login/types.ts +2 -0
  51. package/src/auth/signup/hooks/index.ts +3 -0
  52. package/src/auth/signup/hooks/useSignupAccountForm.ts +101 -0
  53. package/src/auth/signup/hooks/useSignupUserInfoForm.ts +88 -0
  54. package/src/auth/signup/hooks/useSignupVerificationForm.ts +77 -0
  55. package/src/auth/signup/img/check-agree.svg +3 -0
  56. package/src/auth/signup/img/chevron-open-detail.svg +3 -0
  57. package/src/auth/signup/index.ts +27 -0
  58. package/src/auth/signup/markup/AccountForm.tsx +113 -0
  59. package/src/auth/signup/markup/Complete.tsx +59 -0
  60. package/src/auth/signup/markup/Template.tsx +110 -0
  61. package/src/auth/signup/markup/UserInfoForm.tsx +107 -0
  62. package/src/auth/signup/markup/VerificationForm.tsx +285 -0
  63. package/src/auth/signup/markup/index.ts +5 -0
  64. package/src/auth/signup/styles/signup.scss +187 -0
  65. package/src/auth/signup/types/hooks.ts +86 -0
  66. package/src/auth/signup/types/index.ts +2 -0
  67. package/src/auth/signup/types/props.ts +145 -0
  68. package/src/auth/signup/utils/composeFieldProps.ts +50 -0
  69. package/src/auth/signup/utils/getSignupFieldDefaultValue.ts +40 -0
  70. package/src/index.scss +5 -3
  71. package/src/index.tsx +3 -2
  72. package/src/modal/core/components/Container.tsx +41 -0
  73. package/src/modal/core/components/FooterButtons.tsx +132 -0
  74. package/src/modal/core/components/Provider.tsx +28 -0
  75. package/src/modal/core/components/Root.tsx +93 -0
  76. package/src/modal/core/hooks/useModal.ts +136 -0
  77. package/src/modal/core/jotai/atoms.ts +10 -0
  78. package/src/modal/index.scss +4 -0
  79. package/src/modal/index.tsx +16 -0
  80. package/src/modal/styles/animations.scss +24 -0
  81. package/src/modal/styles/base.scss +45 -0
  82. package/src/modal/styles/container.scss +138 -0
  83. package/src/modal/styles/dimmer.scss +23 -0
  84. package/src/modal/templates/Alert.tsx +104 -0
  85. package/src/modal/templates/Dialog.tsx +112 -0
  86. package/src/modal/types/footer.ts +36 -0
  87. package/src/modal/types/index.ts +21 -0
  88. package/src/modal/types/options.ts +6 -0
  89. package/src/modal/types/state.ts +31 -0
  90. package/src/modal/types/templates.ts +32 -0
  91. package/src/page-frame/mobile/header/PageFrameMobileHeader.tsx +52 -0
  92. package/src/page-frame/mobile/header/index.ts +4 -0
  93. package/src/page-frame/mobile/header/page-frame-mobile-header.scss +48 -0
  94. package/src/page-frame/mobile/img/chevron-backward.svg +3 -0
  95. package/src/components/auth/index.tsx +0 -20
  96. package/src/components/auth/login/types.ts +0 -2
  97. /package/src/{components/auth → auth/common}/container/AuthContainer.tsx +0 -0
  98. /package/src/{components/auth → auth/common}/container/index.scss +0 -0
  99. /package/src/{components/auth → auth/common}/container/types.ts +0 -0
  100. /package/src/{components/auth → auth}/login/data/valid-options.ts +0 -0
  101. /package/src/{components/auth → auth}/login/hooks/index.ts +0 -0
  102. /package/src/{components/auth → auth}/login/hooks/useAuthLoginForm.ts +0 -0
  103. /package/src/{components/auth → auth}/login/index.scss +0 -0
  104. /package/src/{components/auth → auth}/login/markup/LinkButtons.tsx +0 -0
  105. /package/src/{components/auth → auth}/login/styles/login.scss +0 -0
  106. /package/src/{components/auth → auth}/login/types/form.ts +0 -0
  107. /package/src/{components/auth → auth}/login/types/hooks.ts +0 -0
  108. /package/src/{components/page-frame → page-frame}/container/PageFrameContainer.tsx +0 -0
  109. /package/src/{components/page-frame → page-frame}/container/index.scss +0 -0
  110. /package/src/{components/page-frame → page-frame}/container/index.tsx +0 -0
  111. /package/src/{components/page-frame → page-frame}/container/types.ts +0 -0
  112. /package/src/{components/page-frame → page-frame}/index.tsx +0 -0
  113. /package/src/{components/page-frame → page-frame}/mobile/PageFrameMobile.tsx +0 -0
  114. /package/src/{components/page-frame → page-frame}/mobile/index.scss +0 -0
  115. /package/src/{components/page-frame → page-frame}/mobile/index.tsx +0 -0
  116. /package/src/{components/page-frame → page-frame}/mobile/types.ts +0 -0
  117. /package/src/{components/page-frame → page-frame}/navigation/PageFrameNavigation.tsx +0 -0
  118. /package/src/{components/page-frame → page-frame}/navigation/index.scss +0 -0
  119. /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="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M5 9.36395L8.53553 12.8995L14.8995 6.53552" stroke="#94989E" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3
+ </svg>
@@ -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;