@uniai-fe/uds-templates 0.4.1 → 0.4.3
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 +23 -0
- package/dist/styles.css +129 -0
- package/package.json +5 -5
- package/src/auth/common/container/index.scss +2 -0
- package/src/auth/common/container/index.tsx +0 -1
- package/src/auth/find-id/markup/StepComplete.tsx +1 -1
- package/src/auth/find-password/index.scss +2 -0
- package/src/auth/find-password/markup/StepComplete.tsx +1 -1
- package/src/auth/index.scss +7 -0
- package/src/auth/index.tsx +1 -1
- package/src/auth/login/types/api.ts +65 -0
- package/src/cctv/index.tsx +0 -2
- package/src/index.scss +3 -8
- package/src/index.tsx +4 -0
- package/src/modal/index.tsx +0 -2
- package/src/page-frame/desktop/components/header/util/setting/Button.tsx +3 -2
- package/src/page-frame/desktop/index.tsx +0 -2
- package/src/page-frame/index.scss +2 -0
- package/src/page-frame/index.tsx +2 -0
- package/src/page-frame/mobile/components/header/Header.tsx +0 -1
- package/src/page-frame/mobile/components/header/index.ts +2 -0
- package/src/page-frame/mobile/index.tsx +0 -2
- package/src/service-inquiry/components/Form.tsx +157 -0
- package/src/service-inquiry/components/OpenButton.tsx +46 -0
- package/src/service-inquiry/hooks/index.ts +2 -0
- package/src/service-inquiry/hooks/useOpenServiceInquiry.ts +48 -0
- package/src/service-inquiry/hooks/useServiceInquiryUserContext.ts +89 -0
- package/src/service-inquiry/index.scss +1 -0
- package/src/service-inquiry/index.tsx +26 -0
- package/src/service-inquiry/styles/form.scss +15 -0
- package/src/service-inquiry/styles/index.scss +2 -0
- package/src/service-inquiry/styles/open-button.scss +15 -0
- package/src/service-inquiry/types/api.ts +68 -0
- package/src/service-inquiry/types/form-context.ts +48 -0
- package/src/service-inquiry/types/hooks.ts +83 -0
- package/src/service-inquiry/types/index.ts +4 -0
- package/src/service-inquiry/types/props.ts +171 -0
- package/src/service-inquiry/utils/modal-option.tsx +38 -0
- package/src/weather/components/icon/Weather.tsx +4 -0
- package/src/weather/index.tsx +2 -2
package/README.md
CHANGED
|
@@ -55,6 +55,15 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
|
|
|
55
55
|
- `weatherCoordinate`
|
|
56
56
|
- `useWeatherKorea`
|
|
57
57
|
- `useOpenWeatherMap`
|
|
58
|
+
- `/service-inquiry`
|
|
59
|
+
- `ServiceInquiry.Form`
|
|
60
|
+
- `ServiceInquiry.OpenButton`
|
|
61
|
+
- `ServiceInquiry.useOpen`
|
|
62
|
+
- `ServiceInquiry.useUserContext`
|
|
63
|
+
- `ServiceInquiry.createModal`
|
|
64
|
+
- `ServiceInquiryFieldMode`
|
|
65
|
+
- `ServiceInquiryFormProps`
|
|
66
|
+
- `ServiceInquiryFormValues`
|
|
58
67
|
- `/cctv`
|
|
59
68
|
- `CCTV.Provider`
|
|
60
69
|
- `CCTV.CamList.Container`
|
|
@@ -95,6 +104,10 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
|
|
|
95
104
|
- `/modal/**`
|
|
96
105
|
- ui-legacy 스택 기반 모달 Provider/Root/Container + 템플릿(`Modal.Alert`, `Modal.Dialog`)
|
|
97
106
|
- Storybook(`apps/design-storybook/src/stories/templates/modal`)에서 Alert/Confirm 케이스를 검증한다.
|
|
107
|
+
- `/service-inquiry/**`
|
|
108
|
+
- 문의 입력 전용 form, 기본 원형 `?` open button, 커스텀 trigger용 open hook, request context 조립 hook, modal preset factory를 제공한다.
|
|
109
|
+
- submit transport, React Query mutation, Next.js route handler, 에러 피드백(`Modal.Alert`)은 서비스 앱이 소유한다.
|
|
110
|
+
- 로그인 후 `farm_name`, `contact`를 auto-fill + readonly로 보여야 할 때는 `formContextOptions.defaultValues`와 `farmNameField.mode`, `contactField.mode`를 함께 전달한다.
|
|
98
111
|
- `/weather/**`
|
|
99
112
|
- page-frame header utility에 결합되는 weather header 템플릿과 weather data hook/mock 도구를 제공한다.
|
|
100
113
|
- `/cctv/**`
|
|
@@ -104,6 +117,16 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
|
|
|
104
117
|
|
|
105
118
|
각 템플릿의 상세한 범위와 의사결정은 `CONTEXT-*.md` 문서에서 관리합니다.
|
|
106
119
|
|
|
120
|
+
### Service Inquiry 도입 흐름
|
|
121
|
+
|
|
122
|
+
1. 서비스 앱이 `react-hook-form`으로 `defaultValues`와 `onSubmit`을 준비한다.
|
|
123
|
+
2. 필요하면 `ServiceInquiry.useUserContext`로 `labels`, `page_path`, 추가 `user_context`를 조립한다.
|
|
124
|
+
3. 기본 버튼은 `ServiceInquiry.OpenButton`, 커스텀 버튼은 `ServiceInquiry.useOpen`으로 모달 open을 연결한다.
|
|
125
|
+
4. modal footer confirm이 `ServiceInquiry.Form` submit 진입을 담당한다.
|
|
126
|
+
5. 실제 submit은 서비스 앱의 `useMutation + Next.js route handler + Modal.Alert` 조합으로 처리한다.
|
|
127
|
+
|
|
128
|
+
`service-inquiry`는 구조와 request context까지만 제공하고, 네트워크 상태/재시도/성공·실패 피드백은 서비스 앱이 소유합니다.
|
|
129
|
+
|
|
107
130
|
### 회원가입 Step 구조
|
|
108
131
|
|
|
109
132
|
1. **Step1 — User Info (name + phone)**: 기본 정보 입력. PhoneInput은 인증 UI 없이 마스킹만 제공한다.
|
package/dist/styles.css
CHANGED
|
@@ -746,6 +746,46 @@
|
|
|
746
746
|
transition: none;
|
|
747
747
|
}
|
|
748
748
|
}
|
|
749
|
+
.auth-stage-header {
|
|
750
|
+
display: flex;
|
|
751
|
+
flex-direction: column;
|
|
752
|
+
gap: var(--spacing-padding-5, 20px);
|
|
753
|
+
padding: 0 var(--spacing-padding-1, 4px);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.auth-stage-step {
|
|
757
|
+
display: flex;
|
|
758
|
+
justify-content: flex-start;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.auth-stage-step-pagination {
|
|
762
|
+
--pagination-carousel-gap: 6px;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.auth-stage-headline {
|
|
766
|
+
display: flex;
|
|
767
|
+
flex-direction: column;
|
|
768
|
+
gap: var(--spacing-padding-1, 4px);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.auth-stage-headline-text {
|
|
772
|
+
margin: 0;
|
|
773
|
+
font-size: 24px;
|
|
774
|
+
font-weight: 600;
|
|
775
|
+
line-height: 1.4;
|
|
776
|
+
letter-spacing: 0.2px;
|
|
777
|
+
color: var(--color-label-standard);
|
|
778
|
+
font-family: "Pretendard JP Variable", "Pretendard", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
.auth-stage-headline-description {
|
|
782
|
+
margin: 0;
|
|
783
|
+
font-size: 14px;
|
|
784
|
+
line-height: 1.4;
|
|
785
|
+
color: var(--color-label-assistive);
|
|
786
|
+
font-family: "Pretendard", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
787
|
+
}
|
|
788
|
+
|
|
749
789
|
|
|
750
790
|
|
|
751
791
|
.auth-container {
|
|
@@ -826,6 +866,10 @@
|
|
|
826
866
|
margin-top: var(--spacing-padding-4, 16px);
|
|
827
867
|
}
|
|
828
868
|
|
|
869
|
+
.auth-code-field {
|
|
870
|
+
margin-top: var(--spacing-gap-6);
|
|
871
|
+
}
|
|
872
|
+
|
|
829
873
|
.auth-login-form {
|
|
830
874
|
display: flex;
|
|
831
875
|
flex-direction: column;
|
|
@@ -1067,6 +1111,59 @@
|
|
|
1067
1111
|
margin: 0;
|
|
1068
1112
|
}
|
|
1069
1113
|
|
|
1114
|
+
.password-confirm-field {
|
|
1115
|
+
margin-top: var(--spacing-gap-6);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
.auth-password-helper {
|
|
1119
|
+
display: flex;
|
|
1120
|
+
flex-wrap: wrap;
|
|
1121
|
+
align-items: center;
|
|
1122
|
+
gap: var(--spacing-gap-3);
|
|
1123
|
+
margin-top: var(--spacing-gap-2);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.auth-password-helper-column {
|
|
1127
|
+
flex-direction: column;
|
|
1128
|
+
align-items: flex-start;
|
|
1129
|
+
gap: var(--spacing-gap-1);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
.auth-set-password-icon {
|
|
1133
|
+
display: flex;
|
|
1134
|
+
align-items: center;
|
|
1135
|
+
justify-content: center;
|
|
1136
|
+
font-size: 0;
|
|
1137
|
+
}
|
|
1138
|
+
.auth-set-password-icon svg path {
|
|
1139
|
+
fill: var(--color-label-assistive);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
.auth-password-helper-text {
|
|
1143
|
+
font-size: var(--font-label-small-size);
|
|
1144
|
+
line-height: var(--font-label-small-line-height);
|
|
1145
|
+
font-weight: var(--font-label-small-weight);
|
|
1146
|
+
color: var(--color-label-assistive);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
.auth-password-helper-item {
|
|
1150
|
+
display: flex;
|
|
1151
|
+
align-items: center;
|
|
1152
|
+
gap: var(--spacing-gap-1);
|
|
1153
|
+
}
|
|
1154
|
+
.auth-password-helper-item[data-state=complete] .auth-password-helper-text {
|
|
1155
|
+
color: var(--color-success);
|
|
1156
|
+
}
|
|
1157
|
+
.auth-password-helper-item[data-state=complete] .auth-set-password-icon svg path {
|
|
1158
|
+
fill: var(--color-success);
|
|
1159
|
+
}
|
|
1160
|
+
.auth-password-helper-item[data-state=error] .auth-password-helper-text {
|
|
1161
|
+
color: var(--color-error);
|
|
1162
|
+
}
|
|
1163
|
+
.auth-password-helper-item[data-state=error] .auth-set-password-icon svg path {
|
|
1164
|
+
fill: var(--color-error);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1070
1167
|
.auth-signup-form {
|
|
1071
1168
|
display: flex;
|
|
1072
1169
|
flex-direction: column;
|
|
@@ -1635,3 +1732,35 @@
|
|
|
1635
1732
|
.cctv-viewer-desktop-pagination-container {
|
|
1636
1733
|
margin-top: var(--spacing-gap-8);
|
|
1637
1734
|
}
|
|
1735
|
+
|
|
1736
|
+
.service-inquiry-form {
|
|
1737
|
+
display: flex;
|
|
1738
|
+
flex-direction: column;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
.service-inquiry-fields {
|
|
1742
|
+
display: flex;
|
|
1743
|
+
flex-direction: column;
|
|
1744
|
+
gap: var(--spacing-padding-5);
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
.service-inquiry-field {
|
|
1748
|
+
display: flex;
|
|
1749
|
+
flex-direction: column;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
.service-inquiry-open-button {
|
|
1753
|
+
width: 40px;
|
|
1754
|
+
height: 40px;
|
|
1755
|
+
border-radius: 20px;
|
|
1756
|
+
border: 1px solid var(--color-border-standard-cool-gray);
|
|
1757
|
+
background: var(--color_100);
|
|
1758
|
+
color: var(--color-label-neutral);
|
|
1759
|
+
font-size: 24px;
|
|
1760
|
+
font-weight: 400;
|
|
1761
|
+
display: flex;
|
|
1762
|
+
align-items: center;
|
|
1763
|
+
justify-content: center;
|
|
1764
|
+
padding: 0;
|
|
1765
|
+
cursor: pointer;
|
|
1766
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniai-fe/uds-templates",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "UNIAI Design System; UI Templates Package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
15
|
-
"packageManager": "pnpm@10.
|
|
15
|
+
"packageManager": "pnpm@10.33.0",
|
|
16
16
|
"engines": {
|
|
17
17
|
"node": ">=24",
|
|
18
18
|
"pnpm": ">=10"
|
|
@@ -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.95.2",
|
|
74
74
|
"@types/node": "^24.10.2",
|
|
75
75
|
"@types/react": "^19.2.14",
|
|
76
76
|
"@types/react-dom": "^19.2.3",
|
|
@@ -86,10 +86,10 @@
|
|
|
86
86
|
"@uniai-fe/util-next": "workspace:*",
|
|
87
87
|
"@uniai-fe/util-rtc": "workspace:*",
|
|
88
88
|
"eslint": "^9.39.2",
|
|
89
|
-
"jotai": "^2.
|
|
89
|
+
"jotai": "^2.19.0",
|
|
90
90
|
"next": "^15.5.11",
|
|
91
91
|
"prettier": "^3.8.1",
|
|
92
|
-
"react-hook-form": "^7.
|
|
92
|
+
"react-hook-form": "^7.72.0",
|
|
93
93
|
"sass": "^1.98.0",
|
|
94
94
|
"typescript": "~5.9.3"
|
|
95
95
|
}
|
|
@@ -20,7 +20,7 @@ export default function FindPasswordStepComplete({
|
|
|
20
20
|
title={title ?? "비밀번호 변경 완료"}
|
|
21
21
|
description={description ?? "로그인 페이지로 이동하여 로그인해 주세요."}
|
|
22
22
|
cta={{
|
|
23
|
-
label: cta?.label ?? "
|
|
23
|
+
label: cta?.label ?? "확인",
|
|
24
24
|
buttonProps: cta?.buttonProps,
|
|
25
25
|
}}
|
|
26
26
|
/>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
@use "./common/container/index.scss" as authCommonContainer;
|
|
2
|
+
@use "./common/complete/index.scss" as authCommonComplete;
|
|
3
|
+
@use "./common/auth-code/styles/index.scss" as authCommonAuthCode;
|
|
4
|
+
@use "./login/index.scss" as authLogin;
|
|
5
|
+
@use "./find-id/index.scss" as authFindId;
|
|
6
|
+
@use "./find-password/index.scss" as authFindPassword;
|
|
7
|
+
@use "./signup/styles/signup.scss" as authSignup;
|
package/src/auth/index.tsx
CHANGED
|
@@ -51,6 +51,58 @@ export interface API_Res_LoginUserFarm {
|
|
|
51
51
|
user_access_role: string;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* 로그인 API; 응답 - 소속 부서 정보
|
|
56
|
+
* @route /auth/user/login
|
|
57
|
+
* @property {number} org_id 소속 조직 인덱스
|
|
58
|
+
* @property {string} org_name 소속 조직명
|
|
59
|
+
* @property {string} org_type 소속 조직 유형 코드
|
|
60
|
+
* @property {number} parent_org_id 상위 조직 인덱스
|
|
61
|
+
* @property {string} parent_org_name 상위 조직명
|
|
62
|
+
* @property {number} root_org_id 최상위 조직 인덱스
|
|
63
|
+
* @property {string} root_org_name 최상위 조직명
|
|
64
|
+
* @property {number} position_id 직책 인덱스
|
|
65
|
+
* @property {string} position_name 직책명
|
|
66
|
+
*/
|
|
67
|
+
export interface API_Res_LoginUserAuth {
|
|
68
|
+
/**
|
|
69
|
+
* 소속 조직 인덱스
|
|
70
|
+
*/
|
|
71
|
+
org_id: number;
|
|
72
|
+
/**
|
|
73
|
+
* 소속 조직명
|
|
74
|
+
*/
|
|
75
|
+
org_name: string;
|
|
76
|
+
/**
|
|
77
|
+
* 소속 조직 유형 코드
|
|
78
|
+
*/
|
|
79
|
+
org_type: string;
|
|
80
|
+
/**
|
|
81
|
+
* 상위 조직 인덱스
|
|
82
|
+
*/
|
|
83
|
+
parent_org_id: number;
|
|
84
|
+
/**
|
|
85
|
+
* 상위 조직명
|
|
86
|
+
*/
|
|
87
|
+
parent_org_name: string;
|
|
88
|
+
/**
|
|
89
|
+
* 최상위 조직 인덱스
|
|
90
|
+
*/
|
|
91
|
+
root_org_id: number;
|
|
92
|
+
/**
|
|
93
|
+
* 최상위 조직명
|
|
94
|
+
*/
|
|
95
|
+
root_org_name: string;
|
|
96
|
+
/**
|
|
97
|
+
* 직책 인덱스
|
|
98
|
+
*/
|
|
99
|
+
position_id: number;
|
|
100
|
+
/**
|
|
101
|
+
* 직책명
|
|
102
|
+
*/
|
|
103
|
+
position_name: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
54
106
|
/**
|
|
55
107
|
* 로그인 API; 응답 - 유저 정보
|
|
56
108
|
* @route /auth/user/login
|
|
@@ -120,6 +172,19 @@ export interface API_Res_LoginUserInfo {
|
|
|
120
172
|
* @property {string} user_access_role 계정 권한/직책 코드 (OWNER: 농장주, EMPLOYEE: 농장직원, REGIONAL_MANAGER: 지역소장)
|
|
121
173
|
*/
|
|
122
174
|
user_farms: API_Res_LoginUserFarm[];
|
|
175
|
+
/**
|
|
176
|
+
* (avic 전용) 소속부서 정보
|
|
177
|
+
* @property {number} org_id 소속 조직 인덱스
|
|
178
|
+
* @property {string} org_name 소속 조직명
|
|
179
|
+
* @property {string} org_type 소속 조직 유형 코드
|
|
180
|
+
* @property {number} parent_org_id 상위 조직 인덱스
|
|
181
|
+
* @property {string} parent_org_name 상위 조직명
|
|
182
|
+
* @property {number} root_org_id 최상위 조직 인덱스
|
|
183
|
+
* @property {string} root_org_name 최상위 조직명
|
|
184
|
+
* @property {number} position_id 직책 인덱스
|
|
185
|
+
* @property {string} position_name 직책명
|
|
186
|
+
*/
|
|
187
|
+
avic_organizations: API_Res_LoginUserAuth[];
|
|
123
188
|
/**
|
|
124
189
|
* 가입일 (UTC)
|
|
125
190
|
* - yyyy-MM-ddTHH:mm:ss
|
package/src/cctv/index.tsx
CHANGED
package/src/index.scss
CHANGED
|
@@ -3,15 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
/* 템플릿 레벨 스타일을 통합해 서비스 앱이 단일 엔트리만 import하도록 구성한다. */
|
|
5
5
|
/* namespace 충돌을 막기 위해 모든 @use 경로에 고유 alias를 부여한다. */
|
|
6
|
-
@use "./page-frame/
|
|
7
|
-
@use "./page-frame/mobile/index.scss" as pageFrameMobile;
|
|
6
|
+
@use "./page-frame/index.scss" as pageFrameStyles;
|
|
8
7
|
@use "./modal/index.scss" as modalStyles;
|
|
9
8
|
|
|
10
|
-
@use "./auth/
|
|
11
|
-
@use "./auth/common/complete/index.scss" as authCommonComplete;
|
|
12
|
-
@use "./auth/login/index.scss" as authLogin;
|
|
13
|
-
@use "./auth/find-id/index.scss" as authFindId;
|
|
14
|
-
@use "./auth/find-password/index.scss" as authFindPassword;
|
|
15
|
-
@use "./auth/signup/styles/signup.scss" as authSignup;
|
|
9
|
+
@use "./auth/index.scss" as authStyles;
|
|
16
10
|
@use "./weather/index.scss" as weatherStyles;
|
|
17
11
|
@use "./cctv/index.scss" as cctvStyles;
|
|
12
|
+
@use "./service-inquiry/index.scss" as serviceInquiryStyles;
|
package/src/index.tsx
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import "./modal/index.scss";
|
|
2
|
+
import "./cctv/index.scss";
|
|
3
|
+
|
|
1
4
|
export * from "./page-frame";
|
|
2
5
|
export * from "./auth";
|
|
3
6
|
export * from "./modal";
|
|
4
7
|
export * from "./weather";
|
|
5
8
|
export * from "./cctv";
|
|
9
|
+
export * from "./service-inquiry";
|
|
6
10
|
|
|
7
11
|
export type * from "./types";
|
package/src/modal/index.tsx
CHANGED
|
@@ -64,8 +64,9 @@ export default function PageHeaderSettingButton({
|
|
|
64
64
|
}, [menuItems]);
|
|
65
65
|
|
|
66
66
|
const isMatchRouteGroup = useMemo(
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
// Storybook의 next/navigation mock에서는 pathname이 null일 수 있어 fallback 경로를 재사용한다.
|
|
68
|
+
() => resolvedPath.startsWith(commonRoute),
|
|
69
|
+
[resolvedPath, commonRoute],
|
|
69
70
|
);
|
|
70
71
|
|
|
71
72
|
const dropdownItems: DropdownTemplateItem[] = useMemo(
|
package/src/page-frame/index.tsx
CHANGED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { Form, Input } from "@uniai-fe/uds-primitives";
|
|
5
|
+
import { useFormContext } from "react-hook-form";
|
|
6
|
+
import type { ServiceInquiryFieldKey, ServiceInquiryFormProps } from "../types";
|
|
7
|
+
import type { ServiceInquiryFormValues } from "../types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Service Inquiry Form; 문의 입력 form
|
|
11
|
+
* @component
|
|
12
|
+
* @param {ServiceInquiryFormProps} props 문의 form props
|
|
13
|
+
* @param {string} [props.className] form className
|
|
14
|
+
* @param {ServiceInquiryFieldKey[]} [props.visibleFields] 노출 필드 목록
|
|
15
|
+
* @param {ServiceInquiryInputFieldProps} [props.farmNameField] 농장명 필드 설정
|
|
16
|
+
* @param {ServiceInquiryInputFieldProps} [props.contactField] 연락처 필드 설정
|
|
17
|
+
* @param {ServiceInquiryTextAreaFieldProps} [props.textField] 문의 본문 필드 설정
|
|
18
|
+
* @param {SubmitHandler<ServiceInquiryFormValues>} props.onSubmit 문의 제출 핸들러
|
|
19
|
+
* @example
|
|
20
|
+
* <ServiceInquiryForm onSubmit={values => console.info(values)} />
|
|
21
|
+
*/
|
|
22
|
+
const ServiceInquiryForm = ({
|
|
23
|
+
className,
|
|
24
|
+
visibleFields,
|
|
25
|
+
farmNameField,
|
|
26
|
+
contactField,
|
|
27
|
+
textField,
|
|
28
|
+
onSubmit,
|
|
29
|
+
}: ServiceInquiryFormProps) => {
|
|
30
|
+
const form = useFormContext<ServiceInquiryFormValues>();
|
|
31
|
+
|
|
32
|
+
// 변경 설명: Modal.Dialog confirm 기본 submit과 연결되도록 form.handleSubmit 결과를 그대로 onSubmit에 바인딩한다.
|
|
33
|
+
const handleSubmit = form.handleSubmit(onSubmit);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<form
|
|
37
|
+
className={clsx("service-inquiry-form", className)}
|
|
38
|
+
onSubmit={handleSubmit}
|
|
39
|
+
>
|
|
40
|
+
<div className="service-inquiry-fields">
|
|
41
|
+
{(visibleFields?.includes("farm_name") ?? true) ? (
|
|
42
|
+
<Form.Field.Template
|
|
43
|
+
className={clsx(
|
|
44
|
+
"service-inquiry-field",
|
|
45
|
+
"service-inquiry-field-farm-name",
|
|
46
|
+
farmNameField?.className,
|
|
47
|
+
)}
|
|
48
|
+
width={farmNameField?.inputProps?.width ?? "full"}
|
|
49
|
+
headerProps={{
|
|
50
|
+
required: farmNameField?.required,
|
|
51
|
+
...(typeof farmNameField?.label === "string"
|
|
52
|
+
? { label: farmNameField.label }
|
|
53
|
+
: typeof farmNameField?.label === "number"
|
|
54
|
+
? { label: String(farmNameField.label) }
|
|
55
|
+
: {
|
|
56
|
+
labelJsx:
|
|
57
|
+
typeof farmNameField?.label === "undefined"
|
|
58
|
+
? "농장명"
|
|
59
|
+
: farmNameField.label,
|
|
60
|
+
}),
|
|
61
|
+
}}
|
|
62
|
+
footer={farmNameField?.helper}
|
|
63
|
+
>
|
|
64
|
+
<Input.Base
|
|
65
|
+
type="text"
|
|
66
|
+
block={farmNameField?.inputProps?.block ?? true}
|
|
67
|
+
readOnly={
|
|
68
|
+
farmNameField?.mode === "readonly" ||
|
|
69
|
+
farmNameField?.inputProps?.readOnly === true
|
|
70
|
+
}
|
|
71
|
+
placeholder={farmNameField?.placeholder ?? "농장명 입력"}
|
|
72
|
+
{...farmNameField?.inputProps}
|
|
73
|
+
register={form.register("farm_name")}
|
|
74
|
+
/>
|
|
75
|
+
</Form.Field.Template>
|
|
76
|
+
) : null}
|
|
77
|
+
|
|
78
|
+
{(visibleFields?.includes("contact") ?? true) ? (
|
|
79
|
+
<Form.Field.Template
|
|
80
|
+
className={clsx(
|
|
81
|
+
"service-inquiry-field",
|
|
82
|
+
"service-inquiry-field-contact",
|
|
83
|
+
contactField?.className,
|
|
84
|
+
)}
|
|
85
|
+
width={contactField?.inputProps?.width ?? "full"}
|
|
86
|
+
headerProps={{
|
|
87
|
+
required: contactField?.required ?? true,
|
|
88
|
+
...(typeof contactField?.label === "string"
|
|
89
|
+
? { label: contactField.label }
|
|
90
|
+
: typeof contactField?.label === "number"
|
|
91
|
+
? { label: String(contactField.label) }
|
|
92
|
+
: {
|
|
93
|
+
labelJsx:
|
|
94
|
+
typeof contactField?.label === "undefined"
|
|
95
|
+
? "연락처"
|
|
96
|
+
: contactField.label,
|
|
97
|
+
}),
|
|
98
|
+
}}
|
|
99
|
+
footer={contactField?.helper}
|
|
100
|
+
>
|
|
101
|
+
<Input.Base
|
|
102
|
+
type="text"
|
|
103
|
+
block={contactField?.inputProps?.block ?? true}
|
|
104
|
+
readOnly={
|
|
105
|
+
contactField?.mode === "readonly" ||
|
|
106
|
+
contactField?.inputProps?.readOnly === true
|
|
107
|
+
}
|
|
108
|
+
placeholder={
|
|
109
|
+
contactField?.placeholder ?? "이메일 또는 전화번호 입력"
|
|
110
|
+
}
|
|
111
|
+
{...contactField?.inputProps}
|
|
112
|
+
register={form.register("contact")}
|
|
113
|
+
/>
|
|
114
|
+
</Form.Field.Template>
|
|
115
|
+
) : null}
|
|
116
|
+
|
|
117
|
+
{(visibleFields?.includes("text") ?? true) ? (
|
|
118
|
+
<Form.Field.Template
|
|
119
|
+
className={clsx(
|
|
120
|
+
"service-inquiry-field",
|
|
121
|
+
"service-inquiry-field-text",
|
|
122
|
+
textField?.className,
|
|
123
|
+
)}
|
|
124
|
+
width={textField?.inputProps?.width ?? "full"}
|
|
125
|
+
headerProps={{
|
|
126
|
+
required: textField?.required ?? true,
|
|
127
|
+
...(typeof textField?.label === "string"
|
|
128
|
+
? { label: textField.label }
|
|
129
|
+
: typeof textField?.label === "number"
|
|
130
|
+
? { label: String(textField.label) }
|
|
131
|
+
: {
|
|
132
|
+
labelJsx:
|
|
133
|
+
typeof textField?.label === "undefined"
|
|
134
|
+
? "문의 내용"
|
|
135
|
+
: textField.label,
|
|
136
|
+
}),
|
|
137
|
+
}}
|
|
138
|
+
footer={textField?.helper}
|
|
139
|
+
>
|
|
140
|
+
<Input.TextArea
|
|
141
|
+
block={textField?.inputProps?.block ?? true}
|
|
142
|
+
placeholder={
|
|
143
|
+
textField?.placeholder ?? "문의 내용을 자세히 입력해 주세요."
|
|
144
|
+
}
|
|
145
|
+
height={textField?.inputProps?.height ?? 160}
|
|
146
|
+
length={textField?.inputProps?.length ?? 10000}
|
|
147
|
+
{...textField?.inputProps}
|
|
148
|
+
register={form.register("text")}
|
|
149
|
+
/>
|
|
150
|
+
</Form.Field.Template>
|
|
151
|
+
) : null}
|
|
152
|
+
</div>
|
|
153
|
+
</form>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export default ServiceInquiryForm;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useOpenServiceInquiry } from "../hooks";
|
|
4
|
+
import type { ServiceInquiryOpenButtonProps } from "../types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Service Inquiry Open Button; 문의 모달 trigger adapter
|
|
8
|
+
* @component
|
|
9
|
+
* @param {ServiceInquiryOpenButtonProps} props 문의 모달 열기 props
|
|
10
|
+
* @param {string} props.stackKey modal stack key
|
|
11
|
+
* @param {ServiceInquiryFormProps} props.formProps 문의 form props
|
|
12
|
+
* @param {Partial<DialogTemplateOptions<ServiceInquiryFormValues>>} [props.dialogOptions] 문의 모달 preset 위에 덮어쓸 dialog option
|
|
13
|
+
* @param {() => void} [props.onOpen] 모달 open 직전 콜백
|
|
14
|
+
* @example
|
|
15
|
+
* <ServiceInquiryOpenButton
|
|
16
|
+
* stackKey="sample"
|
|
17
|
+
* formProps={{ onSubmit: values => console.info(values) }}
|
|
18
|
+
* />
|
|
19
|
+
*/
|
|
20
|
+
const ServiceInquiryOpenButton = ({
|
|
21
|
+
stackKey,
|
|
22
|
+
formProps,
|
|
23
|
+
dialogOptions,
|
|
24
|
+
onOpen,
|
|
25
|
+
}: ServiceInquiryOpenButtonProps) => {
|
|
26
|
+
const { openServiceInquiry } = useOpenServiceInquiry({
|
|
27
|
+
stackKey,
|
|
28
|
+
formProps,
|
|
29
|
+
dialogOptions,
|
|
30
|
+
onOpen,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
className="service-inquiry-open-button"
|
|
37
|
+
aria-label="문의하기"
|
|
38
|
+
// 변경 설명: 기본 제공 버튼은 고정 원형 `?` 버튼 사양으로 렌더링한다.
|
|
39
|
+
onClick={openServiceInquiry}
|
|
40
|
+
>
|
|
41
|
+
?
|
|
42
|
+
</button>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default ServiceInquiryOpenButton;
|