@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,285 @@
|
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
|
+
import { useForm } from "react-hook-form";
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
CheckboxField,
|
|
6
|
+
DrawerBody,
|
|
7
|
+
DrawerContent,
|
|
8
|
+
DrawerFooter,
|
|
9
|
+
DrawerHeader,
|
|
10
|
+
DrawerOverlay,
|
|
11
|
+
DrawerPortal,
|
|
12
|
+
DrawerRoot,
|
|
13
|
+
DrawerTitle,
|
|
14
|
+
EmailInput,
|
|
15
|
+
type EmailInputProps,
|
|
16
|
+
} from "@uniai-fe/uds-primitives";
|
|
17
|
+
import type { FormEvent } from "react";
|
|
18
|
+
import type {
|
|
19
|
+
AuthSignupAgreementOption,
|
|
20
|
+
AuthSignupVerificationProps,
|
|
21
|
+
AuthSignupVerificationValues,
|
|
22
|
+
} from "../types";
|
|
23
|
+
import { useSignupVerificationForm } from "../hooks";
|
|
24
|
+
import { composeSignupFieldProps } from "../utils/composeFieldProps";
|
|
25
|
+
import { getSignupFieldDefaultValue } from "../utils/getSignupFieldDefaultValue";
|
|
26
|
+
import CheckAgreeIcon from "../img/check-agree.svg";
|
|
27
|
+
import ChevronOpenDetailIcon from "../img/chevron-open-detail.svg";
|
|
28
|
+
|
|
29
|
+
const DEFAULT_SUBMIT_LABEL = "다음";
|
|
30
|
+
const REQUEST_CODE_LABEL = "인증코드 요청";
|
|
31
|
+
const RESEND_CODE_LABEL = "인증번호 재요청";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 회원가입 Step2; 약관 동의 + 이메일 인증
|
|
35
|
+
* @component
|
|
36
|
+
* @param {AuthSignupVerificationProps} props verification props
|
|
37
|
+
* @param {AuthSignupAgreementOption[]} props.agreements 약관 목록
|
|
38
|
+
* @param {Record<string, boolean>} props.agreementState 약관 체크 상태
|
|
39
|
+
* @param {(agreementId: string) => void} props.onToggleAgreement 단일 약관 토글
|
|
40
|
+
* @param {(options?: AuthSignupAgreementToggleAllOptions) => void} props.onToggleAll 전체 동의(필수 only) 토글
|
|
41
|
+
* @param {(agreementId: string) => void} [props.onOpenAgreementDetail] 약관 상세 보기
|
|
42
|
+
* @param {React.FormHTMLAttributes<HTMLFormElement>} [props.formAttr] form attr
|
|
43
|
+
* @param {import("react").ReactNode} [props.submitLabel] CTA 라벨
|
|
44
|
+
*/
|
|
45
|
+
export function AuthSignupVerificationForm({
|
|
46
|
+
fields,
|
|
47
|
+
agreements,
|
|
48
|
+
agreementState,
|
|
49
|
+
onToggleAgreement,
|
|
50
|
+
onToggleAll,
|
|
51
|
+
onOpenAgreementDetail,
|
|
52
|
+
formAttr,
|
|
53
|
+
submitLabel,
|
|
54
|
+
onSubmit,
|
|
55
|
+
}: AuthSignupVerificationProps) {
|
|
56
|
+
const [openedAgreementId, setOpenedAgreementId] = useState<string | null>(
|
|
57
|
+
null,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const handleOpenAgreementDetail = (agreementId: string) => {
|
|
61
|
+
if (onOpenAgreementDetail) {
|
|
62
|
+
onOpenAgreementDetail(agreementId);
|
|
63
|
+
}
|
|
64
|
+
setOpenedAgreementId(agreementId);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleCloseDrawer = () => {
|
|
68
|
+
setOpenedAgreementId(null);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const openedAgreement = useMemo(() => {
|
|
72
|
+
if (!openedAgreementId) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return agreements.find(option => option.id === openedAgreementId) ?? null;
|
|
76
|
+
}, [agreements, openedAgreementId]);
|
|
77
|
+
|
|
78
|
+
// 이메일 필드 name attr 기반으로 RHF defaultValues를 설정한다.
|
|
79
|
+
const defaultValues = useMemo(
|
|
80
|
+
() =>
|
|
81
|
+
(Object.keys(fields) as Array<keyof typeof fields>).reduce(
|
|
82
|
+
(acc, fieldKey) => {
|
|
83
|
+
const config = fields[fieldKey];
|
|
84
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
85
|
+
acc[fieldName] = getSignupFieldDefaultValue(config);
|
|
86
|
+
return acc;
|
|
87
|
+
},
|
|
88
|
+
{} as AuthSignupVerificationValues,
|
|
89
|
+
),
|
|
90
|
+
[fields],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const form = useForm<AuthSignupVerificationValues>({
|
|
94
|
+
mode: "onChange",
|
|
95
|
+
defaultValues,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const {
|
|
99
|
+
register,
|
|
100
|
+
helpers,
|
|
101
|
+
disabled,
|
|
102
|
+
onSubmit: handleSubmit,
|
|
103
|
+
} = useSignupVerificationForm({
|
|
104
|
+
fields,
|
|
105
|
+
form,
|
|
106
|
+
onSubmit,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const requiredAgreements = useMemo(
|
|
110
|
+
() => agreements.filter(option => option.required),
|
|
111
|
+
[agreements],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const allRequiredChecked = requiredAgreements.length
|
|
115
|
+
? requiredAgreements.every(option => agreementState[option.id])
|
|
116
|
+
: true;
|
|
117
|
+
|
|
118
|
+
const emailFieldProps = composeSignupFieldProps<EmailInputProps>(
|
|
119
|
+
fields.email,
|
|
120
|
+
helpers.email,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const emailRequestHandler = emailFieldProps.onRequestCode;
|
|
124
|
+
const codeRequested = Boolean(emailFieldProps.codeVisible);
|
|
125
|
+
const inlineRequestButtonVisible =
|
|
126
|
+
codeRequested && Boolean(emailRequestHandler);
|
|
127
|
+
const normalizedEmailFieldProps: EmailInputProps = inlineRequestButtonVisible
|
|
128
|
+
? {
|
|
129
|
+
...emailFieldProps,
|
|
130
|
+
requestButtonLabel:
|
|
131
|
+
emailFieldProps.requestButtonLabel ?? RESEND_CODE_LABEL,
|
|
132
|
+
}
|
|
133
|
+
: {
|
|
134
|
+
...emailFieldProps,
|
|
135
|
+
onRequestCode: undefined,
|
|
136
|
+
requestButtonLabel: undefined,
|
|
137
|
+
requestButtonDisabled: undefined,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const shouldRequestCode =
|
|
141
|
+
allRequiredChecked && !codeRequested && Boolean(emailRequestHandler);
|
|
142
|
+
const ctaLabel = shouldRequestCode
|
|
143
|
+
? REQUEST_CODE_LABEL
|
|
144
|
+
: (submitLabel ?? DEFAULT_SUBMIT_LABEL);
|
|
145
|
+
const ctaDisabled = disabled || !allRequiredChecked;
|
|
146
|
+
|
|
147
|
+
const handleFormSubmit = (event: FormEvent<HTMLFormElement>) => {
|
|
148
|
+
formAttr?.onSubmit?.(event);
|
|
149
|
+
if (event.defaultPrevented) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (shouldRequestCode && emailRequestHandler) {
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
event.stopPropagation();
|
|
156
|
+
emailRequestHandler();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
handleSubmit(event);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const renderAgreementLabel = (option: AuthSignupAgreementOption) => (
|
|
164
|
+
<span className="auth-signup-agreement-label">
|
|
165
|
+
<span
|
|
166
|
+
className="auth-signup-agreement-badge"
|
|
167
|
+
data-required={option.required ? "true" : "false"}
|
|
168
|
+
>
|
|
169
|
+
[{option.required ? "필수" : "선택"}]
|
|
170
|
+
</span>
|
|
171
|
+
<span className="auth-signup-agreement-title">{option.label}</span>
|
|
172
|
+
</span>
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<form
|
|
177
|
+
className="auth-signup-form auth-signup-form--verification"
|
|
178
|
+
{...formAttr}
|
|
179
|
+
onSubmit={handleFormSubmit}
|
|
180
|
+
>
|
|
181
|
+
<div className="auth-signup-fields">
|
|
182
|
+
<EmailInput {...normalizedEmailFieldProps} register={register.email} />
|
|
183
|
+
</div>
|
|
184
|
+
{agreements.length ? (
|
|
185
|
+
<section className="auth-signup-agreements" aria-label="약관 동의">
|
|
186
|
+
<div className="auth-signup-agreement-all">
|
|
187
|
+
<CheckboxField
|
|
188
|
+
size="large"
|
|
189
|
+
label={<span>필수 약관에 모두 동의하기</span>}
|
|
190
|
+
checked={allRequiredChecked}
|
|
191
|
+
onCheckedChange={() => onToggleAll({ requiredOnly: true })}
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
<div className="auth-signup-agreements-list">
|
|
195
|
+
{agreements.map(option => {
|
|
196
|
+
const checked = Boolean(agreementState[option.id]);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<div
|
|
200
|
+
className="auth-signup-agreement-row"
|
|
201
|
+
key={option.id}
|
|
202
|
+
data-required={option.required ? "true" : undefined}
|
|
203
|
+
data-checked={checked ? "true" : undefined}
|
|
204
|
+
>
|
|
205
|
+
{/* 약관 토글은 Figma 아이콘 사양대로 커스텀 버튼으로 구성한다. */}
|
|
206
|
+
<button
|
|
207
|
+
type="button"
|
|
208
|
+
className="auth-signup-agreement-toggle"
|
|
209
|
+
aria-pressed={checked}
|
|
210
|
+
data-checked={checked ? "true" : undefined}
|
|
211
|
+
onClick={() => onToggleAgreement(option.id)}
|
|
212
|
+
>
|
|
213
|
+
<span
|
|
214
|
+
className="auth-signup-agreement-icon"
|
|
215
|
+
aria-hidden="true"
|
|
216
|
+
>
|
|
217
|
+
<CheckAgreeIcon />
|
|
218
|
+
</span>
|
|
219
|
+
{renderAgreementLabel(option)}
|
|
220
|
+
</button>
|
|
221
|
+
{onOpenAgreementDetail ? (
|
|
222
|
+
<button
|
|
223
|
+
type="button"
|
|
224
|
+
className="auth-signup-agreement-detail"
|
|
225
|
+
aria-label={`${option.label} 약관 상세 보기`}
|
|
226
|
+
onClick={() => handleOpenAgreementDetail(option.id)}
|
|
227
|
+
>
|
|
228
|
+
<span aria-hidden="true">
|
|
229
|
+
<ChevronOpenDetailIcon />
|
|
230
|
+
</span>
|
|
231
|
+
</button>
|
|
232
|
+
) : null}
|
|
233
|
+
</div>
|
|
234
|
+
);
|
|
235
|
+
})}
|
|
236
|
+
</div>
|
|
237
|
+
</section>
|
|
238
|
+
) : null}
|
|
239
|
+
<Button.Default
|
|
240
|
+
type="submit"
|
|
241
|
+
scale="solid-xlarge"
|
|
242
|
+
priority="primary"
|
|
243
|
+
block
|
|
244
|
+
disabled={ctaDisabled}
|
|
245
|
+
>
|
|
246
|
+
{ctaLabel}
|
|
247
|
+
</Button.Default>
|
|
248
|
+
<DrawerRoot
|
|
249
|
+
open={Boolean(openedAgreement)}
|
|
250
|
+
onOpenChange={isOpen => {
|
|
251
|
+
if (!isOpen) {
|
|
252
|
+
handleCloseDrawer();
|
|
253
|
+
}
|
|
254
|
+
}}
|
|
255
|
+
>
|
|
256
|
+
<DrawerPortal>
|
|
257
|
+
<DrawerOverlay />
|
|
258
|
+
<DrawerContent>
|
|
259
|
+
<DrawerHeader>
|
|
260
|
+
<DrawerTitle>{openedAgreement?.label ?? "약관 상세"}</DrawerTitle>
|
|
261
|
+
</DrawerHeader>
|
|
262
|
+
{openedAgreement?.description ? (
|
|
263
|
+
<DrawerBody className="auth-signup-agreement-drawer-body">
|
|
264
|
+
{openedAgreement.description}
|
|
265
|
+
</DrawerBody>
|
|
266
|
+
) : null}
|
|
267
|
+
<DrawerFooter>
|
|
268
|
+
<Button.Default
|
|
269
|
+
type="button"
|
|
270
|
+
scale="solid-medium"
|
|
271
|
+
priority="primary"
|
|
272
|
+
block
|
|
273
|
+
onClick={handleCloseDrawer}
|
|
274
|
+
>
|
|
275
|
+
닫기
|
|
276
|
+
</Button.Default>
|
|
277
|
+
</DrawerFooter>
|
|
278
|
+
</DrawerContent>
|
|
279
|
+
</DrawerPortal>
|
|
280
|
+
</DrawerRoot>
|
|
281
|
+
</form>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export default AuthSignupVerificationForm;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AuthSignupUserInfoForm } from "./UserInfoForm";
|
|
2
|
+
export { AuthSignupVerificationForm } from "./VerificationForm";
|
|
3
|
+
export { AuthSignupAccountForm } from "./AccountForm";
|
|
4
|
+
export { AuthSignupComplete } from "./Complete";
|
|
5
|
+
export { AuthSignupTemplate } from "./Template";
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
.auth-signup-form {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--spacing-padding-8, 32px);
|
|
5
|
+
margin-top: var(--spacing-padding-9, 36px);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.auth-signup-fields {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: var(--spacing-padding-5, 20px);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.auth-signup-verification {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
gap: var(--spacing-padding-9, 36px);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.auth-signup-agreements {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
gap: var(--spacing-padding-4, 16px);
|
|
24
|
+
background: var(--color-background-subtle);
|
|
25
|
+
border-radius: var(--shape-rounded-3, 16px);
|
|
26
|
+
padding: var(--spacing-padding-5, 20px);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.auth-signup-agreement-all {
|
|
30
|
+
background: var(--color-background-soft, #f2f2f3);
|
|
31
|
+
border-radius: var(--shape-rounded-2, 12px);
|
|
32
|
+
padding: var(--spacing-padding-4, 16px);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.auth-signup-agreements-list {
|
|
36
|
+
width: 100%;
|
|
37
|
+
background: var(--color-common-100, #ffffff);
|
|
38
|
+
border-radius: var(--shape-rounded-2, 12px);
|
|
39
|
+
padding: var(--spacing-padding-4, 16px);
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
gap: var(--spacing-padding-3, 12px);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.auth-signup-agreement-row {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: space-between;
|
|
49
|
+
gap: var(--spacing-padding-3, 12px);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 약관 토글 행은 SVG 아이콘/텍스트를 하나의 버튼으로 묶어 디자인 명세와 일치시킨다.
|
|
53
|
+
.auth-signup-agreement-toggle {
|
|
54
|
+
border: none;
|
|
55
|
+
background: none;
|
|
56
|
+
padding: 0;
|
|
57
|
+
margin: 0;
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: var(--spacing-padding-3, 12px);
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
flex: 1;
|
|
63
|
+
text-align: left;
|
|
64
|
+
flex-wrap: nowrap;
|
|
65
|
+
font-size: 14px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.auth-signup-agreement-icon {
|
|
69
|
+
width: 20px;
|
|
70
|
+
height: 20px;
|
|
71
|
+
color: var(--color-label-assistive, #94989e);
|
|
72
|
+
flex-shrink: 0;
|
|
73
|
+
|
|
74
|
+
svg {
|
|
75
|
+
display: block;
|
|
76
|
+
width: 100%;
|
|
77
|
+
height: 100%;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
svg path {
|
|
81
|
+
stroke: currentColor;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.auth-signup-agreement-toggle[data-checked="true"] .auth-signup-agreement-icon {
|
|
86
|
+
color: var(--color-primary-default);
|
|
87
|
+
border-color: var(--color-primary-default);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.auth-signup-agreement-label {
|
|
91
|
+
display: inline-flex;
|
|
92
|
+
gap: var(--spacing-padding-1, 4px);
|
|
93
|
+
align-items: baseline;
|
|
94
|
+
flex-wrap: nowrap;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.auth-signup-agreement-badge {
|
|
98
|
+
font-size: 14px;
|
|
99
|
+
// font-weight: 600;
|
|
100
|
+
color: var(--color-primary-default);
|
|
101
|
+
|
|
102
|
+
&[data-required="false"] {
|
|
103
|
+
color: var(--color-label-standard);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.auth-signup-agreement-title {
|
|
108
|
+
font-size: 14px;
|
|
109
|
+
color: var(--color-label-standard);
|
|
110
|
+
font-weight: 400;
|
|
111
|
+
line-height: 1.4;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.auth-signup-agreement-description {
|
|
115
|
+
font-size: 13px;
|
|
116
|
+
color: var(--color-label-assistive);
|
|
117
|
+
margin: 0;
|
|
118
|
+
padding-left: calc(20px + var(--spacing-padding-3, 12px));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.auth-signup-agreement-detail {
|
|
122
|
+
border: none;
|
|
123
|
+
background: none;
|
|
124
|
+
color: var(--color-label-assistive);
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
padding: 0;
|
|
127
|
+
display: flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
justify-content: center;
|
|
130
|
+
width: 20px;
|
|
131
|
+
height: 20px;
|
|
132
|
+
|
|
133
|
+
svg {
|
|
134
|
+
display: block;
|
|
135
|
+
width: 100%;
|
|
136
|
+
height: 100%;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.auth-signup-agreement-detail:disabled {
|
|
141
|
+
opacity: 0.4;
|
|
142
|
+
cursor: default;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.auth-signup-agreement-detail:hover svg path {
|
|
146
|
+
fill: var(--color-label-standard);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.auth-signup-complete {
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
gap: var(--spacing-padding-7, 28px);
|
|
153
|
+
text-align: center;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.auth-signup-complete-illustration {
|
|
157
|
+
display: flex;
|
|
158
|
+
justify-content: center;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.auth-signup-complete-title {
|
|
162
|
+
font-size: 24px;
|
|
163
|
+
font-weight: 700;
|
|
164
|
+
color: var(--color-label-standard);
|
|
165
|
+
margin: 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.auth-signup-complete-description {
|
|
169
|
+
margin: var(--spacing-padding-2, 8px) 0 0;
|
|
170
|
+
color: var(--color-label-assistive);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.auth-signup-complete-actions {
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-direction: column;
|
|
176
|
+
gap: var(--spacing-padding-4, 16px);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.auth-signup-agreement-drawer-body {
|
|
180
|
+
p {
|
|
181
|
+
font-weight: 400;
|
|
182
|
+
color: var(--color-label-standard);
|
|
183
|
+
font-size: var(--font-body-xxsmall-size);
|
|
184
|
+
line-height: 1.5em;
|
|
185
|
+
letter-spacing: 0px;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { InputState } from "@uniai-fe/uds-primitives";
|
|
3
|
+
import type {
|
|
4
|
+
AuthSignupFieldProps,
|
|
5
|
+
AuthSignupUserInfoFields,
|
|
6
|
+
AuthSignupUserInfoValues,
|
|
7
|
+
AuthSignupVerificationFields,
|
|
8
|
+
AuthSignupVerificationValues,
|
|
9
|
+
AuthSignupAccountFields,
|
|
10
|
+
AuthSignupAccountValues,
|
|
11
|
+
} from "./props";
|
|
12
|
+
import type {
|
|
13
|
+
SubmitHandler,
|
|
14
|
+
UseFormRegisterReturn,
|
|
15
|
+
UseFormReturn,
|
|
16
|
+
} from "react-hook-form";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 공용 Helper 타입
|
|
20
|
+
*/
|
|
21
|
+
export type AuthSignupHelperMap<
|
|
22
|
+
TFields extends Record<string, AuthSignupFieldProps>,
|
|
23
|
+
> = Record<keyof TFields, { text?: ReactNode; state?: InputState }>;
|
|
24
|
+
|
|
25
|
+
/** Step1 옵션 */
|
|
26
|
+
export interface UseSignupUserInfoFormOptions<
|
|
27
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
28
|
+
AuthSignupUserInfoFields,
|
|
29
|
+
> {
|
|
30
|
+
fields: TFields;
|
|
31
|
+
form: UseFormReturn<AuthSignupUserInfoValues>;
|
|
32
|
+
onSubmit: SubmitHandler<AuthSignupUserInfoValues>;
|
|
33
|
+
activeFields?: Array<keyof TFields>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface UseSignupUserInfoFormReturn<
|
|
37
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
38
|
+
AuthSignupUserInfoFields,
|
|
39
|
+
> {
|
|
40
|
+
register: Record<keyof TFields, UseFormRegisterReturn>;
|
|
41
|
+
helpers: AuthSignupHelperMap<TFields>;
|
|
42
|
+
disabled: boolean;
|
|
43
|
+
onSubmit: ReturnType<UseFormReturn<AuthSignupUserInfoValues>["handleSubmit"]>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Step2 옵션 */
|
|
47
|
+
export interface UseSignupVerificationFormOptions<
|
|
48
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
49
|
+
AuthSignupVerificationFields,
|
|
50
|
+
> {
|
|
51
|
+
fields: TFields;
|
|
52
|
+
form: UseFormReturn<AuthSignupVerificationValues>;
|
|
53
|
+
onSubmit: SubmitHandler<AuthSignupVerificationValues>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface UseSignupVerificationFormReturn<
|
|
57
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
58
|
+
AuthSignupVerificationFields,
|
|
59
|
+
> {
|
|
60
|
+
register: Record<keyof TFields, UseFormRegisterReturn>;
|
|
61
|
+
helpers: AuthSignupHelperMap<TFields>;
|
|
62
|
+
disabled: boolean;
|
|
63
|
+
onSubmit: ReturnType<
|
|
64
|
+
UseFormReturn<AuthSignupVerificationValues>["handleSubmit"]
|
|
65
|
+
>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Step3 옵션 */
|
|
69
|
+
export interface UseSignupAccountFormOptions<
|
|
70
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
71
|
+
AuthSignupAccountFields,
|
|
72
|
+
> {
|
|
73
|
+
fields: TFields;
|
|
74
|
+
form: UseFormReturn<AuthSignupAccountValues>;
|
|
75
|
+
onSubmit: SubmitHandler<AuthSignupAccountValues>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface UseSignupAccountFormReturn<
|
|
79
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
80
|
+
AuthSignupAccountFields,
|
|
81
|
+
> {
|
|
82
|
+
register: Record<keyof TFields, UseFormRegisterReturn>;
|
|
83
|
+
helpers: AuthSignupHelperMap<TFields>;
|
|
84
|
+
disabled: boolean;
|
|
85
|
+
onSubmit: ReturnType<UseFormReturn<AuthSignupAccountValues>["handleSubmit"]>;
|
|
86
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type React from "react";
|
|
3
|
+
import type { SubmitHandler } from "react-hook-form";
|
|
4
|
+
import type {
|
|
5
|
+
EmailInputProps,
|
|
6
|
+
InputFieldProps,
|
|
7
|
+
InputPasswordProps,
|
|
8
|
+
InputProps,
|
|
9
|
+
PhoneInputProps,
|
|
10
|
+
} from "@uniai-fe/uds-primitives";
|
|
11
|
+
import type { AuthPasswordRule } from "../../common/password/types";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 회원가입 공통 필드 props; primitives InputFieldProps를 그대로 사용한다.
|
|
15
|
+
*/
|
|
16
|
+
export type AuthSignupFieldProps<TProps extends InputProps = InputProps> =
|
|
17
|
+
InputFieldProps<TProps>;
|
|
18
|
+
|
|
19
|
+
/** 사용자 기본 정보(이름/휴대폰) */
|
|
20
|
+
export type AuthSignupUserInfoFields = {
|
|
21
|
+
name: AuthSignupFieldProps<InputProps>;
|
|
22
|
+
phone: AuthSignupFieldProps<PhoneInputProps>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type AuthSignupUserInfoValues = Record<string, string>; // name/phone 값
|
|
26
|
+
|
|
27
|
+
export interface AuthSignupUserInfoProps {
|
|
28
|
+
fields: AuthSignupUserInfoFields;
|
|
29
|
+
visibleFields?: Array<keyof AuthSignupUserInfoFields>;
|
|
30
|
+
submitLabel?: ReactNode;
|
|
31
|
+
formAttr?: React.FormHTMLAttributes<HTMLFormElement>;
|
|
32
|
+
onSubmit: SubmitHandler<AuthSignupUserInfoValues>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 약관 + 이메일 인증 */
|
|
36
|
+
export interface AuthSignupAgreementOption {
|
|
37
|
+
id: string;
|
|
38
|
+
label: ReactNode;
|
|
39
|
+
description?: ReactNode;
|
|
40
|
+
required?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type AuthSignupAgreementState = Record<string, boolean>;
|
|
44
|
+
export interface AuthSignupAgreementToggleAllOptions {
|
|
45
|
+
requiredOnly?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type AuthSignupVerificationFields = {
|
|
49
|
+
email: AuthSignupFieldProps<EmailInputProps>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type AuthSignupVerificationValues = Record<string, string>;
|
|
53
|
+
|
|
54
|
+
export interface AuthSignupVerificationProps {
|
|
55
|
+
fields: AuthSignupVerificationFields;
|
|
56
|
+
agreements: AuthSignupAgreementOption[];
|
|
57
|
+
agreementState: AuthSignupAgreementState;
|
|
58
|
+
onToggleAgreement: (agreementId: string) => void;
|
|
59
|
+
onToggleAll: (options?: AuthSignupAgreementToggleAllOptions) => void;
|
|
60
|
+
onOpenAgreementDetail?: (agreementId: string) => void;
|
|
61
|
+
submitLabel?: ReactNode;
|
|
62
|
+
formAttr?: React.FormHTMLAttributes<HTMLFormElement>;
|
|
63
|
+
onSubmit: SubmitHandler<AuthSignupVerificationValues>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 계정 정보(아이디/비밀번호) */
|
|
67
|
+
export type AuthSignupAccountFields = {
|
|
68
|
+
accountId: AuthSignupFieldProps<InputProps>;
|
|
69
|
+
password: AuthSignupFieldProps<InputPasswordProps>;
|
|
70
|
+
confirmPassword: AuthSignupFieldProps<InputPasswordProps>;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type AuthSignupAccountValues = Record<string, string>;
|
|
74
|
+
|
|
75
|
+
export type AuthSignupPasswordRule = AuthPasswordRule;
|
|
76
|
+
|
|
77
|
+
export interface AuthSignupAccountProps {
|
|
78
|
+
fields: AuthSignupAccountFields;
|
|
79
|
+
passwordRules?: AuthSignupPasswordRule[];
|
|
80
|
+
submitLabel?: ReactNode;
|
|
81
|
+
formAttr?: React.FormHTMLAttributes<HTMLFormElement>;
|
|
82
|
+
onSubmit: SubmitHandler<AuthSignupAccountValues>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 완료 CTA */
|
|
86
|
+
export interface AuthSignupCompleteAction {
|
|
87
|
+
label: ReactNode;
|
|
88
|
+
onClick: () => void;
|
|
89
|
+
disabled?: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface AuthSignupCompleteProps {
|
|
93
|
+
title: ReactNode;
|
|
94
|
+
description?: ReactNode;
|
|
95
|
+
illustration?: ReactNode;
|
|
96
|
+
primaryAction: AuthSignupCompleteAction;
|
|
97
|
+
secondaryAction?: AuthSignupCompleteAction;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Step 식별자 */
|
|
101
|
+
export type AuthSignupStepId =
|
|
102
|
+
| "userInfo"
|
|
103
|
+
| "verifyAgreement"
|
|
104
|
+
| "generateAccount"
|
|
105
|
+
| "complete";
|
|
106
|
+
|
|
107
|
+
/** 헤더 구성 요소 */
|
|
108
|
+
export interface AuthSignupHeaderProps {
|
|
109
|
+
navigationTitle: ReactNode;
|
|
110
|
+
headline: ReactNode;
|
|
111
|
+
description?: ReactNode;
|
|
112
|
+
backIcon?: ReactNode;
|
|
113
|
+
onBack?: () => void;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Step indicator 항목 */
|
|
117
|
+
export interface AuthSignupStepIndicatorItem {
|
|
118
|
+
id: AuthSignupStepId;
|
|
119
|
+
label: ReactNode;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface AuthSignupStepIndicatorProps {
|
|
123
|
+
current?: AuthSignupStepId;
|
|
124
|
+
currentIndex?: number;
|
|
125
|
+
items?: AuthSignupStepIndicatorItem[];
|
|
126
|
+
total?: number;
|
|
127
|
+
showLabels?: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Step 구성 props 집합 */
|
|
131
|
+
export interface AuthSignupFlowProps {
|
|
132
|
+
userInfo: AuthSignupUserInfoProps;
|
|
133
|
+
verification: AuthSignupVerificationProps;
|
|
134
|
+
account: AuthSignupAccountProps;
|
|
135
|
+
complete: AuthSignupCompleteProps;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** 회원가입 템플릿 props */
|
|
139
|
+
export interface AuthSignupTemplateProps extends AuthSignupFlowProps {
|
|
140
|
+
className?: string;
|
|
141
|
+
header: AuthSignupHeaderProps;
|
|
142
|
+
step: AuthSignupStepId;
|
|
143
|
+
stepIndicator?: AuthSignupStepIndicatorProps;
|
|
144
|
+
footer?: React.ReactNode;
|
|
145
|
+
}
|