@uniai-fe/uds-templates 0.4.6 → 0.4.8

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 CHANGED
@@ -107,7 +107,7 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
107
107
  - ui-legacy 스택 기반 모달 Provider/Root/Container + 템플릿(`Modal.Alert`, `Modal.Dialog`)
108
108
  - Storybook(`apps/design-storybook/src/stories/templates/modal`)에서 Alert/Confirm 케이스를 검증한다.
109
109
  - `/service-inquiry/**`
110
- - 문의 입력 전용 form, 기본 원형 `?` open button, 커스텀 trigger용 open hook, 페이지 context 등록 hook, request context 조립 hook, modal preset factory를 제공한다.
110
+ - 문의 입력 전용 form, 기본 원형 `?` open button, 커스텀 trigger용 open hook, 페이지 context 등록 hook, request context 조립 hook, 네트워크 오류 수집 hook, modal preset factory를 제공한다.
111
111
  - submit transport, React Query mutation, Next.js route handler, 에러 피드백(`Modal.Alert`)은 서비스 앱이 소유한다.
112
112
  - 모듈 내부 Jotai registry를 사용하므로, layout 고정 버튼 1개와 페이지별 context 등록 hook 조합으로 ready-to-use 구성이 가능하다.
113
113
  - 로그인 후 `farm_name`, `contact`를 auto-fill + readonly로 보여야 할 때는 `formContextOptions.defaultValues`와 `farmNameField.mode`, `contactField.mode`를 함께 전달한다.
@@ -124,10 +124,11 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
124
124
 
125
125
  1. 서비스 앱이 `react-hook-form`으로 `defaultValues`와 `onSubmit`을 준비한다.
