@uniai-fe/uds-templates 0.4.33 → 0.5.1
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 +12 -0
- package/dist/styles.css +142 -19
- package/package.json +4 -4
- package/src/auth/signup/hooks/index.ts +3 -0
- package/src/auth/signup/hooks/useSignupAgreementForm.ts +48 -0
- package/src/auth/signup/hooks/useSignupFarmCodeForm.ts +87 -0
- package/src/auth/signup/hooks/useSignupTypeSelectForm.ts +56 -0
- package/src/auth/signup/img/select-user-type-default.svg +15 -0
- package/src/auth/signup/img/select-user-type-selected.svg +3 -0
- package/src/auth/signup/index.ts +10 -0
- package/src/auth/signup/markup/AccountForm.tsx +1 -0
- package/src/auth/signup/markup/AgreementForm.tsx +229 -0
- package/src/auth/signup/markup/FarmCodeForm.tsx +122 -0
- package/src/auth/signup/markup/Template.tsx +58 -2
- package/src/auth/signup/markup/TypeSelectForm.tsx +102 -0
- package/src/auth/signup/markup/UserInfoForm.tsx +9 -0
- package/src/auth/signup/markup/VerificationForm.tsx +36 -26
- package/src/auth/signup/markup/index.ts +3 -0
- package/src/auth/signup/styles/signup.scss +95 -20
- package/src/auth/signup/types/hooks.ts +132 -0
- package/src/auth/signup/types/props.ts +210 -4
- package/src/edge-case/EdgeCase.tsx +16 -0
- package/src/edge-case/components/Empty.tsx +42 -0
- package/src/edge-case/components/Loading.tsx +42 -0
- package/src/edge-case/components/NotFound.tsx +74 -0
- package/src/edge-case/components/index.tsx +3 -0
- package/src/edge-case/img/404.svg +3 -0
- package/src/edge-case/index.scss +1 -0
- package/src/edge-case/index.tsx +11 -0
- package/src/edge-case/styles/edge-case.scss +56 -0
- package/src/edge-case/styles/index.scss +1 -0
- package/src/edge-case/types/index.ts +94 -0
- package/src/index.scss +1 -0
- package/src/index.tsx +1 -0
package/README.md
CHANGED
|
@@ -98,6 +98,14 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
|
|
|
98
98
|
- `MobileFrameProps`
|
|
99
99
|
- `PageFrameDesktopNavProps`
|
|
100
100
|
- `SitemapDataType`
|
|
101
|
+
- `/edge-case`
|
|
102
|
+
- `EdgeCase`
|
|
103
|
+
- `EdgeCase.Empty`
|
|
104
|
+
- `EdgeCase.Loading`
|
|
105
|
+
- `EdgeCase.NotFound`
|
|
106
|
+
- `EdgeCaseEmptyProps`
|
|
107
|
+
- `EdgeCaseLoadingProps`
|
|
108
|
+
- `EdgeCaseNotFoundProps`
|
|
101
109
|
|
|
102
110
|
## 현재 제공 템플릿/모듈
|
|
103
111
|
|
|
@@ -126,6 +134,9 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
|
|
|
126
134
|
- finder/viewer/video/pagination 조합과 rtc/company-list API helper를 제공한다.
|
|
127
135
|
- `/page-frame/**`
|
|
128
136
|
- mobile/desktop private route frame, nav/header/popup 조합을 제공한다.
|
|
137
|
+
- `/edge-case/**`
|
|
138
|
+
- empty/loading/not-found 화면 상태 템플릿을 제공한다.
|
|
139
|
+
- primitives `Alternate.Layout.*`를 조합하며, 실제 routing/history 판정은 서비스 앱이 소유한다.
|
|
129
140
|
|
|
130
141
|
각 템플릿의 상세한 범위와 의사결정은 `CONTEXT-*.md` 문서에서 관리합니다.
|
|
131
142
|
|
|
@@ -225,6 +236,7 @@ export default function LoginPage() {
|
|
|
225
236
|
|
|
226
237
|
- Modal CSS 변수 네임스페이스를 `--modal-*`, `--modal-alert-*`, `--modal-dialog-*` 세 축으로 통일했다. 기존 `--dialog-*` 이름을 사용하지 말고, Auth 템플릿이 Alert을 호출할 때에는 반드시 `Modal.Alert` 팩토리를 통해 렌더링한다.
|
|
227
238
|
- Auth(회원가입/아이디 찾기/비밀번호 찾기) 템플릿은 이메일 인증 타이머 5분, alert 문구, readonly 이메일 필드, 완료 단계 헤더 제거 등 최신 플로우를 반영했다. 모듈에서 `window.alert`/`window.confirm`을 호출하지 않고, 서비스 앱이 Modal helper를 주입하는 구조를 README와 `CONTEXT-AUTH*.md`에 명시했다.
|
|
239
|
+
- Edge Case 템플릿은 `EdgeCase.Empty`, `EdgeCase.Loading`, `EdgeCase.NotFound` namespace로 공개한다. `NotFound`는 `onPrev` 또는 `fallbackHref="/"`를 연결하지만 `router.back()`이나 service-origin 판정은 실행하지 않는다.
|
|
228
240
|
|
|
229
241
|
### 토큰 스코프 & ThemeProvider
|
|
230
242
|
|
package/dist/styles.css
CHANGED
|
@@ -1229,6 +1229,62 @@
|
|
|
1229
1229
|
gap: var(--spacing-padding-5);
|
|
1230
1230
|
}
|
|
1231
1231
|
|
|
1232
|
+
.auth-signup-type-options {
|
|
1233
|
+
display: flex;
|
|
1234
|
+
flex-direction: column;
|
|
1235
|
+
gap: var(--spacing-padding-6);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
.auth-signup-type-option-group {
|
|
1239
|
+
display: flex;
|
|
1240
|
+
flex-direction: column;
|
|
1241
|
+
gap: var(--spacing-padding-3);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
.auth-signup-type-question {
|
|
1245
|
+
margin: 0;
|
|
1246
|
+
color: var(--color-label-standard);
|
|
1247
|
+
font-size: var(--font-heading-xxsmall-size);
|
|
1248
|
+
line-height: 1.4;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
.auth-signup-type-option {
|
|
1252
|
+
display: flex;
|
|
1253
|
+
justify-content: flex-start;
|
|
1254
|
+
text-align: left;
|
|
1255
|
+
--button-default-padding-inline-large: 16px;
|
|
1256
|
+
--button-default-tertiary-outline-foreground: var(--color-label-alternative);
|
|
1257
|
+
}
|
|
1258
|
+
.auth-signup-type-option .button-left {
|
|
1259
|
+
display: flex;
|
|
1260
|
+
width: 24px;
|
|
1261
|
+
height: 24px;
|
|
1262
|
+
align-items: center;
|
|
1263
|
+
justify-content: center;
|
|
1264
|
+
flex-shrink: 0;
|
|
1265
|
+
}
|
|
1266
|
+
.auth-signup-type-option .button-left svg {
|
|
1267
|
+
display: block;
|
|
1268
|
+
width: 24px;
|
|
1269
|
+
height: 24px;
|
|
1270
|
+
}
|
|
1271
|
+
.auth-signup-type-option .button-label {
|
|
1272
|
+
flex: 1;
|
|
1273
|
+
text-align: left;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
.auth-signup-type-option-label {
|
|
1277
|
+
font-size: var(--font-body-large-size);
|
|
1278
|
+
line-height: 1.5;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
.auth-signup-type-description {
|
|
1282
|
+
margin: 0;
|
|
1283
|
+
color: var(--color-label-assistive);
|
|
1284
|
+
font-size: var(--font-body-xxsmall-size);
|
|
1285
|
+
line-height: 1.5;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1232
1288
|
.auth-signup-verification {
|
|
1233
1289
|
display: flex;
|
|
1234
1290
|
flex-direction: column;
|
|
@@ -1238,34 +1294,47 @@
|
|
|
1238
1294
|
.auth-signup-agreements {
|
|
1239
1295
|
display: flex;
|
|
1240
1296
|
flex-direction: column;
|
|
1241
|
-
gap:
|
|
1297
|
+
gap: 12px;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
.auth-signup-agreement-all-field {
|
|
1301
|
+
gap: 0;
|
|
1242
1302
|
}
|
|
1243
1303
|
|
|
1244
1304
|
.auth-signup-agreement-all {
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
align-items: center;
|
|
1305
|
+
width: 100%;
|
|
1306
|
+
min-height: 56px;
|
|
1248
1307
|
background: var(--color-bg-alternative-cool-gray, #f2f2f3);
|
|
1249
|
-
border-radius:
|
|
1250
|
-
|
|
1251
|
-
|
|
1308
|
+
border-radius: 12px;
|
|
1309
|
+
padding: 8px 12px;
|
|
1310
|
+
gap: 6px;
|
|
1311
|
+
align-items: center;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
.auth-signup-agreement-all-checkbox {
|
|
1315
|
+
flex-shrink: 0;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
.auth-signup-agreement-all-label {
|
|
1319
|
+
color: var(--color-label-standard);
|
|
1320
|
+
font-size: var(--font-body-large-size);
|
|
1321
|
+
font-weight: 500;
|
|
1322
|
+
line-height: 1.5;
|
|
1252
1323
|
}
|
|
1253
1324
|
|
|
1254
1325
|
.auth-signup-agreements-list {
|
|
1255
1326
|
width: 100%;
|
|
1256
|
-
background: var(--color-common-100);
|
|
1257
|
-
border-radius: var(--theme-radius-medium-3, 8px);
|
|
1258
|
-
padding: var(--spacing-padding-3);
|
|
1259
1327
|
display: flex;
|
|
1260
1328
|
flex-direction: column;
|
|
1261
|
-
gap:
|
|
1329
|
+
gap: 12px;
|
|
1330
|
+
padding: 4px 12px;
|
|
1262
1331
|
}
|
|
1263
1332
|
|
|
1264
1333
|
.auth-signup-agreement-row {
|
|
1265
1334
|
display: flex;
|
|
1266
1335
|
align-items: center;
|
|
1267
1336
|
justify-content: space-between;
|
|
1268
|
-
gap:
|
|
1337
|
+
gap: 8px;
|
|
1269
1338
|
}
|
|
1270
1339
|
|
|
1271
1340
|
.auth-signup-agreement-toggle {
|
|
@@ -1275,12 +1344,13 @@
|
|
|
1275
1344
|
margin: 0;
|
|
1276
1345
|
display: flex;
|
|
1277
1346
|
align-items: center;
|
|
1278
|
-
gap:
|
|
1347
|
+
gap: 8px;
|
|
1279
1348
|
cursor: pointer;
|
|
1280
1349
|
flex: 1;
|
|
1281
1350
|
text-align: left;
|
|
1282
1351
|
flex-wrap: nowrap;
|
|
1283
1352
|
font-size: 14px;
|
|
1353
|
+
min-width: 0;
|
|
1284
1354
|
}
|
|
1285
1355
|
|
|
1286
1356
|
.auth-signup-agreement-icon {
|
|
@@ -1300,18 +1370,19 @@
|
|
|
1300
1370
|
|
|
1301
1371
|
.auth-signup-agreement-toggle[data-checked=true] .auth-signup-agreement-icon {
|
|
1302
1372
|
color: var(--color-primary-default);
|
|
1303
|
-
border-color: var(--color-primary-default);
|
|
1304
1373
|
}
|
|
1305
1374
|
|
|
1306
1375
|
.auth-signup-agreement-label {
|
|
1307
|
-
display:
|
|
1308
|
-
gap:
|
|
1376
|
+
display: flex;
|
|
1377
|
+
gap: 0;
|
|
1309
1378
|
align-items: baseline;
|
|
1310
1379
|
flex-wrap: nowrap;
|
|
1380
|
+
min-width: 0;
|
|
1311
1381
|
}
|
|
1312
1382
|
|
|
1313
1383
|
.auth-signup-agreement-badge {
|
|
1314
1384
|
font-size: 14px;
|
|
1385
|
+
font-weight: 400;
|
|
1315
1386
|
color: var(--color-primary-default);
|
|
1316
1387
|
}
|
|
1317
1388
|
.auth-signup-agreement-badge[data-required=false] {
|
|
@@ -1322,7 +1393,9 @@
|
|
|
1322
1393
|
font-size: 14px;
|
|
1323
1394
|
color: var(--color-label-standard);
|
|
1324
1395
|
font-weight: 400;
|
|
1325
|
-
line-height: 1.
|
|
1396
|
+
line-height: 1.5;
|
|
1397
|
+
overflow: hidden;
|
|
1398
|
+
text-overflow: ellipsis;
|
|
1326
1399
|
}
|
|
1327
1400
|
|
|
1328
1401
|
.auth-signup-agreement-description {
|
|
@@ -1343,11 +1416,12 @@
|
|
|
1343
1416
|
justify-content: center;
|
|
1344
1417
|
width: 20px;
|
|
1345
1418
|
height: 20px;
|
|
1419
|
+
flex-shrink: 0;
|
|
1346
1420
|
}
|
|
1347
1421
|
.auth-signup-agreement-detail svg {
|
|
1348
1422
|
display: block;
|
|
1349
|
-
width:
|
|
1350
|
-
height:
|
|
1423
|
+
width: 16px;
|
|
1424
|
+
height: 16px;
|
|
1351
1425
|
}
|
|
1352
1426
|
|
|
1353
1427
|
.auth-signup-agreement-detail:disabled {
|
|
@@ -1849,3 +1923,52 @@
|
|
|
1849
1923
|
.page-frame-inquiry-button {
|
|
1850
1924
|
margin-top: auto;
|
|
1851
1925
|
}
|
|
1926
|
+
|
|
1927
|
+
.edge-case {
|
|
1928
|
+
width: 100%;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
.edge-case:where([data-edge-case=not-found]) {
|
|
1932
|
+
--alternate-layout-gap: var(--spacing-gap-3);
|
|
1933
|
+
--alternate-layout-figure-size: 128px;
|
|
1934
|
+
--alternate-layout-title-color: var(--color-label-standard);
|
|
1935
|
+
--alternate-layout-title-font-size: var(--font-heading-xsmall-size);
|
|
1936
|
+
--alternate-layout-title-line-height: var(--font-heading-xsmall-line-height);
|
|
1937
|
+
--alternate-layout-title-letter-spacing: var(
|
|
1938
|
+
--font-heading-xsmall-letter-spacing
|
|
1939
|
+
);
|
|
1940
|
+
--alternate-layout-title-font-weight: var(--font-heading-xsmall-weight);
|
|
1941
|
+
--alternate-layout-contents-font-size: var(--font-body-xsmall-size);
|
|
1942
|
+
--alternate-layout-contents-line-height: var(--font-body-xsmall-line-height);
|
|
1943
|
+
--alternate-layout-contents-letter-spacing: var(
|
|
1944
|
+
--font-body-xsmall-letter-spacing
|
|
1945
|
+
);
|
|
1946
|
+
--alternate-layout-contents-font-weight: var(--font-body-xsmall-weight);
|
|
1947
|
+
--button-default-font-label-medium-size: var(--font-label-small-size);
|
|
1948
|
+
--button-default-font-label-medium-line-height: var(
|
|
1949
|
+
--font-label-small-line-height
|
|
1950
|
+
);
|
|
1951
|
+
--button-default-font-label-medium-letter-spacing: var(
|
|
1952
|
+
--font-label-small-letter-spacing
|
|
1953
|
+
);
|
|
1954
|
+
--button-default-font-label-medium-weight: var(--font-label-small-weight);
|
|
1955
|
+
--button-default-font-weight: var(--font-label-small-weight);
|
|
1956
|
+
min-height: 0;
|
|
1957
|
+
padding: calc(var(--spacing-padding-11) + var(--spacing-padding-7)) var(--spacing-padding-8);
|
|
1958
|
+
border: 1px solid var(--color-border-assistive);
|
|
1959
|
+
border-radius: var(--theme-radius-large-1);
|
|
1960
|
+
background: var(--color-common-100);
|
|
1961
|
+
justify-content: flex-start;
|
|
1962
|
+
}
|
|
1963
|
+
.edge-case:where([data-edge-case=not-found]) :where(.alternate-layout-figure) {
|
|
1964
|
+
margin-bottom: var(--spacing-gap-3);
|
|
1965
|
+
}
|
|
1966
|
+
.edge-case:where([data-edge-case=not-found]) :where(.alternate-layout-contents) {
|
|
1967
|
+
white-space: nowrap;
|
|
1968
|
+
}
|
|
1969
|
+
.edge-case:where([data-edge-case=not-found]) :where(.alternate-layout-contents) :where(p + p) {
|
|
1970
|
+
margin-top: var(--spacing-gap-3);
|
|
1971
|
+
}
|
|
1972
|
+
.edge-case:where([data-edge-case=not-found]) :where(.alternate-layout-button) {
|
|
1973
|
+
margin-top: var(--spacing-gap-3);
|
|
1974
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniai-fe/uds-templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "UNIAI Design System; UI Templates Package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@svgr/webpack": "^8.1.0",
|
|
73
|
-
"@tanstack/react-query": "^5.
|
|
73
|
+
"@tanstack/react-query": "^5.99.0",
|
|
74
74
|
"@types/node": "^24.10.2",
|
|
75
75
|
"@types/react": "^19.2.14",
|
|
76
76
|
"@types/react-dom": "^19.2.3",
|
|
@@ -88,9 +88,9 @@
|
|
|
88
88
|
"eslint": "^9.39.2",
|
|
89
89
|
"jotai": "^2.19.1",
|
|
90
90
|
"next": "^15.5.11",
|
|
91
|
-
"prettier": "^3.8.
|
|
91
|
+
"prettier": "^3.8.3",
|
|
92
92
|
"react-hook-form": "^7.72.1",
|
|
93
93
|
"sass": "^1.99.0",
|
|
94
|
-
"typescript": "
|
|
94
|
+
"typescript": "5.9.3"
|
|
95
95
|
}
|
|
96
96
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import type {
|
|
5
|
+
UseSignupAgreementFormOptions,
|
|
6
|
+
UseSignupAgreementFormReturn,
|
|
7
|
+
} from "../types";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_THIRD_PARTY_AGREEMENT_ID = "thirdParty";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 회원가입 Step4 훅; 약관 동의 상태 계산
|
|
13
|
+
* @hook
|
|
14
|
+
* @param {UseSignupAgreementFormOptions} options 훅 옵션
|
|
15
|
+
* @desc
|
|
16
|
+
* - 필수 약관 id 집합과 CTA 활성화 여부를 계산한다.
|
|
17
|
+
*/
|
|
18
|
+
export function useSignupAgreementForm({
|
|
19
|
+
agreements,
|
|
20
|
+
agreementState,
|
|
21
|
+
isThirdPartyRequired,
|
|
22
|
+
thirdPartyAgreementId = DEFAULT_THIRD_PARTY_AGREEMENT_ID,
|
|
23
|
+
}: UseSignupAgreementFormOptions): UseSignupAgreementFormReturn {
|
|
24
|
+
const requiredAgreementIds = useMemo(() => {
|
|
25
|
+
return agreements
|
|
26
|
+
.filter(option => {
|
|
27
|
+
if (option.required) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return isThirdPartyRequired && option.id === thirdPartyAgreementId;
|
|
32
|
+
})
|
|
33
|
+
.map(option => option.id);
|
|
34
|
+
}, [agreements, isThirdPartyRequired, thirdPartyAgreementId]);
|
|
35
|
+
|
|
36
|
+
const allRequiredChecked =
|
|
37
|
+
requiredAgreementIds.length > 0
|
|
38
|
+
? requiredAgreementIds.every(id => Boolean(agreementState[id]))
|
|
39
|
+
: true;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
requiredAgreementIds,
|
|
43
|
+
allRequiredChecked,
|
|
44
|
+
disabled: !allRequiredChecked,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default useSignupAgreementForm;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { useWatch } from "react-hook-form";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import type {
|
|
7
|
+
AuthSignupFarmCodeFields,
|
|
8
|
+
AuthSignupFarmCodeValues,
|
|
9
|
+
AuthSignupFieldProps,
|
|
10
|
+
AuthSignupFormValues,
|
|
11
|
+
UseSignupFarmCodeFormOptions,
|
|
12
|
+
UseSignupFarmCodeFormReturn,
|
|
13
|
+
} from "../types";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 회원가입 Step3 훅; 농장 식별번호 입력
|
|
17
|
+
* @hook
|
|
18
|
+
* @template TFields
|
|
19
|
+
* @param {UseSignupFarmCodeFormOptions<TFields>} options 훅 옵션
|
|
20
|
+
* @desc
|
|
21
|
+
* - 1) form init → 2) register merge → 3) helper/state → 4) submit 순서
|
|
22
|
+
*/
|
|
23
|
+
export function useSignupFarmCodeForm<
|
|
24
|
+
TFields extends Record<string, AuthSignupFieldProps> =
|
|
25
|
+
AuthSignupFarmCodeFields,
|
|
26
|
+
>({
|
|
27
|
+
fields,
|
|
28
|
+
form,
|
|
29
|
+
onSubmit,
|
|
30
|
+
}: UseSignupFarmCodeFormOptions<TFields>): UseSignupFarmCodeFormReturn<TFields> {
|
|
31
|
+
const values = useWatch({ control: form.control }) as
|
|
32
|
+
| AuthSignupFormValues
|
|
33
|
+
| undefined;
|
|
34
|
+
|
|
35
|
+
const register = useMemo(() => {
|
|
36
|
+
return (Object.keys(fields) as Array<keyof TFields>).reduce(
|
|
37
|
+
(acc, fieldKey) => {
|
|
38
|
+
const config = fields[fieldKey] as AuthSignupFieldProps;
|
|
39
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
40
|
+
acc[fieldKey] = form.register(fieldName);
|
|
41
|
+
return acc;
|
|
42
|
+
},
|
|
43
|
+
{} as UseSignupFarmCodeFormReturn<TFields>["register"],
|
|
44
|
+
);
|
|
45
|
+
}, [fields, form]);
|
|
46
|
+
|
|
47
|
+
const helpers = useMemo(() => {
|
|
48
|
+
return (Object.keys(fields) as Array<keyof TFields>).reduce(
|
|
49
|
+
(acc, fieldKey) => {
|
|
50
|
+
const config = fields[fieldKey] as AuthSignupFieldProps;
|
|
51
|
+
const fieldName = config.attr?.name ?? String(fieldKey);
|
|
52
|
+
const state = form.getFieldState(fieldName);
|
|
53
|
+
acc[fieldKey] = {
|
|
54
|
+
text:
|
|
55
|
+
(state.error?.message as ReactNode | undefined) ?? config.helper,
|
|
56
|
+
state: state.invalid ? "error" : undefined,
|
|
57
|
+
};
|
|
58
|
+
return acc;
|
|
59
|
+
},
|
|
60
|
+
{} as UseSignupFarmCodeFormReturn<TFields>["helpers"],
|
|
61
|
+
);
|
|
62
|
+
}, [fields, form]);
|
|
63
|
+
|
|
64
|
+
const farmCodeFieldName =
|
|
65
|
+
fields.farmCode.attr?.name ??
|
|
66
|
+
("farmCode" as keyof AuthSignupFarmCodeValues);
|
|
67
|
+
const farmCodeValue =
|
|
68
|
+
values?.[farmCodeFieldName as keyof AuthSignupFormValues] ?? "";
|
|
69
|
+
const normalizedFarmCode =
|
|
70
|
+
typeof farmCodeValue === "string"
|
|
71
|
+
? farmCodeValue.replace(/\D/g, "")
|
|
72
|
+
: String(farmCodeValue ?? "").replace(/\D/g, "");
|
|
73
|
+
|
|
74
|
+
const disabled =
|
|
75
|
+
form.formState.isSubmitting || !/^\d{6}$/.test(normalizedFarmCode);
|
|
76
|
+
|
|
77
|
+
const onSubmitHandler = form.handleSubmit(onSubmit);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
register,
|
|
81
|
+
helpers,
|
|
82
|
+
disabled,
|
|
83
|
+
onSubmit: onSubmitHandler,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default useSignupFarmCodeForm;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect } from "react";
|
|
4
|
+
import { useWatch } from "react-hook-form";
|
|
5
|
+
import type {
|
|
6
|
+
AuthSignupFormValues,
|
|
7
|
+
AuthSignupTypeSelectValues,
|
|
8
|
+
AuthSignupTypeValue,
|
|
9
|
+
UseSignupTypeSelectFormOptions,
|
|
10
|
+
UseSignupTypeSelectFormReturn,
|
|
11
|
+
} from "../types";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 회원가입 Step1 훅; 가입 유형 선택
|
|
15
|
+
* @hook
|
|
16
|
+
* @param {UseSignupTypeSelectFormOptions} options 훅 옵션
|
|
17
|
+
* @desc
|
|
18
|
+
* - 1) form init → 2) register merge → 3) state/helper → 4) submit 순서
|
|
19
|
+
*/
|
|
20
|
+
export function useSignupTypeSelectForm({
|
|
21
|
+
form,
|
|
22
|
+
onSubmit,
|
|
23
|
+
}: UseSignupTypeSelectFormOptions): UseSignupTypeSelectFormReturn {
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
// 가입 유형은 커스텀 버튼으로 변경되므로 RHF에 명시적으로 등록한다.
|
|
26
|
+
form.register("signupType");
|
|
27
|
+
}, [form]);
|
|
28
|
+
|
|
29
|
+
const selectedType = useWatch({
|
|
30
|
+
control: form.control,
|
|
31
|
+
name: "signupType",
|
|
32
|
+
}) as AuthSignupTypeValue | undefined;
|
|
33
|
+
|
|
34
|
+
const handleValueChange = useCallback(
|
|
35
|
+
(value: AuthSignupTypeValue) => {
|
|
36
|
+
form.setValue("signupType", value, {
|
|
37
|
+
shouldDirty: true,
|
|
38
|
+
shouldTouch: true,
|
|
39
|
+
shouldValidate: true,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
[form],
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const disabled = form.formState.isSubmitting || !selectedType;
|
|
46
|
+
const onSubmitHandler = form.handleSubmit(onSubmit);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
selectedType,
|
|
50
|
+
onValueChange: handleValueChange,
|
|
51
|
+
disabled,
|
|
52
|
+
onSubmit: onSubmitHandler,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default useSignupTypeSelectForm;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
width="24"
|
|
3
|
+
height="24"
|
|
4
|
+
viewBox="0 0 24 24"
|
|
5
|
+
fill="none"
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
>
|
|
8
|
+
<path
|
|
9
|
+
d="M4 10.8995L9.65685 16.5563L19.5563 6.65683"
|
|
10
|
+
stroke="currentColor"
|
|
11
|
+
stroke-width="1.6"
|
|
12
|
+
stroke-linecap="round"
|
|
13
|
+
stroke-linejoin="round"
|
|
14
|
+
/>
|
|
15
|
+
</svg>
|
package/src/auth/signup/index.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AuthSignupTypeSelectForm,
|
|
2
3
|
AuthSignupUserInfoForm,
|
|
4
|
+
AuthSignupFarmCodeForm,
|
|
5
|
+
AuthSignupAgreementForm,
|
|
3
6
|
AuthSignupVerificationForm,
|
|
4
7
|
AuthSignupAccountForm,
|
|
5
8
|
AuthSignupComplete,
|
|
@@ -12,8 +15,11 @@ import "./styles/signup.scss";
|
|
|
12
15
|
export type * from "./types";
|
|
13
16
|
export * from "./hooks";
|
|
14
17
|
export {
|
|
18
|
+
AuthSignupTypeSelectForm,
|
|
15
19
|
AuthSignupProvider,
|
|
16
20
|
AuthSignupUserInfoForm,
|
|
21
|
+
AuthSignupFarmCodeForm,
|
|
22
|
+
AuthSignupAgreementForm,
|
|
17
23
|
AuthSignupVerificationForm,
|
|
18
24
|
AuthSignupAccountForm,
|
|
19
25
|
AuthSignupComplete,
|
|
@@ -23,6 +29,10 @@ export {
|
|
|
23
29
|
export const AuthSignup = {
|
|
24
30
|
Provider: AuthSignupProvider,
|
|
25
31
|
Template: AuthSignupTemplate,
|
|
32
|
+
StepTypeSelect: AuthSignupTypeSelectForm,
|
|
33
|
+
StepIdentity: AuthSignupUserInfoForm,
|
|
34
|
+
StepFarmCode: AuthSignupFarmCodeForm,
|
|
35
|
+
StepAgreement: AuthSignupAgreementForm,
|
|
26
36
|
StepUserInfo: AuthSignupUserInfoForm,
|
|
27
37
|
StepVerification: AuthSignupVerificationForm,
|
|
28
38
|
StepAccount: AuthSignupAccountForm,
|
|
@@ -86,6 +86,7 @@ export function AuthSignupAccountForm({
|
|
|
86
86
|
<div className="auth-signup-fields">
|
|
87
87
|
<Form.Field.Template
|
|
88
88
|
width={fields.accountId.template?.width ?? "full"}
|
|
89
|
+
state={helpers.accountId?.state === "error" ? "error" : undefined}
|
|
89
90
|
className={clsx(
|
|
90
91
|
"auth-signup-field",
|
|
91
92
|
"auth-signup-field-account-id",
|