@uniai-fe/uds-templates 0.4.2 → 0.4.4
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 +27 -0
- package/dist/styles.css +32 -0
- package/package.json +1 -1
- package/src/index.scss +1 -0
- package/src/index.tsx +1 -0
- package/src/service-inquiry/components/Form.tsx +145 -0
- package/src/service-inquiry/components/OpenButton.tsx +46 -0
- package/src/service-inquiry/hooks/index.ts +3 -0
- package/src/service-inquiry/hooks/useOpen.ts +48 -0
- package/src/service-inquiry/hooks/useProvideContext.ts +49 -0
- package/src/service-inquiry/hooks/useUserContext.ts +57 -0
- package/src/service-inquiry/index.scss +1 -0
- package/src/service-inquiry/index.tsx +32 -0
- package/src/service-inquiry/jotai/context.ts +88 -0
- package/src/service-inquiry/jotai/index.ts +1 -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 +60 -0
- package/src/service-inquiry/types/index.ts +4 -0
- package/src/service-inquiry/types/props.ts +146 -0
- package/src/service-inquiry/utils/modal-option.tsx +38 -0
package/README.md
CHANGED
|
@@ -55,6 +55,17 @@ 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.useProvideContext`
|
|
63
|
+
- `ServiceInquiry.useUserContext`
|
|
64
|
+
- `ServiceInquiry.createModal`
|
|
65
|
+
- `ServiceInquiryFieldMode`
|
|
66
|
+
- `ServiceInquiryFormProps`
|
|
67
|
+
- `ServiceInquiryFormValues`
|
|
68
|
+
- `ServiceInquiryProvidedContext`
|
|
58
69
|
- `/cctv`
|
|
59
70
|
- `CCTV.Provider`
|
|
60
71
|
- `CCTV.CamList.Container`
|
|
@@ -95,6 +106,11 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
|
|
|
95
106
|
- `/modal/**`
|
|
96
107
|
- ui-legacy 스택 기반 모달 Provider/Root/Container + 템플릿(`Modal.Alert`, `Modal.Dialog`)
|
|
97
108
|
- Storybook(`apps/design-storybook/src/stories/templates/modal`)에서 Alert/Confirm 케이스를 검증한다.
|
|
109
|
+
- `/service-inquiry/**`
|
|
110
|
+
- 문의 입력 전용 form, 기본 원형 `?` open button, 커스텀 trigger용 open hook, 페이지 context 등록 hook, request context 조립 hook, modal preset factory를 제공한다.
|
|
111
|
+
- submit transport, React Query mutation, Next.js route handler, 에러 피드백(`Modal.Alert`)은 서비스 앱이 소유한다.
|
|
112
|
+
- 모듈 내부 Jotai registry를 사용하므로, layout 고정 버튼 1개와 페이지별 context 등록 hook 조합으로 ready-to-use 구성이 가능하다.
|
|
113
|
+
- 로그인 후 `farm_name`, `contact`를 auto-fill + readonly로 보여야 할 때는 `formContextOptions.defaultValues`와 `farmNameField.mode`, `contactField.mode`를 함께 전달한다.
|
|
98
114
|
- `/weather/**`
|
|
99
115
|
- page-frame header utility에 결합되는 weather header 템플릿과 weather data hook/mock 도구를 제공한다.
|
|
100
116
|
- `/cctv/**`
|
|
@@ -104,6 +120,17 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
|
|
|
104
120
|
|
|
105
121
|
각 템플릿의 상세한 범위와 의사결정은 `CONTEXT-*.md` 문서에서 관리합니다.
|
|
106
122
|
|
|
123
|
+
### Service Inquiry 도입 흐름
|
|
124
|
+
|
|
125
|
+
1. 서비스 앱이 `react-hook-form`으로 `defaultValues`와 `onSubmit`을 준비한다.
|
|
126
|
+
2. layout 고정 버튼은 `ServiceInquiry.useUserContext()`로 모듈 내부 registry 기반 `requestContext`를 읽는다.
|
|
127
|
+
3. 각 페이지/폼은 `ServiceInquiry.useProvideContext({ labels, pagePath, userContext })`로 `ServiceInquiryProvidedContext`를 등록한다.
|
|
128
|
+
4. 기본 버튼은 `ServiceInquiry.OpenButton`, 커스텀 버튼은 `ServiceInquiry.useOpen`으로 모달 open을 연결한다.
|
|
129
|
+
5. modal footer confirm이 `ServiceInquiry.Form` submit 진입을 담당한다.
|
|
130
|
+
6. 실제 submit은 서비스 앱의 `useMutation + Next.js route handler + Modal.Alert` 조합으로 처리한다.
|
|
131
|
+
|
|
132
|
+
`service-inquiry`는 구조와 request context까지만 제공하고, 네트워크 상태/재시도/성공·실패 피드백은 서비스 앱이 소유합니다.
|
|
133
|
+
|
|
107
134
|
### 회원가입 Step 구조
|
|
108
135
|
|
|
109
136
|
1. **Step1 — User Info (name + phone)**: 기본 정보 입력. PhoneInput은 인증 UI 없이 마스킹만 제공한다.
|
package/dist/styles.css
CHANGED
|
@@ -1732,3 +1732,35 @@
|
|
|
1732
1732
|
.cctv-viewer-desktop-pagination-container {
|
|
1733
1733
|
margin-top: var(--spacing-gap-8);
|
|
1734
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
package/src/index.scss
CHANGED
package/src/index.tsx
CHANGED
|
@@ -0,0 +1,145 @@
|
|
|
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
|
+
)}
|
|
47
|
+
width="full"
|
|
48
|
+
headerProps={{
|
|
49
|
+
required: farmNameField?.required,
|
|
50
|
+
...(typeof farmNameField?.label === "string"
|
|
51
|
+
? { label: farmNameField.label }
|
|
52
|
+
: typeof farmNameField?.label === "number"
|
|
53
|
+
? { label: String(farmNameField.label) }
|
|
54
|
+
: {
|
|
55
|
+
labelJsx:
|
|
56
|
+
typeof farmNameField?.label === "undefined"
|
|
57
|
+
? "농장명"
|
|
58
|
+
: farmNameField.label,
|
|
59
|
+
}),
|
|
60
|
+
}}
|
|
61
|
+
footer={farmNameField?.helper}
|
|
62
|
+
>
|
|
63
|
+
<Input.Base
|
|
64
|
+
type="text"
|
|
65
|
+
block={true}
|
|
66
|
+
readOnly={farmNameField?.mode === "readonly"}
|
|
67
|
+
placeholder={farmNameField?.placeholder ?? "농장명 입력"}
|
|
68
|
+
register={form.register("farm_name")}
|
|
69
|
+
/>
|
|
70
|
+
</Form.Field.Template>
|
|
71
|
+
) : null}
|
|
72
|
+
|
|
73
|
+
{(visibleFields?.includes("contact") ?? true) ? (
|
|
74
|
+
<Form.Field.Template
|
|
75
|
+
className={clsx(
|
|
76
|
+
"service-inquiry-field",
|
|
77
|
+
"service-inquiry-field-contact",
|
|
78
|
+
)}
|
|
79
|
+
width="full"
|
|
80
|
+
headerProps={{
|
|
81
|
+
required: contactField?.required ?? true,
|
|
82
|
+
...(typeof contactField?.label === "string"
|
|
83
|
+
? { label: contactField.label }
|
|
84
|
+
: typeof contactField?.label === "number"
|
|
85
|
+
? { label: String(contactField.label) }
|
|
86
|
+
: {
|
|
87
|
+
labelJsx:
|
|
88
|
+
typeof contactField?.label === "undefined"
|
|
89
|
+
? "연락처"
|
|
90
|
+
: contactField.label,
|
|
91
|
+
}),
|
|
92
|
+
}}
|
|
93
|
+
footer={contactField?.helper}
|
|
94
|
+
>
|
|
95
|
+
<Input.Base
|
|
96
|
+
type="text"
|
|
97
|
+
block={true}
|
|
98
|
+
readOnly={contactField?.mode === "readonly"}
|
|
99
|
+
placeholder={
|
|
100
|
+
contactField?.placeholder ?? "이메일 또는 전화번호 입력"
|
|
101
|
+
}
|
|
102
|
+
register={form.register("contact")}
|
|
103
|
+
/>
|
|
104
|
+
</Form.Field.Template>
|
|
105
|
+
) : null}
|
|
106
|
+
|
|
107
|
+
{(visibleFields?.includes("text") ?? true) ? (
|
|
108
|
+
<Form.Field.Template
|
|
109
|
+
className={clsx(
|
|
110
|
+
"service-inquiry-field",
|
|
111
|
+
"service-inquiry-field-text",
|
|
112
|
+
)}
|
|
113
|
+
width="full"
|
|
114
|
+
headerProps={{
|
|
115
|
+
required: textField?.required ?? true,
|
|
116
|
+
...(typeof textField?.label === "string"
|
|
117
|
+
? { label: textField.label }
|
|
118
|
+
: typeof textField?.label === "number"
|
|
119
|
+
? { label: String(textField.label) }
|
|
120
|
+
: {
|
|
121
|
+
labelJsx:
|
|
122
|
+
typeof textField?.label === "undefined"
|
|
123
|
+
? "문의 내용"
|
|
124
|
+
: textField.label,
|
|
125
|
+
}),
|
|
126
|
+
}}
|
|
127
|
+
footer={textField?.helper}
|
|
128
|
+
>
|
|
129
|
+
<Input.TextArea
|
|
130
|
+
block={true}
|
|
131
|
+
placeholder={
|
|
132
|
+
textField?.placeholder ?? "문의 내용을 자세히 입력해 주세요."
|
|
133
|
+
}
|
|
134
|
+
height={160}
|
|
135
|
+
length={10000}
|
|
136
|
+
register={form.register("text")}
|
|
137
|
+
/>
|
|
138
|
+
</Form.Field.Template>
|
|
139
|
+
) : null}
|
|
140
|
+
</div>
|
|
141
|
+
</form>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default ServiceInquiryForm;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useOpenServiceInquiry } from "../hooks";
|
|
4
|
+
import type { UseOpenServiceInquiryOptions } from "../types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Service Inquiry Open Button; 문의 모달 trigger adapter
|
|
8
|
+
* @component
|
|
9
|
+
* @param {UseOpenServiceInquiryOptions} 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
|
+
}: UseOpenServiceInquiryOptions) => {
|
|
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;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Modal } from "../../modal";
|
|
4
|
+
import { createServiceInquiryModal } from "../utils/modal-option";
|
|
5
|
+
import type {
|
|
6
|
+
UseOpenServiceInquiryOptions,
|
|
7
|
+
UseOpenServiceInquiryReturn,
|
|
8
|
+
} from "../types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Service Inquiry Hook; 문의 모달 열기 Hook
|
|
12
|
+
* @hook
|
|
13
|
+
* @param {UseOpenServiceInquiryOptions} options 문의 모달 열기 훅 옵션
|
|
14
|
+
* @param {string} options.stackKey modal stack key
|
|
15
|
+
* @param {ServiceInquiryFormProps} options.formProps 문의 form props
|
|
16
|
+
* @param {Partial<DialogTemplateOptions<ServiceInquiryFormValues>>} [options.dialogOptions] 문의 모달 preset 위에 덮어쓸 dialog option
|
|
17
|
+
* @param {() => void} [options.onOpen] 모달 open 직전 콜백
|
|
18
|
+
* @returns {UseOpenServiceInquiryReturn} 문의 모달 open 함수
|
|
19
|
+
* @example
|
|
20
|
+
* const { openServiceInquiry } = useOpenServiceInquiry({
|
|
21
|
+
* stackKey: "sample",
|
|
22
|
+
* formProps: { onSubmit: values => console.info(values) },
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
export function useOpenServiceInquiry({
|
|
26
|
+
stackKey,
|
|
27
|
+
formProps,
|
|
28
|
+
dialogOptions,
|
|
29
|
+
onOpen,
|
|
30
|
+
}: UseOpenServiceInquiryOptions): UseOpenServiceInquiryReturn {
|
|
31
|
+
const { newModal } = Modal.useModal();
|
|
32
|
+
|
|
33
|
+
// 변경 설명: 커스텀 버튼은 render prop 대신 hook이 돌려주는 단일 open 함수로 연결한다.
|
|
34
|
+
const openServiceInquiry = () => {
|
|
35
|
+
onOpen?.();
|
|
36
|
+
newModal(
|
|
37
|
+
createServiceInquiryModal({
|
|
38
|
+
stackKey,
|
|
39
|
+
formProps,
|
|
40
|
+
dialogOptions,
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
openServiceInquiry,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useId } from "react";
|
|
4
|
+
import { useSetAtom } from "jotai";
|
|
5
|
+
import { serviceInquiryProvidedContextRegistryAtom } from "../jotai";
|
|
6
|
+
import type { ServiceInquiryProvidedContext } from "../types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Service Inquiry Hook; 페이지별 문의 context 등록 Hook
|
|
10
|
+
* @hook
|
|
11
|
+
* @param {ServiceInquiryProvidedContext | null} context 페이지 또는 폼이 등록할 문의 context
|
|
12
|
+
* @example
|
|
13
|
+
* useProvideServiceInquiryContext({
|
|
14
|
+
* labels: ["orders"],
|
|
15
|
+
* userContext: { order_id: selectedOrderId },
|
|
16
|
+
* });
|
|
17
|
+
*/
|
|
18
|
+
export function useProvideServiceInquiryContext(
|
|
19
|
+
context: ServiceInquiryProvidedContext | null,
|
|
20
|
+
): void {
|
|
21
|
+
const registrationId = useId();
|
|
22
|
+
const setProvidedContextRegistry = useSetAtom(
|
|
23
|
+
serviceInquiryProvidedContextRegistryAtom,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
setProvidedContextRegistry(currentRegistry => {
|
|
28
|
+
const nextRegistry = { ...currentRegistry };
|
|
29
|
+
|
|
30
|
+
if (context) {
|
|
31
|
+
nextRegistry[registrationId] = context;
|
|
32
|
+
} else {
|
|
33
|
+
delete nextRegistry[registrationId];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return nextRegistry;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
setProvidedContextRegistry(currentRegistry => {
|
|
41
|
+
const nextRegistry = { ...currentRegistry };
|
|
42
|
+
|
|
43
|
+
delete nextRegistry[registrationId];
|
|
44
|
+
|
|
45
|
+
return nextRegistry;
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
}, [context, registrationId, setProvidedContextRegistry]);
|
|
49
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useAtom, useAtomValue } from "jotai";
|
|
4
|
+
import type {
|
|
5
|
+
ServiceInquiryUserContext,
|
|
6
|
+
UseServiceInquiryUserContextReturn,
|
|
7
|
+
} from "../types";
|
|
8
|
+
import {
|
|
9
|
+
serviceInquiryAdditionalUserContextAtom,
|
|
10
|
+
serviceInquiryRequestContextAtom,
|
|
11
|
+
} from "../jotai";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Service Inquiry Hook; request context 제어 Hook
|
|
15
|
+
* @hook
|
|
16
|
+
* @returns {UseServiceInquiryUserContextReturn} request context 및 추가 user_context 제어 함수
|
|
17
|
+
* @example
|
|
18
|
+
* const inquiryContext = useServiceInquiryUserContext();
|
|
19
|
+
*/
|
|
20
|
+
export function useServiceInquiryUserContext(): UseServiceInquiryUserContextReturn {
|
|
21
|
+
/** 1) form init — request context snapshot과 trigger 추가 context를 atom 기반으로 읽는다. */
|
|
22
|
+
const requestContext = useAtomValue(serviceInquiryRequestContextAtom);
|
|
23
|
+
const [additionalUserContext, setAdditionalUserContext] = useAtom(
|
|
24
|
+
serviceInquiryAdditionalUserContextAtom,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
/** 2) submit/액션 — trigger별 추가 context를 append/reset 할 수 있게 연다. */
|
|
28
|
+
const setNextAdditionalUserContext: UseServiceInquiryUserContextReturn["setAdditionalUserContext"] =
|
|
29
|
+
nextUserContext => {
|
|
30
|
+
setAdditionalUserContext(nextUserContext);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const appendUserContext: UseServiceInquiryUserContextReturn["appendUserContext"] =
|
|
34
|
+
(nextUserContext?: ServiceInquiryUserContext | null) => {
|
|
35
|
+
if (!nextUserContext) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setAdditionalUserContext(currentUserContext => ({
|
|
40
|
+
...(currentUserContext ?? {}),
|
|
41
|
+
...nextUserContext,
|
|
42
|
+
}));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const clearAdditionalUserContext: UseServiceInquiryUserContextReturn["clearAdditionalUserContext"] =
|
|
46
|
+
() => {
|
|
47
|
+
setAdditionalUserContext(null);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
requestContext,
|
|
52
|
+
additionalUserContext,
|
|
53
|
+
setAdditionalUserContext: setNextAdditionalUserContext,
|
|
54
|
+
appendUserContext,
|
|
55
|
+
clearAdditionalUserContext,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@use "./styles/index.scss";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "./index.scss";
|
|
2
|
+
import ServiceInquiryForm from "./components/Form";
|
|
3
|
+
import ServiceInquiryOpenButton from "./components/OpenButton";
|
|
4
|
+
import {
|
|
5
|
+
useOpenServiceInquiry,
|
|
6
|
+
useProvideServiceInquiryContext,
|
|
7
|
+
useServiceInquiryUserContext,
|
|
8
|
+
} from "./hooks";
|
|
9
|
+
import { createServiceInquiryModal } from "./utils/modal-option";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Service Inquiry; 문의 form + modal option 엔트리
|
|
13
|
+
* - 변경 설명: Step 2에서 Form과 Modal.Dialog 기반 factory를 첫 runtime 조각으로 연다.
|
|
14
|
+
*/
|
|
15
|
+
export const ServiceInquiry = {
|
|
16
|
+
Form: ServiceInquiryForm,
|
|
17
|
+
OpenButton: ServiceInquiryOpenButton,
|
|
18
|
+
useOpen: useOpenServiceInquiry,
|
|
19
|
+
useProvideContext: useProvideServiceInquiryContext,
|
|
20
|
+
useUserContext: useServiceInquiryUserContext,
|
|
21
|
+
createModal: createServiceInquiryModal,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
ServiceInquiryForm,
|
|
26
|
+
ServiceInquiryOpenButton,
|
|
27
|
+
useOpenServiceInquiry,
|
|
28
|
+
useProvideServiceInquiryContext,
|
|
29
|
+
useServiceInquiryUserContext,
|
|
30
|
+
createServiceInquiryModal,
|
|
31
|
+
};
|
|
32
|
+
export type * from "./types";
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { atom } from "jotai";
|
|
4
|
+
import type {
|
|
5
|
+
ServiceInquiryRequestContext,
|
|
6
|
+
ServiceInquiryProvidedContext,
|
|
7
|
+
ServiceInquiryUserContext,
|
|
8
|
+
} from "../types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Service Inquiry State; 페이지별 등록 context registry
|
|
12
|
+
* @state
|
|
13
|
+
*/
|
|
14
|
+
export const serviceInquiryProvidedContextRegistryAtom = atom<
|
|
15
|
+
Record<string, ServiceInquiryProvidedContext>
|
|
16
|
+
>({});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Service Inquiry State; trigger/interaction 추가 user_context
|
|
20
|
+
* @state
|
|
21
|
+
*/
|
|
22
|
+
export const serviceInquiryAdditionalUserContextAtom =
|
|
23
|
+
atom<ServiceInquiryUserContext | null>(null);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Service Inquiry State; registry 병합 context
|
|
27
|
+
* @state
|
|
28
|
+
*/
|
|
29
|
+
export const serviceInquiryProvidedContextAtom =
|
|
30
|
+
atom<ServiceInquiryProvidedContext>(get => {
|
|
31
|
+
const providedContexts = Object.values(
|
|
32
|
+
get(serviceInquiryProvidedContextRegistryAtom),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
labels: Array.from(
|
|
37
|
+
new Set(
|
|
38
|
+
providedContexts.flatMap(
|
|
39
|
+
providedContext => providedContext.labels ?? [],
|
|
40
|
+
),
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
pagePath:
|
|
44
|
+
[...providedContexts].reverse().find(providedContext => {
|
|
45
|
+
return (
|
|
46
|
+
typeof providedContext.pagePath === "string" &&
|
|
47
|
+
providedContext.pagePath.length > 0
|
|
48
|
+
);
|
|
49
|
+
})?.pagePath ?? "",
|
|
50
|
+
userContext: providedContexts.reduce<ServiceInquiryUserContext | null>(
|
|
51
|
+
(currentUserContext, providedContext) => {
|
|
52
|
+
if (!providedContext.userContext) {
|
|
53
|
+
return currentUserContext;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
...(currentUserContext ?? {}),
|
|
58
|
+
...providedContext.userContext,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
null,
|
|
62
|
+
),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Service Inquiry State; 최종 request context
|
|
68
|
+
* @state
|
|
69
|
+
*/
|
|
70
|
+
export const serviceInquiryRequestContextAtom =
|
|
71
|
+
atom<ServiceInquiryRequestContext>(get => {
|
|
72
|
+
const providedContext = get(serviceInquiryProvidedContextAtom);
|
|
73
|
+
const additionalUserContext = get(serviceInquiryAdditionalUserContextAtom);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
labels: providedContext.labels ?? [],
|
|
77
|
+
page_path:
|
|
78
|
+
providedContext.pagePath ||
|
|
79
|
+
(typeof window === "undefined" ? "" : window.location.pathname),
|
|
80
|
+
user_context:
|
|
81
|
+
providedContext.userContext || additionalUserContext
|
|
82
|
+
? {
|
|
83
|
+
...(providedContext.userContext ?? {}),
|
|
84
|
+
...(additionalUserContext ?? {}),
|
|
85
|
+
}
|
|
86
|
+
: null,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./context";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.service-inquiry-form {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.service-inquiry-fields {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
gap: var(--spacing-padding-5);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.service-inquiry-field {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.service-inquiry-open-button {
|
|
2
|
+
width: 40px;
|
|
3
|
+
height: 40px;
|
|
4
|
+
border-radius: 20px;
|
|
5
|
+
border: 1px solid var(--color-border-standard-cool-gray);
|
|
6
|
+
background: var(--color_100);
|
|
7
|
+
color: var(--color-label-neutral);
|
|
8
|
+
font-size: 24px;
|
|
9
|
+
font-weight: 400;
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
padding: 0;
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ServiceInquiryUserContext } from "./form-context";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Service Inquiry API; 문의 등록 요청
|
|
5
|
+
* @property {string} text 문의 본문
|
|
6
|
+
* @property {string} farm_name 문의 대상 농장명 또는 빈 문자열
|
|
7
|
+
* @property {string[]} labels JIRA label 목록
|
|
8
|
+
* @property {string} contact 문의자 연락처(email 또는 phone number)
|
|
9
|
+
* @property {string} page_path 문의하기를 연 페이지 경로
|
|
10
|
+
* @property {ServiceInquiryUserContext | null} user_context 문의 시점 사용자/환경 맥락
|
|
11
|
+
*/
|
|
12
|
+
export interface API_Req_ServiceInquiry {
|
|
13
|
+
/**
|
|
14
|
+
* 문의 본문
|
|
15
|
+
*/
|
|
16
|
+
text: string;
|
|
17
|
+
/**
|
|
18
|
+
* 문의 대상 농장명 또는 빈 문자열
|
|
19
|
+
*/
|
|
20
|
+
farm_name: string;
|
|
21
|
+
/**
|
|
22
|
+
* JIRA label 목록
|
|
23
|
+
*/
|
|
24
|
+
labels: string[];
|
|
25
|
+
/**
|
|
26
|
+
* 문의자 연락처
|
|
27
|
+
*/
|
|
28
|
+
contact: string;
|
|
29
|
+
/**
|
|
30
|
+
* 문의하기를 연 페이지 경로
|
|
31
|
+
*/
|
|
32
|
+
page_path: string;
|
|
33
|
+
/**
|
|
34
|
+
* 문의 시점 사용자/환경 맥락
|
|
35
|
+
*/
|
|
36
|
+
user_context: ServiceInquiryUserContext | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Service Inquiry API; 문의 등록 응답
|
|
41
|
+
* @property {boolean} success 문의 등록 성공 여부
|
|
42
|
+
* @property {string} issue_key JIRA issue key
|
|
43
|
+
* @property {string} issue_url JIRA issue URL
|
|
44
|
+
* @property {string} summary JIRA issue 제목
|
|
45
|
+
* @property {string} message 서버 응답 메시지
|
|
46
|
+
*/
|
|
47
|
+
export interface API_Res_ServiceInquiry {
|
|
48
|
+
/**
|
|
49
|
+
* 문의 등록 성공 여부
|
|
50
|
+
*/
|
|
51
|
+
success: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* JIRA issue key
|
|
54
|
+
*/
|
|
55
|
+
issue_key: string;
|
|
56
|
+
/**
|
|
57
|
+
* JIRA issue URL
|
|
58
|
+
*/
|
|
59
|
+
issue_url: string;
|
|
60
|
+
/**
|
|
61
|
+
* JIRA issue 제목
|
|
62
|
+
*/
|
|
63
|
+
summary: string;
|
|
64
|
+
/**
|
|
65
|
+
* 서버 응답 메시지
|
|
66
|
+
*/
|
|
67
|
+
message: string;
|
|
68
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Inquiry Form; 사용자 입력값
|
|
3
|
+
* @property {string} text 문의 본문
|
|
4
|
+
* @property {string} farm_name 농장명 또는 빈 문자열
|
|
5
|
+
* @property {string} contact 문의자 연락처
|
|
6
|
+
*/
|
|
7
|
+
export interface ServiceInquiryFormValues {
|
|
8
|
+
/**
|
|
9
|
+
* 문의 본문
|
|
10
|
+
*/
|
|
11
|
+
text: string;
|
|
12
|
+
/**
|
|
13
|
+
* 농장명 또는 빈 문자열
|
|
14
|
+
*/
|
|
15
|
+
farm_name: string;
|
|
16
|
+
/**
|
|
17
|
+
* 문의자 연락처
|
|
18
|
+
*/
|
|
19
|
+
contact: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Service Inquiry Form; 시스템 주입 request context
|
|
24
|
+
* @property {string[]} labels JIRA label 목록
|
|
25
|
+
* @property {string} page_path 문의를 연 페이지 경로
|
|
26
|
+
* @property {ServiceInquiryUserContext | null} user_context 문의 시점 사용자/환경 맥락
|
|
27
|
+
*/
|
|
28
|
+
export interface ServiceInquiryRequestContext {
|
|
29
|
+
/**
|
|
30
|
+
* JIRA label 목록
|
|
31
|
+
*/
|
|
32
|
+
labels: string[];
|
|
33
|
+
/**
|
|
34
|
+
* 문의를 연 페이지 경로
|
|
35
|
+
*/
|
|
36
|
+
page_path: string;
|
|
37
|
+
/**
|
|
38
|
+
* 문의 시점 사용자/환경 맥락
|
|
39
|
+
*/
|
|
40
|
+
user_context: ServiceInquiryUserContext | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Service Inquiry Form; 사용자/환경 맥락 payload
|
|
45
|
+
* @property {unknown} [key] 디버깅을 위한 사용자 동작/환경 스냅샷
|
|
46
|
+
*/
|
|
47
|
+
// 변경 설명: user_context는 서비스별 arbitrary object 확장 지점이므로 Record 기반 의도를 타입 이름에서 바로 읽히게 맞춘다.
|
|
48
|
+
export interface ServiceInquiryUserContext extends Record<string, unknown> {}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ServiceInquiryRequestContext,
|
|
3
|
+
ServiceInquiryUserContext,
|
|
4
|
+
} from "./form-context";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Service Inquiry Hook; 모듈 내부 공유 context 값
|
|
8
|
+
* @property {string[]} [labels] 문의 request에 포함할 labels
|
|
9
|
+
* @property {string} [pagePath] 문의를 연 페이지 경로
|
|
10
|
+
* @property {ServiceInquiryUserContext | null} [userContext] 페이지/폼이 제공하는 추가 user_context
|
|
11
|
+
*/
|
|
12
|
+
export interface ServiceInquiryProvidedContext {
|
|
13
|
+
/**
|
|
14
|
+
* 문의 request에 포함할 labels
|
|
15
|
+
*/
|
|
16
|
+
labels?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* 문의를 연 페이지 경로
|
|
19
|
+
*/
|
|
20
|
+
pagePath?: string;
|
|
21
|
+
/**
|
|
22
|
+
* 페이지/폼이 제공하는 추가 user_context
|
|
23
|
+
*/
|
|
24
|
+
userContext?: ServiceInquiryUserContext | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Service Inquiry Hook; user_context 훅 반환값
|
|
29
|
+
* @property {ServiceInquiryRequestContext} requestContext form submit에 바로 사용할 request context
|
|
30
|
+
* @property {ServiceInquiryUserContext | null} additionalUserContext trigger/interaction에서 추가된 user_context
|
|
31
|
+
* @property {(nextUserContext: ServiceInquiryUserContext | null) => void} setAdditionalUserContext 추가 user_context 교체 함수
|
|
32
|
+
* @property {(nextUserContext?: ServiceInquiryUserContext | null) => void} appendUserContext 추가 user_context 병합 함수
|
|
33
|
+
* @property {() => void} clearAdditionalUserContext 추가 user_context 초기화 함수
|
|
34
|
+
*/
|
|
35
|
+
export interface UseServiceInquiryUserContextReturn {
|
|
36
|
+
/**
|
|
37
|
+
* form submit에 바로 사용할 request context
|
|
38
|
+
*/
|
|
39
|
+
requestContext: ServiceInquiryRequestContext;
|
|
40
|
+
/**
|
|
41
|
+
* trigger/interaction에서 추가된 user_context
|
|
42
|
+
*/
|
|
43
|
+
additionalUserContext: ServiceInquiryUserContext | null;
|
|
44
|
+
/**
|
|
45
|
+
* 추가 user_context 교체 함수
|
|
46
|
+
*/
|
|
47
|
+
setAdditionalUserContext: (
|
|
48
|
+
nextUserContext: ServiceInquiryUserContext | null,
|
|
49
|
+
) => void;
|
|
50
|
+
/**
|
|
51
|
+
* 추가 user_context 병합 함수
|
|
52
|
+
*/
|
|
53
|
+
appendUserContext: (
|
|
54
|
+
nextUserContext?: ServiceInquiryUserContext | null,
|
|
55
|
+
) => void;
|
|
56
|
+
/**
|
|
57
|
+
* 추가 user_context 초기화 함수
|
|
58
|
+
*/
|
|
59
|
+
clearAdditionalUserContext: () => void;
|
|
60
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { SubmitHandler } from "react-hook-form";
|
|
3
|
+
import type { DialogTemplateOptions } from "../../modal/types";
|
|
4
|
+
import type { ServiceInquiryFormValues } from "./form-context";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Service Inquiry; 노출 필드 키
|
|
8
|
+
*/
|
|
9
|
+
export type ServiceInquiryFieldKey = "farm_name" | "contact" | "text";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Service Inquiry Form; field 입력 모드
|
|
13
|
+
* @typedef {"editable" | "readonly"} ServiceInquiryFieldMode
|
|
14
|
+
*/
|
|
15
|
+
export type ServiceInquiryFieldMode = "editable" | "readonly";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Service Inquiry Form; 공통 field 설정
|
|
19
|
+
* @property {ReactNode} [label] field label
|
|
20
|
+
* @property {ReactNode} [helper] field helper
|
|
21
|
+
* @property {boolean} [required] required 표시 여부
|
|
22
|
+
*/
|
|
23
|
+
export interface ServiceInquiryFieldBaseProps {
|
|
24
|
+
/**
|
|
25
|
+
* field label
|
|
26
|
+
*/
|
|
27
|
+
label?: ReactNode;
|
|
28
|
+
/**
|
|
29
|
+
* field helper
|
|
30
|
+
*/
|
|
31
|
+
helper?: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* required 표시 여부
|
|
34
|
+
*/
|
|
35
|
+
required?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Service Inquiry Form; 단일 line input field 설정
|
|
40
|
+
* @property {"editable" | "readonly"} [mode] 서비스가 제어하는 field 입력 모드
|
|
41
|
+
* @property {string} [placeholder] input placeholder
|
|
42
|
+
*/
|
|
43
|
+
export interface ServiceInquiryInputFieldProps extends ServiceInquiryFieldBaseProps {
|
|
44
|
+
/**
|
|
45
|
+
* 서비스가 제어하는 field 입력 모드
|
|
46
|
+
*/
|
|
47
|
+
mode?: ServiceInquiryFieldMode;
|
|
48
|
+
/**
|
|
49
|
+
* input placeholder
|
|
50
|
+
*/
|
|
51
|
+
placeholder?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Service Inquiry Form; textarea field 설정
|
|
56
|
+
* @property {string} [placeholder] textarea placeholder
|
|
57
|
+
*/
|
|
58
|
+
export interface ServiceInquiryTextAreaFieldProps extends ServiceInquiryFieldBaseProps {
|
|
59
|
+
/**
|
|
60
|
+
* textarea placeholder
|
|
61
|
+
*/
|
|
62
|
+
placeholder?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Service Inquiry Form; 문의 입력 폼 props
|
|
67
|
+
* @property {string} [className] form className
|
|
68
|
+
* @property {ServiceInquiryFieldKey[]} [visibleFields] 노출 필드 목록
|
|
69
|
+
* @property {ServiceInquiryInputFieldProps} [farmNameField] 농장명 필드 설정
|
|
70
|
+
* @property {ServiceInquiryInputFieldProps} [contactField] 연락처 필드 설정
|
|
71
|
+
* @property {ServiceInquiryTextAreaFieldProps} [textField] 문의 본문 필드 설정
|
|
72
|
+
* @property {SubmitHandler<ServiceInquiryFormValues>} props.onSubmit 문의 제출 핸들러
|
|
73
|
+
*/
|
|
74
|
+
export interface ServiceInquiryFormProps {
|
|
75
|
+
/**
|
|
76
|
+
* form className
|
|
77
|
+
*/
|
|
78
|
+
className?: string;
|
|
79
|
+
/**
|
|
80
|
+
* 노출 필드 목록
|
|
81
|
+
*/
|
|
82
|
+
visibleFields?: ServiceInquiryFieldKey[];
|
|
83
|
+
/**
|
|
84
|
+
* 농장명 필드 설정
|
|
85
|
+
*/
|
|
86
|
+
farmNameField?: ServiceInquiryInputFieldProps;
|
|
87
|
+
/**
|
|
88
|
+
* 연락처 필드 설정
|
|
89
|
+
*/
|
|
90
|
+
contactField?: ServiceInquiryInputFieldProps;
|
|
91
|
+
/**
|
|
92
|
+
* 문의 본문 필드 설정
|
|
93
|
+
*/
|
|
94
|
+
textField?: ServiceInquiryTextAreaFieldProps;
|
|
95
|
+
/**
|
|
96
|
+
* 문의 제출 핸들러
|
|
97
|
+
*/
|
|
98
|
+
onSubmit: SubmitHandler<ServiceInquiryFormValues>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Service Inquiry Modal; 문의 모달 preset 생성 옵션
|
|
103
|
+
* @property {string} stackKey modal stack key
|
|
104
|
+
* @property {ServiceInquiryFormProps} formProps 문의 form props
|
|
105
|
+
* @property {Partial<DialogTemplateOptions<ServiceInquiryFormValues>>} [dialogOptions] 문의 모달 preset 위에 덮어쓸 dialog option
|
|
106
|
+
*/
|
|
107
|
+
// 변경 설명: DialogTemplateOptions를 거의 다시 적는 wrapper 대신, 모듈 고유 필드와 dialog override bag만 가진 preset 입력 타입으로 줄인다.
|
|
108
|
+
export interface ServiceInquiryCreateModalOptions {
|
|
109
|
+
/**
|
|
110
|
+
* modal stack key
|
|
111
|
+
*/
|
|
112
|
+
stackKey: string;
|
|
113
|
+
/**
|
|
114
|
+
* 문의 form props
|
|
115
|
+
*/
|
|
116
|
+
formProps: ServiceInquiryFormProps;
|
|
117
|
+
/**
|
|
118
|
+
* 문의 모달 preset 위에 덮어쓸 dialog option
|
|
119
|
+
*/
|
|
120
|
+
dialogOptions?: Partial<DialogTemplateOptions<ServiceInquiryFormValues>>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Service Inquiry Hook; 문의 모달 열기 훅 옵션
|
|
125
|
+
* @property {string} stackKey modal stack key
|
|
126
|
+
* @property {ServiceInquiryFormProps} formProps 문의 form props
|
|
127
|
+
* @property {Partial<DialogTemplateOptions<ServiceInquiryFormValues>>} [dialogOptions] 문의 모달 preset 위에 덮어쓸 dialog option
|
|
128
|
+
* @property {() => void} [onOpen] 모달 open 직전 콜백
|
|
129
|
+
*/
|
|
130
|
+
export interface UseOpenServiceInquiryOptions extends ServiceInquiryCreateModalOptions {
|
|
131
|
+
/**
|
|
132
|
+
* 모달 open 직전 콜백
|
|
133
|
+
*/
|
|
134
|
+
onOpen?: () => void;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Service Inquiry Hook; 문의 모달 열기 훅 반환값
|
|
139
|
+
* @property {() => void} openServiceInquiry 문의 모달 open 함수
|
|
140
|
+
*/
|
|
141
|
+
export interface UseOpenServiceInquiryReturn {
|
|
142
|
+
/**
|
|
143
|
+
* 문의 모달 open 함수
|
|
144
|
+
*/
|
|
145
|
+
openServiceInquiry: () => void;
|
|
146
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createDialogModal } from "../../modal/components/dialog/Template";
|
|
2
|
+
import ServiceInquiryForm from "../components/Form";
|
|
3
|
+
import type {
|
|
4
|
+
ServiceInquiryCreateModalOptions,
|
|
5
|
+
ServiceInquiryFormValues,
|
|
6
|
+
} from "../types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Service Inquiry Modal; 문의 form이 포함된 Dialog option 생성기
|
|
10
|
+
* @component
|
|
11
|
+
* @param {ServiceInquiryCreateModalOptions} options 문의 모달 preset 생성 옵션
|
|
12
|
+
* @param {string} options.stackKey modal stack key
|
|
13
|
+
* @param {ServiceInquiryFormProps} options.formProps 문의 form props
|
|
14
|
+
* @param {Partial<DialogTemplateOptions<ServiceInquiryFormValues>>} [options.dialogOptions] 문의 모달 preset 위에 덮어쓸 dialog option
|
|
15
|
+
* @example
|
|
16
|
+
* createServiceInquiryModal({
|
|
17
|
+
* stackKey: "service-inquiry",
|
|
18
|
+
* formProps: { onSubmit: values => console.info(values) },
|
|
19
|
+
* })
|
|
20
|
+
*/
|
|
21
|
+
export function createServiceInquiryModal({
|
|
22
|
+
stackKey,
|
|
23
|
+
formProps,
|
|
24
|
+
dialogOptions,
|
|
25
|
+
}: ServiceInquiryCreateModalOptions) {
|
|
26
|
+
return createDialogModal<ServiceInquiryFormValues>({
|
|
27
|
+
...dialogOptions,
|
|
28
|
+
stackKey,
|
|
29
|
+
title: dialogOptions?.title ?? "문의하기",
|
|
30
|
+
// 변경 설명: 문의 모달은 title과 안내 문구를 좌측 정렬로 읽히게 하기 위해 split header를 기본값으로 사용한다.
|
|
31
|
+
headerLayout: dialogOptions?.headerLayout ?? "split",
|
|
32
|
+
content: <ServiceInquiryForm {...formProps} />,
|
|
33
|
+
confirm: {
|
|
34
|
+
label: "문의 접수",
|
|
35
|
+
...dialogOptions?.confirm,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|