126
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` 조합으로 처리한다.
127
+ 3. 서비스 앱은 네트워크 오류가 발생했을 때 `ServiceInquiry.useNetworkError().reportNetworkError(...)`만 호출하고, 모듈이 이를 `user_context.network_errors`에 자동 병합한다.
128
+ 4. 페이지/폼은 `ServiceInquiry.useProvideContext({ labels, userContext })`로 `ServiceInquiryProvidedContext`를 등록한다.
129
+ 5. 기본 버튼은 `ServiceInquiry.OpenButton`, 커스텀 버튼은 `ServiceInquiry.useOpen`으로 모달 open을 연결한다.
130
+ 6. modal footer confirm이 `ServiceInquiry.Form` submit 진입을 담당한다.
131
+ 7. 실제 submit은 서비스 앱의 `useMutation + Next.js route handler + Modal.Alert` 조합으로 처리한다.
131
132
 
132
133
  `service-inquiry`는 구조와 request context까지만 제공하고, 네트워크 상태/재시도/성공·실패 피드백은 서비스 앱이 소유합니다.
133
134
 
package/dist/styles.css CHANGED
@@ -1764,5 +1764,5 @@
1764
1764
  left: var(--service-inquiry-button-pos-left);
1765
1765
  right: var(--service-inquiry-button-pos-right);
1766
1766
  bottom: var(--service-inquiry-button-pos-bottom);
1767
- z-index: 100;
1767
+ z-index: 700;
1768
1768
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-templates",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "UNIAI Design System; UI Templates Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -2,12 +2,14 @@
2
2
 
3
3
  import { Button } from "@uniai-fe/uds-primitives";
4
4
  import { useOpenServiceInquiry } from "../hooks";
5
- import type { UseOpenServiceInquiryOptions } from "../types";
5
+ import type { ServiceInquiryOpenButtonProps } from "../types";
6
+ import clsx from "clsx";
6
7
 
7
8
  /**
8
9
  * Service Inquiry Open Button; 문의 모달 trigger adapter
9
10
  * @component
10
11
  * @param {UseOpenServiceInquiryOptions} props 문의 모달 열기 props
12
+ * @param {string} [props.className]
11
13
  * @param {string} props.stackKey modal stack key
12
14
  * @param {ServiceInquiryFormProps} props.formProps 문의 form props
13
15
  * @param {Partial<DialogTemplateOptions<ServiceInquiryFormValues>>} [props.dialogOptions] 문의 모달 preset 위에 덮어쓸 dialog option
@@ -19,11 +21,12 @@ import type { UseOpenServiceInquiryOptions } from "../types";
19
21
  * />
20
22
  */
21
23
  const ServiceInquiryOpenButton = ({
24
+ className,
22
25
  stackKey,
23
26
  formProps,
24
27
  dialogOptions,
25
28
  onOpen,
26
- }: UseOpenServiceInquiryOptions) => {
29
+ }: ServiceInquiryOpenButtonProps) => {
27
30
  const { openServiceInquiry } = useOpenServiceInquiry({
28
31
  stackKey,
29
32
  formProps,
@@ -33,7 +36,7 @@ const ServiceInquiryOpenButton = ({
33
36
 
34
37
  return (
35
38
  <Button.Rounded
36
- className="service-inquiry-open-button"
39
+ className={clsx("service-inquiry-open-button", className)}
37
40
  priority="tertiary"
38
41
  size="large"
39
42
  // 변경 설명: 기본 제공 버튼은 고정 원형 `?` 버튼 사양으로 렌더링한다.
@@ -1,3 +1,4 @@
1
1
  export * from "./useOpen";
2
+ export * from "./useNetworkError";
2
3
  export * from "./useProvideContext";
3
4
  export * from "./useUserContext";
@@ -0,0 +1,46 @@
1
+ "use client";
2
+
3
+ import { useAtom } from "jotai";
4
+ import { serviceInquiryNetworkErrorsAtom } from "../jotai";
5
+ import type {
6
+ ServiceInquiryNetworkError,
7
+ UseServiceInquiryNetworkErrorReturn,
8
+ } from "../types";
9
+
10
+ /**
11
+ * Service Inquiry Hook; 네트워크 오류 수집 Hook
12
+ * @hook
13
+ * @returns {UseServiceInquiryNetworkErrorReturn} 최근 오류 목록과 기록/초기화 함수
14
+ * @example
15
+ * const { reportNetworkError } = useServiceInquiryNetworkError();
16
+ * reportNetworkError({ route: "/api/v2/login", message: "timeout" });
17
+ */
18
+ export function useServiceInquiryNetworkError(): UseServiceInquiryNetworkErrorReturn {
19
+ const [networkErrors, setNetworkErrors] = useAtom(
20
+ serviceInquiryNetworkErrorsAtom,
21
+ );
22
+
23
+ const reportNetworkError: UseServiceInquiryNetworkErrorReturn["reportNetworkError"] =
24
+ (nextError: ServiceInquiryNetworkError) => {
25
+ setNetworkErrors(currentErrors =>
26
+ [
27
+ {
28
+ ...nextError,
29
+ timestamp: nextError.timestamp ?? new Date().toISOString(),
30
+ },
31
+ ...currentErrors,
32
+ ].slice(0, 5),
33
+ );
34
+ };
35
+
36
+ const clearNetworkErrors: UseServiceInquiryNetworkErrorReturn["clearNetworkErrors"] =
37
+ () => {
38
+ setNetworkErrors([]);
39
+ };
40
+
41
+ return {
42
+ networkErrors,
43
+ reportNetworkError,
44
+ clearNetworkErrors,
45
+ };
46
+ }
@@ -2,6 +2,7 @@ import "./index.scss";
2
2
  import ServiceInquiryForm from "./components/Form";
3
3
  import ServiceInquiryOpenButton from "./components/OpenButton";
4
4
  import {
5
+ useServiceInquiryNetworkError,
5
6
  useOpenServiceInquiry,
6
7
  useProvideServiceInquiryContext,
7
8
  useServiceInquiryUserContext,
@@ -16,6 +17,7 @@ export const ServiceInquiry = {
16
17
  Form: ServiceInquiryForm,
17
18
  OpenButton: ServiceInquiryOpenButton,
18
19
  useOpen: useOpenServiceInquiry,
20
+ useNetworkError: useServiceInquiryNetworkError,
19
21
  useProvideContext: useProvideServiceInquiryContext,
20
22
  useUserContext: useServiceInquiryUserContext,
21
23
  createModal: createServiceInquiryModal,
@@ -24,6 +26,7 @@ export const ServiceInquiry = {
24
26
  export {
25
27
  ServiceInquiryForm,
26
28
  ServiceInquiryOpenButton,
29
+ useServiceInquiryNetworkError,
27
30
  useOpenServiceInquiry,
28
31
  useProvideServiceInquiryContext,
29
32
  useServiceInquiryUserContext,
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { atom } from "jotai";
4
4
  import type {
5
+ ServiceInquiryNetworkError,
5
6
  ServiceInquiryRequestContext,
6
7
  ServiceInquiryProvidedContext,
7
8
  ServiceInquiryUserContext,
@@ -22,6 +23,14 @@ export const serviceInquiryProvidedContextRegistryAtom = atom<
22
23
  export const serviceInquiryAdditionalUserContextAtom =
23
24
  atom<ServiceInquiryUserContext | null>(null);
24
25
 
26
+ /**
27
+ * Service Inquiry State; 최근 네트워크 오류 목록
28
+ * @state
29
+ */
30
+ export const serviceInquiryNetworkErrorsAtom = atom<
31
+ ServiceInquiryNetworkError[]
32
+ >([]);
33
+
25
34
  /**
26
35
  * Service Inquiry State; registry 병합 context
27
36
  * @state
@@ -40,13 +49,6 @@ export const serviceInquiryProvidedContextAtom =
40
49
  ),
41
50
  ),
42
51
  ),
43
- pagePath:
44
- [...providedContexts].reverse().find(providedContext => {
45
- return (
46
- typeof providedContext.pagePath === "string" &&
47
- providedContext.pagePath.length > 0
48
- );
49
- })?.pagePath ?? "",
50
52
  userContext: providedContexts.reduce<ServiceInquiryUserContext | null>(
51
53
  (currentUserContext, providedContext) => {
52
54
  if (!providedContext.userContext) {
@@ -71,17 +73,22 @@ export const serviceInquiryRequestContextAtom =
71
73
  atom<ServiceInquiryRequestContext>(get => {
72
74
  const providedContext = get(serviceInquiryProvidedContextAtom);
73
75
  const additionalUserContext = get(serviceInquiryAdditionalUserContextAtom);
76
+ const networkErrors = get(serviceInquiryNetworkErrorsAtom);
74
77
 
75
78
  return {
76
79
  labels: providedContext.labels ?? [],
77
- page_path:
78
- providedContext.pagePath ||
79
- (typeof window === "undefined" ? "" : window.location.pathname),
80
+ // 변경 설명: page_path는 서비스 입력값이 아니라 모듈이 현재 페이지 URL 전체를 자동 수집하는 시스템 필드로 고정한다.
81
+ page_path: typeof window === "undefined" ? "" : window.location.href,
80
82
  user_context:
81
- providedContext.userContext || additionalUserContext
83
+ providedContext.userContext ||
84
+ additionalUserContext ||
85
+ networkErrors.length > 0
82
86
  ? {
83
87
  ...(providedContext.userContext ?? {}),
84
88
  ...(additionalUserContext ?? {}),
89
+ ...(networkErrors.length > 0
90
+ ? { network_errors: networkErrors }
91
+ : {}),
85
92
  }
86
93
  : null,
87
94
  };
@@ -7,5 +7,5 @@
7
7
  left: var(--service-inquiry-button-pos-left);
8
8
  right: var(--service-inquiry-button-pos-right);
9
9
  bottom: var(--service-inquiry-button-pos-bottom);
10
- z-index: 100;
10
+ z-index: 700;
11
11
  }
@@ -6,7 +6,7 @@ import type { ServiceInquiryUserContext } from "./form-context";
6
6
  * @property {string} farm_name 문의 대상 농장명 또는 빈 문자열
7
7
  * @property {string[]} labels JIRA label 목록
8
8
  * @property {string} contact 문의자 연락처(email 또는 phone number)
9
- * @property {string} page_path 문의하기를 연 페이지 경로
9
+ * @property {string} page_path 문의하기를 연 현재 페이지 URL
10
10
  * @property {ServiceInquiryUserContext | null} user_context 문의 시점 사용자/환경 맥락
11
11
  */
12
12
  export interface API_Req_ServiceInquiry {
@@ -27,7 +27,7 @@ export interface API_Req_ServiceInquiry {
27
27
  */
28
28
  contact: string;
29
29
  /**
30
- * 문의하기를 연 페이지 경로
30
+ * 문의하기를 연 현재 페이지 URL
31
31
  */
32
32
  page_path: string;
33
33
  /**
@@ -22,7 +22,7 @@ export interface ServiceInquiryFormValues {
22
22
  /**
23
23
  * Service Inquiry Form; 시스템 주입 request context
24
24
  * @property {string[]} labels JIRA label 목록
25
- * @property {string} page_path 문의를 연 페이지 경로
25
+ * @property {string} page_path 문의를 연 현재 페이지 URL
26
26
  * @property {ServiceInquiryUserContext | null} user_context 문의 시점 사용자/환경 맥락
27
27
  */
28
28
  export interface ServiceInquiryRequestContext {
@@ -31,7 +31,7 @@ export interface ServiceInquiryRequestContext {
31
31
  */
32
32
  labels: string[];
33
33
  /**
34
- * 문의를 연 페이지 경로
34
+ * 문의를 연 현재 페이지 URL
35
35
  */
36
36
  page_path: string;
37
37
  /**
@@ -6,7 +6,6 @@ import type {
6
6
  /**
7
7
  * Service Inquiry Hook; 모듈 내부 공유 context 값
8
8
  * @property {string[]} [labels] 문의 request에 포함할 labels
9
- * @property {string} [pagePath] 문의를 연 페이지 경로
10
9
  * @property {ServiceInquiryUserContext | null} [userContext] 페이지/폼이 제공하는 추가 user_context
11
10
  */
12
11
  export interface ServiceInquiryProvidedContext {
@@ -14,16 +13,64 @@ export interface ServiceInquiryProvidedContext {
14
13
  * 문의 request에 포함할 labels
15
14
  */
16
15
  labels?: string[];
17
- /**
18
- * 문의를 연 페이지 경로
19
- */
20
- pagePath?: string;
21
16
  /**
22
17
  * 페이지/폼이 제공하는 추가 user_context
23
18
  */
24
19
  userContext?: ServiceInquiryUserContext | null;
25
20
  }
26
21
 
22
+ /**
23
+ * Service Inquiry Hook; 네트워크 오류 기록
24
+ * @property {string} [route] 요청 route 또는 endpoint
25
+ * @property {number} [code] HTTP status code
26
+ * @property {string} [state] 서비스 레이어 상태 문자열
27
+ * @property {string} [message] 사용자/운영 확인용 오류 메시지
28
+ * @property {string} [timestamp] 오류 기록 시각
29
+ */
30
+ export interface ServiceInquiryNetworkError {
31
+ /**
32
+ * 요청 route 또는 endpoint
33
+ */
34
+ route?: string;
35
+ /**
36
+ * HTTP status code
37
+ */
38
+ code?: number;
39
+ /**
40
+ * 서비스 레이어 상태 문자열
41
+ */
42
+ state?: string;
43
+ /**
44
+ * 사용자/운영 확인용 오류 메시지
45
+ */
46
+ message?: string;
47
+ /**
48
+ * 오류 기록 시각
49
+ */
50
+ timestamp?: string;
51
+ }
52
+
53
+ /**
54
+ * Service Inquiry Hook; 네트워크 오류 수집 훅 반환값
55
+ * @property {ServiceInquiryNetworkError[]} networkErrors 누적된 최근 네트워크 오류 목록
56
+ * @property {(nextError: ServiceInquiryNetworkError) => void} reportNetworkError 네트워크 오류 1건 기록
57
+ * @property {() => void} clearNetworkErrors 누적된 네트워크 오류 초기화
58
+ */
59
+ export interface UseServiceInquiryNetworkErrorReturn {
60
+ /**
61
+ * 누적된 최근 네트워크 오류 목록
62
+ */
63
+ networkErrors: ServiceInquiryNetworkError[];
64
+ /**
65
+ * 네트워크 오류 1건 기록
66
+ */
67
+ reportNetworkError: (nextError: ServiceInquiryNetworkError) => void;
68
+ /**
69
+ * 누적된 네트워크 오류 초기화
70
+ */
71
+ clearNetworkErrors: () => void;
72
+ }
73
+
27
74
  /**
28
75
  * Service Inquiry Hook; user_context 훅 반환값
29
76
  * @property {ServiceInquiryRequestContext} requestContext form submit에 바로 사용할 request context
@@ -134,6 +134,18 @@ export interface UseOpenServiceInquiryOptions extends ServiceInquiryCreateModalO
134
134
  onOpen?: () => void;
135
135
  }
136
136
 
137
+ /**
138
+ * Service Inquiry Button props; 버튼 옵션
139
+ * @property {string} [className]
140
+ * @property {string} stackKey modal stack key
141
+ * @property {ServiceInquiryFormProps} formProps 문의 form props
142
+ * @property {Partial<DialogTemplateOptions<ServiceInquiryFormValues>>} [dialogOptions] 문의 모달 preset 위에 덮어쓸 dialog option
143
+ * @property {() => void} [onOpen] 모달 open 직전 콜백
144
+ */
145
+ export interface ServiceInquiryOpenButtonProps extends UseOpenServiceInquiryOptions {
146
+ className?: string;
147
+ }
148
+
137
149
  /**
138
150
  * Service Inquiry Hook; 문의 모달 열기 훅 반환값
139
151
  * @property {() => void} openServiceInquiry 문의 모달 open 함수