@uniai-fe/uds-templates 0.0.11 → 0.0.13

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 (122) hide show
  1. package/README.md +11 -0
  2. package/dist/styles.css +916 -1074
  3. package/package.json +3 -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/markup/Container.tsx +1 -1
  47. package/src/{components/auth → auth}/login/types/props.ts +1 -1
  48. package/src/{components/auth → auth}/signup/hooks/useSignupAccountForm.ts +26 -2
  49. package/src/{components/auth → auth}/signup/hooks/useSignupUserInfoForm.ts +10 -3
  50. package/src/auth/signup/img/check-agree.svg +3 -0
  51. package/src/auth/signup/img/chevron-open-detail.svg +3 -0
  52. package/src/{components/auth → auth}/signup/index.ts +3 -0
  53. package/src/auth/signup/markup/AccountForm.tsx +113 -0
  54. package/src/auth/signup/markup/Complete.tsx +59 -0
  55. package/src/auth/signup/markup/Template.tsx +110 -0
  56. package/src/{components/auth → auth}/signup/markup/UserInfoForm.tsx +23 -13
  57. package/src/auth/signup/markup/VerificationForm.tsx +285 -0
  58. package/src/{components/auth → auth}/signup/markup/index.ts +1 -0
  59. package/src/auth/signup/styles/signup.scss +187 -0
  60. package/src/{components/auth → auth}/signup/types/hooks.ts +1 -0
  61. package/src/{components/auth → auth}/signup/types/props.ts +49 -9
  62. package/src/auth/signup/utils/getSignupFieldDefaultValue.ts +40 -0
  63. package/src/index.scss +5 -4
  64. package/src/index.tsx +3 -3
  65. package/src/page-frame/mobile/header/PageFrameMobileHeader.tsx +52 -0
  66. package/src/page-frame/mobile/header/index.ts +4 -0
  67. package/src/page-frame/mobile/header/page-frame-mobile-header.scss +48 -0
  68. package/src/page-frame/mobile/img/chevron-backward.svg +3 -0
  69. package/src/components/auth/index.tsx +0 -20
  70. package/src/components/auth/signup/markup/AccountForm.tsx +0 -124
  71. package/src/components/auth/signup/markup/Complete.tsx +0 -61
  72. package/src/components/auth/signup/markup/VerificationForm.tsx +0 -155
  73. package/src/components/auth/signup/styles/signup.scss +0 -135
  74. /package/src/{components/auth → auth/common}/container/AuthContainer.tsx +0 -0
  75. /package/src/{components/auth → auth/common}/container/index.scss +0 -0
  76. /package/src/{components/auth → auth/common}/container/types.ts +0 -0
  77. /package/src/{components/auth → auth}/login/data/valid-options.ts +0 -0
  78. /package/src/{components/auth → auth}/login/hooks/index.ts +0 -0
  79. /package/src/{components/auth → auth}/login/hooks/useAuthLoginForm.ts +0 -0
  80. /package/src/{components/auth → auth}/login/index.scss +0 -0
  81. /package/src/{components/auth → auth}/login/index.tsx +0 -0
  82. /package/src/{components/auth → auth}/login/markup/FormField.tsx +0 -0
  83. /package/src/{components/auth → auth}/login/markup/LinkButtons.tsx +0 -0
  84. /package/src/{components/auth → auth}/login/styles/login.scss +0 -0
  85. /package/src/{components/auth → auth}/login/types/form.ts +0 -0
  86. /package/src/{components/auth → auth}/login/types/hooks.ts +0 -0
  87. /package/src/{components/auth → auth}/login/types.ts +0 -0
  88. /package/src/{components/auth → auth}/signup/hooks/index.ts +0 -0
  89. /package/src/{components/auth → auth}/signup/hooks/useSignupVerificationForm.ts +0 -0
  90. /package/src/{components/auth → auth}/signup/types/index.ts +0 -0
  91. /package/src/{components/auth → auth}/signup/utils/composeFieldProps.ts +0 -0
  92. /package/src/{components/modal → modal}/core/components/Container.tsx +0 -0
  93. /package/src/{components/modal → modal}/core/components/FooterButtons.tsx +0 -0
  94. /package/src/{components/modal → modal}/core/components/Provider.tsx +0 -0
  95. /package/src/{components/modal → modal}/core/components/Root.tsx +0 -0
  96. /package/src/{components/modal → modal}/core/hooks/useModal.ts +0 -0
  97. /package/src/{components/modal → modal}/core/jotai/atoms.ts +0 -0
  98. /package/src/{components/modal → modal}/index.scss +0 -0
  99. /package/src/{components/modal → modal}/index.tsx +0 -0
  100. /package/src/{components/modal → modal}/styles/animations.scss +0 -0
  101. /package/src/{components/modal → modal}/styles/base.scss +0 -0
  102. /package/src/{components/modal → modal}/styles/container.scss +0 -0
  103. /package/src/{components/modal → modal}/styles/dimmer.scss +0 -0
  104. /package/src/{components/modal → modal}/templates/Alert.tsx +0 -0
  105. /package/src/{components/modal → modal}/templates/Dialog.tsx +0 -0
  106. /package/src/{components/modal → modal}/types/footer.ts +0 -0
  107. /package/src/{components/modal → modal}/types/index.ts +0 -0
  108. /package/src/{components/modal → modal}/types/options.ts +0 -0
  109. /package/src/{components/modal → modal}/types/state.ts +0 -0
  110. /package/src/{components/modal → modal}/types/templates.ts +0 -0
  111. /package/src/{components/page-frame → page-frame}/container/PageFrameContainer.tsx +0 -0
  112. /package/src/{components/page-frame → page-frame}/container/index.scss +0 -0
  113. /package/src/{components/page-frame → page-frame}/container/index.tsx +0 -0
  114. /package/src/{components/page-frame → page-frame}/container/types.ts +0 -0
  115. /package/src/{components/page-frame → page-frame}/index.tsx +0 -0
  116. /package/src/{components/page-frame → page-frame}/mobile/PageFrameMobile.tsx +0 -0
  117. /package/src/{components/page-frame → page-frame}/mobile/index.scss +0 -0
  118. /package/src/{components/page-frame → page-frame}/mobile/index.tsx +0 -0
  119. /package/src/{components/page-frame → page-frame}/mobile/types.ts +0 -0
  120. /package/src/{components/page-frame → page-frame}/navigation/PageFrameNavigation.tsx +0 -0
  121. /package/src/{components/page-frame → page-frame}/navigation/index.scss +0 -0
  122. /package/src/{components/page-frame → page-frame}/navigation/index.tsx +0 -0
@@ -0,0 +1,250 @@
1
+ import "../styles/password-set-field.scss";
2
+
3
+ import { useMemo } from "react";
4
+ import type { ReactNode } from "react";
5
+ import {
6
+ useFormContext,
7
+ type FieldPath,
8
+ type FieldValues,
9
+ } from "react-hook-form";
10
+ import {
11
+ PasswordInput,
12
+ type InputPasswordProps,
13
+ } from "@uniai-fe/uds-primitives";
14
+ import CheckPasswordIcon from "../img/check-password.svg";
15
+ import type {
16
+ AuthPasswordHelperItem,
17
+ AuthPasswordHelperState,
18
+ AuthPasswordRule,
19
+ AuthPasswordSetFieldProps,
20
+ } from "../types";
21
+ import { useCheckPassword } from "../hooks/useCheckPassword";
22
+ import { composePasswordFieldProps } from "../utils/composePasswordFieldProps";
23
+
24
+ const PASSWORD_MATCHED_HELPER_ID = "confirm-complete";
25
+ const PASSWORD_MISMATCH_HELPER_ID = "confirm-error";
26
+ const PASSWORD_MATCHED_TEXT = "비밀번호 일치";
27
+ const RHF_CONTEXT_ERROR =
28
+ "AuthPasswordSetField requires react-hook-form context. Wrap it with FormProvider or pass a form prop.";
29
+
30
+ /**
31
+ * RHF 컨텍스트 optional getter
32
+ * @hook
33
+ */
34
+ function useOptionalFormContext<TValues extends FieldValues>() {
35
+ try {
36
+ return useFormContext<TValues>();
37
+ } catch {
38
+ return undefined;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Helper 아이템 리스트 렌더러
44
+ * @param {{ items: AuthPasswordHelperItem[] }} props helper 컬렉션
45
+ */
46
+ const HelperList = ({ items }: { items: AuthPasswordHelperItem[] }) =>
47
+ items.length ? (
48
+ <ul className="auth-password-set-helpers" aria-live="polite">
49
+ {items.map(item => (
50
+ <li
51
+ key={item.id}
52
+ className="auth-password-set-helper"
53
+ data-state={item.state}
54
+ >
55
+ <figure className="auth-password-set-helper-icon" aria-hidden="true">
56
+ <CheckPasswordIcon />
57
+ </figure>
58
+ <span>{item.text}</span>
59
+ </li>
60
+ ))}
61
+ </ul>
62
+ ) : null;
63
+
64
+ /**
65
+ * Password Input Set; 비밀번호 + 재확인 필드 묶음
66
+ * @component
67
+ * @param {AuthPasswordSetFieldProps} props PasswordSetField 구성 요소
68
+ * @param {UseFormReturn} props.form react-hook-form 객체
69
+ * @param {AuthPasswordFieldProps<InputPasswordProps>} props.passwordField 비밀번호 입력 필드
70
+ * @param {AuthPasswordFieldProps<InputPasswordProps>} props.confirmPasswordField 비밀번호 재확인 필드
71
+ * @param {AuthPasswordRule[]} [props.passwordRules] 비밀번호 규칙 또는 predicate 목록
72
+ * @param {AuthPasswordSetMessages} [props.messages] 검증 메시지
73
+ */
74
+ export function AuthPasswordSetField<
75
+ TValues extends FieldValues = Record<string, string>,
76
+ >({
77
+ form,
78
+ passwordField,
79
+ confirmPasswordField,
80
+ passwordRules,
81
+ passwordFieldName,
82
+ confirmPasswordFieldName,
83
+ messages,
84
+ }: AuthPasswordSetFieldProps<TValues>) {
85
+ const contextForm = useOptionalFormContext<TValues>();
86
+ const resolvedForm = form ?? contextForm;
87
+
88
+ if (!resolvedForm) {
89
+ throw new Error(RHF_CONTEXT_ERROR);
90
+ }
91
+ const resolvedPasswordFieldName: FieldPath<TValues> =
92
+ passwordFieldName ??
93
+ (passwordField.attr?.name as FieldPath<TValues> | undefined) ??
94
+ ("password" as FieldPath<TValues>);
95
+ const resolvedConfirmFieldName: FieldPath<TValues> =
96
+ confirmPasswordFieldName ??
97
+ (confirmPasswordField.attr?.name as FieldPath<TValues> | undefined) ??
98
+ ("confirmPassword" as FieldPath<TValues>);
99
+
100
+ const {
101
+ passwordValue = "",
102
+ confirmPasswordValue = "",
103
+ validator: confirmValidator,
104
+ helper: confirmState,
105
+ isMatched,
106
+ } = useCheckPassword<TValues>({
107
+ form: resolvedForm,
108
+ passwordField: resolvedPasswordFieldName,
109
+ confirmField: resolvedConfirmFieldName,
110
+ helperText: confirmPasswordField.helper,
111
+ messages,
112
+ });
113
+
114
+ const passwordHelperBase: AuthPasswordHelperState = useMemo(
115
+ () => ({
116
+ text:
117
+ (resolvedForm.getFieldState(resolvedPasswordFieldName).error
118
+ ?.message as ReactNode | undefined) ?? passwordField.helper,
119
+ state: resolvedForm.getFieldState(resolvedPasswordFieldName).invalid
120
+ ? "error"
121
+ : undefined,
122
+ }),
123
+ [passwordField.helper, resolvedForm, resolvedPasswordFieldName],
124
+ );
125
+
126
+ const evaluatedRules = useMemo(() => {
127
+ if (!passwordRules || passwordRules.length === 0) {
128
+ return undefined;
129
+ }
130
+ return passwordRules.map((rule: AuthPasswordRule) => {
131
+ const nextFulfilled =
132
+ typeof rule.fulfilled === "boolean"
133
+ ? rule.fulfilled
134
+ : typeof rule.predicate === "function"
135
+ ? rule.predicate(passwordValue ?? "")
136
+ : false;
137
+ return {
138
+ ...rule,
139
+ fulfilled: nextFulfilled,
140
+ };
141
+ });
142
+ }, [passwordRules, passwordValue]);
143
+
144
+ const hasPasswordValue =
145
+ typeof passwordValue === "string" && passwordValue.trim().length > 0;
146
+ const showRules =
147
+ Boolean(evaluatedRules?.length) &&
148
+ hasPasswordValue &&
149
+ passwordHelperBase.state !== "error";
150
+
151
+ const allRulesFulfilled =
152
+ evaluatedRules && evaluatedRules.length > 0
153
+ ? evaluatedRules.every(rule => rule.fulfilled)
154
+ : true;
155
+ const canConfirmPassword =
156
+ hasPasswordValue &&
157
+ passwordHelperBase.state !== "error" &&
158
+ allRulesFulfilled;
159
+
160
+ const passwordRulesItems: AuthPasswordHelperItem[] =
161
+ showRules && evaluatedRules
162
+ ? evaluatedRules.map((rule: AuthPasswordRule) => ({
163
+ id: rule.id,
164
+ text: rule.label,
165
+ state: rule.fulfilled ? "complete" : undefined,
166
+ }))
167
+ : [];
168
+
169
+ const confirmHelperItems: AuthPasswordHelperItem[] = [];
170
+ const blockedByPassword =
171
+ !canConfirmPassword && Boolean(confirmPasswordValue?.length);
172
+
173
+ if (blockedByPassword) {
174
+ confirmHelperItems.push({
175
+ id: `${resolvedConfirmFieldName}-blocked`,
176
+ text: messages?.missing ?? "비밀번호를 먼저 입력해 주세요.",
177
+ state: "error",
178
+ });
179
+ } else if (confirmState.state === "error" && confirmState.text) {
180
+ confirmHelperItems.push({
181
+ id: PASSWORD_MISMATCH_HELPER_ID,
182
+ text: confirmState.text,
183
+ state: "error",
184
+ });
185
+ } else if (
186
+ isMatched &&
187
+ passwordHelperBase.state !== "error" &&
188
+ allRulesFulfilled
189
+ ) {
190
+ confirmHelperItems.push({
191
+ id: PASSWORD_MATCHED_HELPER_ID,
192
+ text: PASSWORD_MATCHED_TEXT,
193
+ state: "complete",
194
+ });
195
+ }
196
+
197
+ const passwordHelperNode: AuthPasswordHelperState = passwordRulesItems.length
198
+ ? {
199
+ ...passwordHelperBase,
200
+ text: <HelperList items={passwordRulesItems} />,
201
+ state: undefined,
202
+ }
203
+ : passwordHelperBase;
204
+
205
+ const confirmHelperProps: AuthPasswordHelperState = confirmHelperItems.length
206
+ ? {
207
+ ...confirmState,
208
+ text: <HelperList items={confirmHelperItems} />,
209
+ state: confirmState.state === "error" ? "error" : undefined,
210
+ }
211
+ : { ...confirmState, text: undefined, state: undefined };
212
+
213
+ const passwordRegister = resolvedForm.register(resolvedPasswordFieldName);
214
+ const confirmPasswordRegister = resolvedForm.register(
215
+ resolvedConfirmFieldName,
216
+ {
217
+ validate: confirmValidator,
218
+ },
219
+ );
220
+
221
+ return (
222
+ <div className="auth-password-set">
223
+ <PasswordInput
224
+ {...composePasswordFieldProps<InputPasswordProps>(
225
+ passwordField,
226
+ passwordHelperNode,
227
+ )}
228
+ register={passwordRegister}
229
+ success={null}
230
+ error={null}
231
+ />
232
+ <PasswordInput
233
+ {...composePasswordFieldProps<InputPasswordProps>(
234
+ confirmPasswordField,
235
+ confirmHelperProps,
236
+ )}
237
+ register={confirmPasswordRegister}
238
+ success={null}
239
+ error={null}
240
+ disabled={
241
+ confirmPasswordField.props?.disabled ??
242
+ confirmPasswordField.attr?.disabled ??
243
+ !canConfirmPassword
244
+ }
245
+ />
246
+ </div>
247
+ );
248
+ }
249
+
250
+ export default AuthPasswordSetField;
@@ -0,0 +1,49 @@
1
+ @use "@uniai-fe/uds-foundation/css";
2
+
3
+ .auth-password-set {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--spacing-gap-6, 12px);
7
+ }
8
+
9
+ .auth-password-set-helpers {
10
+ list-style: none;
11
+ padding: 0;
12
+ margin: var(--spacing-gap-3, 6px) 0 0;
13
+ display: flex;
14
+ gap: var(--spacing-gap-3, 6px);
15
+ flex-wrap: wrap;
16
+ }
17
+
18
+ .auth-password-set-helper {
19
+ display: inline-flex;
20
+ align-items: center;
21
+ gap: 4px;
22
+ font-size: var(--font-label-small-size);
23
+ line-height: var(--font-label-small-line-height);
24
+ color: var(--color-label-assistive);
25
+
26
+ &[data-state="error"] {
27
+ color: var(--color-error);
28
+
29
+ svg path {
30
+ fill: var(--color-error);
31
+ }
32
+ }
33
+
34
+ &[data-state="complete"] {
35
+ color: var(--color-success);
36
+
37
+ svg path {
38
+ fill: var(--color-success);
39
+ }
40
+ }
41
+ }
42
+
43
+ .auth-password-set-helper-icon {
44
+ width: 16px;
45
+ height: 16px;
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ }
@@ -0,0 +1,142 @@
1
+ import type { ReactNode } from "react";
2
+ import type {
3
+ InputFieldProps,
4
+ InputPasswordProps,
5
+ InputProps,
6
+ } from "@uniai-fe/uds-primitives";
7
+ import type { FieldPath, FieldValues, UseFormReturn } from "react-hook-form";
8
+
9
+ /**
10
+ * Password 필드 공통 prop; primitives InputFieldProps를 그대로 확장한다.
11
+ * @typedef
12
+ */
13
+ export type AuthPasswordFieldProps<TProps extends InputProps = InputProps> =
14
+ InputFieldProps<TProps>;
15
+
16
+ /**
17
+ * Password 규칙 메타데이터
18
+ * @typedef
19
+ */
20
+ export interface AuthPasswordRule {
21
+ /**
22
+ * 규칙 식별자
23
+ * @type {string}
24
+ */
25
+ id: string;
26
+ /**
27
+ * 규칙 라벨 또는 설명
28
+ * @type {ReactNode}
29
+ */
30
+ label: ReactNode;
31
+ /**
32
+ * 충족 여부(직접 전달)
33
+ * @type {boolean | undefined}
34
+ */
35
+ fulfilled?: boolean;
36
+ /**
37
+ * 규칙을 계산하는 predicate
38
+ * @type {(value: string) => boolean | undefined}
39
+ */
40
+ predicate?: (value: string) => boolean;
41
+ }
42
+
43
+ /**
44
+ * Password helper 상태
45
+ * @typedef
46
+ */
47
+ export interface AuthPasswordHelperState {
48
+ /**
49
+ * helper 텍스트
50
+ * @type {ReactNode | undefined}
51
+ */
52
+ text?: ReactNode;
53
+ /**
54
+ * helper state (complete/error 등)
55
+ * @type {AuthPasswordHelperStatus | undefined}
56
+ */
57
+ state?: AuthPasswordHelperStatus;
58
+ }
59
+
60
+ /**
61
+ * Helper 출력 아이템; list 렌더링에 사용
62
+ * @typedef
63
+ */
64
+ export interface AuthPasswordHelperItem {
65
+ /**
66
+ * helper 아이템 식별자
67
+ * @type {string}
68
+ */
69
+ id: string;
70
+ /**
71
+ * helper에 노출할 텍스트/노드
72
+ * @type {ReactNode}
73
+ */
74
+ text: ReactNode;
75
+ /**
76
+ * helper state (success/error 등)
77
+ * @type {AuthPasswordHelperStatus | undefined}
78
+ */
79
+ state?: AuthPasswordHelperStatus;
80
+ }
81
+
82
+ /**
83
+ * Password helper에서 사용하는 상태
84
+ */
85
+ export type AuthPasswordHelperStatus = "error" | "complete";
86
+
87
+ /**
88
+ * PasswordSetField 컴포넌트 props
89
+ * @typedef
90
+ */
91
+ export interface AuthPasswordSetMessages {
92
+ /**
93
+ * 비밀번호 미입력 시 메시지
94
+ * @type {string | undefined}
95
+ */
96
+ missing?: string;
97
+ /**
98
+ * 비밀번호 불일치 메시지
99
+ * @type {string | undefined}
100
+ */
101
+ mismatch?: string;
102
+ }
103
+
104
+ export interface AuthPasswordSetFieldProps<
105
+ TValues extends FieldValues = Record<string, string>,
106
+ > {
107
+ /**
108
+ * react-hook-form form 객체
109
+ * @type {UseFormReturn<TValues> | undefined}
110
+ */
111
+ form?: UseFormReturn<TValues>;
112
+ /**
113
+ * 비밀번호 입력 필드 설정
114
+ * @type {AuthPasswordFieldProps<InputPasswordProps>}
115
+ */
116
+ passwordField: AuthPasswordFieldProps<InputPasswordProps>;
117
+ /**
118
+ * 비밀번호 재확인 필드 설정
119
+ * @type {AuthPasswordFieldProps<InputPasswordProps>}
120
+ */
121
+ confirmPasswordField: AuthPasswordFieldProps<InputPasswordProps>;
122
+ /**
123
+ * 비밀번호 필드명(미지정 시 attr.name 또는 password)
124
+ * @type {FieldPath<TValues> | undefined}
125
+ */
126
+ passwordFieldName?: FieldPath<TValues>;
127
+ /**
128
+ * 재확인 필드명(미지정 시 attr.name 또는 confirmPassword)
129
+ * @type {FieldPath<TValues> | undefined}
130
+ */
131
+ confirmPasswordFieldName?: FieldPath<TValues>;
132
+ /**
133
+ * 비밀번호 규칙 목록
134
+ * @type {AuthPasswordRule[] | undefined}
135
+ */
136
+ passwordRules?: AuthPasswordRule[];
137
+ /**
138
+ * 검증 메시지 override
139
+ * @type {AuthPasswordSetMessages | undefined}
140
+ */
141
+ messages?: AuthPasswordSetMessages;
142
+ }
@@ -0,0 +1,44 @@
1
+ import type { InputProps } from "@uniai-fe/uds-primitives";
2
+ import type { AuthPasswordFieldProps, AuthPasswordHelperState } from "../types";
3
+
4
+ /**
5
+ * Password 전용 필드 구성; attr/props를 합쳐 helper/label/state를 통일한다.
6
+ * @util
7
+ * @param {AuthPasswordFieldProps<TProps>} config 입력 필드 설정값
8
+ * @param {AuthPasswordHelperState} [helper] 훅에서 계산한 helper 상태
9
+ * @returns {TProps} Input 컴포넌트에 그대로 전달할 props
10
+ */
11
+ export const composePasswordFieldProps = <TProps extends InputProps>(
12
+ config: AuthPasswordFieldProps<TProps>,
13
+ helper?: AuthPasswordHelperState,
14
+ ): TProps => {
15
+ const baseProps = {
16
+ ...(config.attr ?? {}),
17
+ ...(config.props ?? ({} as TProps)),
18
+ } as TProps;
19
+ const mergedLabelProps = config.labelClassName
20
+ ? {
21
+ ...baseProps.labelProps,
22
+ className: config.labelClassName,
23
+ }
24
+ : baseProps.labelProps;
25
+ const mergedHelperProps = config.helperClassName
26
+ ? {
27
+ ...baseProps.helperProps,
28
+ className: config.helperClassName,
29
+ }
30
+ : baseProps.helperProps;
31
+
32
+ const helperDrivenState = helper?.state === "error" ? "error" : undefined;
33
+
34
+ return {
35
+ ...baseProps,
36
+ label: config.label ?? baseProps.label,
37
+ helper: helper?.text ?? config.helper ?? baseProps.helper,
38
+ state: helperDrivenState ?? baseProps.state,
39
+ block: config.block ?? baseProps.block ?? true,
40
+ className: config.className ?? baseProps.className,
41
+ labelProps: mergedLabelProps,
42
+ helperProps: mergedHelperProps,
43
+ };
44
+ };
@@ -0,0 +1,28 @@
1
+ import {
2
+ FindAccountInfoScreen,
3
+ FindAccountCodeScreen,
4
+ FindAccountIdComplete,
5
+ useFindAccountForm,
6
+ } from "./find-id";
7
+ import {
8
+ FindAccountPasswordForm,
9
+ FindAccountPasswordComplete,
10
+ } from "./find-password";
11
+
12
+ export type * from "./find-id/types";
13
+ export { useFindAccountForm };
14
+ export {
15
+ FindAccountInfoScreen,
16
+ FindAccountCodeScreen,
17
+ FindAccountIdComplete,
18
+ FindAccountPasswordForm,
19
+ FindAccountPasswordComplete,
20
+ };
21
+
22
+ export const FindAccount = {
23
+ Info: FindAccountInfoScreen,
24
+ Code: FindAccountCodeScreen,
25
+ IdComplete: FindAccountIdComplete,
26
+ PasswordForm: FindAccountPasswordForm,
27
+ PasswordComplete: FindAccountPasswordComplete,
28
+ };
@@ -0,0 +1 @@
1
+ export { useFindAccountForm } from "../../common/find/hooks/useFindAccountForm";
@@ -0,0 +1 @@
1
+ @use "../common/find/styles/find-account.scss";
@@ -0,0 +1,23 @@
1
+ import "./index.scss";
2
+ import FindIdStepIdentify from "./markup/StepIdentify";
3
+ import FindIdStepVerifyCode from "./markup/StepVerifyCode";
4
+ import FindIdStepComplete from "./markup/StepComplete";
5
+
6
+ export type * from "./types";
7
+ export * from "./hooks";
8
+
9
+ export const FindAccountInfoScreen = FindIdStepIdentify;
10
+ export const FindAccountCodeScreen = FindIdStepVerifyCode;
11
+ export const FindAccountIdComplete = FindIdStepComplete;
12
+
13
+ export { FindIdStepIdentify, FindIdStepVerifyCode, FindIdStepComplete };
14
+
15
+ export const FindId = {
16
+ StepIdentify: FindIdStepIdentify,
17
+ StepVerifyCode: FindIdStepVerifyCode,
18
+ StepComplete: FindIdStepComplete,
19
+ // Backwards compatibility
20
+ Info: FindAccountInfoScreen,
21
+ Code: FindAccountCodeScreen,
22
+ IdComplete: FindAccountIdComplete,
23
+ };
@@ -0,0 +1,58 @@
1
+ import clsx from "clsx";
2
+ import type { FindAccountIdCompleteProps } from "../types";
3
+ import AuthCompleteTemplate from "../../common/complete/Template";
4
+
5
+ const DEFAULT_TITLE = "아이디 찾기 완료";
6
+ const DEFAULT_ID_LABEL = "아이디";
7
+ const DEFAULT_REGISTERED_LABEL = "가입일";
8
+ const DEFAULT_DESCRIPTION = "입력하신 정보로 계정을 찾았어요.";
9
+ const DEFAULT_CTA_LABEL = "로그인하기";
10
+
11
+ /**
12
+ * 아이디 찾기 완료 화면
13
+ * @component
14
+ */
15
+ export default function FindIdStepComplete({
16
+ className,
17
+ title,
18
+ summary,
19
+ description,
20
+ cta,
21
+ }: FindAccountIdCompleteProps) {
22
+ return (
23
+ <AuthCompleteTemplate
24
+ className={clsx("auth-find-account-container", className)}
25
+ title={title ?? DEFAULT_TITLE}
26
+ description={description ?? DEFAULT_DESCRIPTION}
27
+ cta={{
28
+ label: cta?.label ?? DEFAULT_CTA_LABEL,
29
+ buttonProps: cta?.buttonProps,
30
+ }}
31
+ >
32
+ <section className="auth-find-account-id-summary">
33
+ <div className="auth-find-account-id-summary-card">
34
+ <dl className="auth-find-account-id-summary-row">
35
+ <dt className="auth-find-account-id-summary-text">
36
+ <span>{summary.userIdLabel ?? DEFAULT_ID_LABEL}</span>
37
+ </dt>
38
+ <dd className="auth-find-account-id-summary-text">
39
+ <span>{summary.userId}</span>
40
+ </dd>
41
+ </dl>
42
+ {summary.registeredAt ? (
43
+ <dl className="auth-find-account-id-summary-row">
44
+ <dt className="auth-find-account-id-summary-text">
45
+ <span>
46
+ {summary.registeredAtLabel ?? DEFAULT_REGISTERED_LABEL}
47
+ </span>
48
+ </dt>
49
+ <dd className="auth-find-account-id-summary-text">
50
+ <span>{summary.registeredAt}</span>
51
+ </dd>
52
+ </dl>
53
+ ) : null}
54
+ </div>
55
+ </section>
56
+ </AuthCompleteTemplate>
57
+ );
58
+ }
@@ -0,0 +1,46 @@
1
+ import type { ReactNode } from "react";
2
+ import FindAccountInfoStep from "../../common/find/markup/InfoStep";
3
+ import type { FindAccountInfoStepProps } from "../../common/find/types";
4
+
5
+ const DEFAULT_NAVIGATION = {
6
+ title: "아이디 찾기",
7
+ };
8
+
9
+ const DEFAULT_HEADLINE = {
10
+ progress: { total: 2, current: 1 },
11
+ title: (
12
+ <>
13
+ 가입한 이름과 메일 주소를
14
+ <br />
15
+ 입력해 주세요
16
+ </>
17
+ ),
18
+ };
19
+
20
+ const DEFAULT_CTA_LABEL = "인증코드 요청";
21
+
22
+ export type FindIdStepIdentifyProps = FindAccountInfoStepProps;
23
+
24
+ /**
25
+ * Find ID Step1 — 이름/메일 입력
26
+ * @component
27
+ */
28
+ export default function FindIdStepIdentify({
29
+ navigation,
30
+ headline,
31
+ cta,
32
+ ...rest
33
+ }: FindIdStepIdentifyProps) {
34
+ const resolvedNavigation = navigation ?? DEFAULT_NAVIGATION;
35
+ const resolvedHeadline = headline ?? DEFAULT_HEADLINE;
36
+ const resolvedCTA = cta ?? { label: DEFAULT_CTA_LABEL as ReactNode };
37
+
38
+ return (
39
+ <FindAccountInfoStep
40
+ {...rest}
41
+ navigation={resolvedNavigation}
42
+ headline={resolvedHeadline}
43
+ cta={resolvedCTA}
44
+ />
45
+ );
46
+ }
@@ -0,0 +1,48 @@
1
+ import FindAccountCodeStep from "../../common/find/markup/CodeStep";
2
+ import type { FindAccountCodeStepProps } from "../../common/find/types";
3
+
4
+ const DEFAULT_NAVIGATION = {
5
+ title: "아이디 찾기",
6
+ };
7
+
8
+ const DEFAULT_HEADLINE = {
9
+ progress: { total: 2, current: 2 },
10
+ title: (
11
+ <>
12
+ 메일 주소로
13
+ <br />
14
+ 인증을 진행해 주세요
15
+ </>
16
+ ),
17
+ };
18
+
19
+ const DEFAULT_EMAIL_LABEL = "메일";
20
+
21
+ export type FindIdStepVerifyCodeProps = FindAccountCodeStepProps;
22
+
23
+ /**
24
+ * Find ID Step2 — 인증코드 입력
25
+ * @component
26
+ */
27
+ export default function FindIdStepVerifyCode({
28
+ navigation,
29
+ headline,
30
+ emailDisplay,
31
+ ...rest
32
+ }: FindIdStepVerifyCodeProps) {
33
+ const resolvedNavigation = navigation ?? DEFAULT_NAVIGATION;
34
+ const resolvedHeadline = headline ?? DEFAULT_HEADLINE;
35
+ const resolvedEmailDisplay = {
36
+ label: DEFAULT_EMAIL_LABEL,
37
+ ...emailDisplay,
38
+ };
39
+
40
+ return (
41
+ <FindAccountCodeStep
42
+ {...rest}
43
+ navigation={resolvedNavigation}
44
+ headline={resolvedHeadline}
45
+ emailDisplay={resolvedEmailDisplay}
46
+ />
47
+ );
48
+ }