@yeongseoksong/framework 1.6.1 → 1.7.0
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 +63 -1
- package/dist/platform/index.d.mts +40 -0
- package/dist/platform/index.mjs +30 -0
- package/dist/ui/index.d.mts +16 -7
- package/dist/ui/index.mjs +140 -143
- package/dist/util/index.d.mts +50 -1
- package/dist/util/index.mjs +68 -0
- package/package.json +14 -33
- package/dist/store/index.cjs +0 -393
- package/dist/store/index.d.ts +0 -237
- package/dist/types/index.cjs +0 -18
- package/dist/types/index.d.ts +0 -105
- package/dist/ui/index.cjs +0 -3141
- package/dist/ui/index.d.ts +0 -1338
- package/dist/util/index.cjs +0 -199
- package/dist/util/index.d.ts +0 -114
package/dist/store/index.d.ts
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import * as zustand_middleware from 'zustand/middleware';
|
|
2
|
-
import * as zustand from 'zustand';
|
|
3
|
-
import { ReactNode, FormEvent } from 'react';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 로그인한 사용자. 소비자 앱의 사용자 모델이 더 넓으면 `login()`에 넘기기 전에
|
|
7
|
-
* 이 형태로 좁혀서 넣는다 — 스토어는 화면이 필요로 하는 최소 정보만 갖는다.
|
|
8
|
-
*/
|
|
9
|
-
interface AuthUser {
|
|
10
|
-
id: string;
|
|
11
|
-
email: string;
|
|
12
|
-
name?: string;
|
|
13
|
-
roles?: string[];
|
|
14
|
-
}
|
|
15
|
-
interface AuthState {
|
|
16
|
-
user: AuthUser | null;
|
|
17
|
-
/** user의 파생값이지만, 셀렉터를 `(s) => s.isAuthenticated` 한 줄로 유지하려고 상태로 둔다. */
|
|
18
|
-
isAuthenticated: boolean;
|
|
19
|
-
/** localStorage 복원이 끝났는지. 정적 프리렌더 대비용 — 아래 주석 참고. */
|
|
20
|
-
hasHydrated: boolean;
|
|
21
|
-
login: (user: AuthUser) => void;
|
|
22
|
-
logout: () => void;
|
|
23
|
-
setHasHydrated: (value: boolean) => void;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* 인증 세션.
|
|
27
|
-
*
|
|
28
|
-
* **액세스 토큰을 여기 넣지 말 것.** `partialize`가 사용자 프로필만 저장하도록
|
|
29
|
-
* 막아 두었다. 토큰은 httpOnly 쿠키로 두는 게 소비자 앱의 몫이다 — localStorage에
|
|
30
|
-
* 담으면 XSS 한 번에 그대로 털린다.
|
|
31
|
-
*
|
|
32
|
-
* `skipHydration`을 켠 이유: 자동 복원은 모듈 평가 시점에 일어나서 **첫 클라이언트
|
|
33
|
-
* 렌더가 이미 로그인 상태**가 된다. 반면 서버가 만든 HTML에는 항상 로그아웃 상태가
|
|
34
|
-
* 박혀 나가므로(`output: "export"`면 빌드 시점에 고정) 하이드레이션 불일치가 난다.
|
|
35
|
-
* 복원을 이펙트로 미루면 첫 렌더가 서버와 일치하고, 그 다음 틱에 세션이 반영된다.
|
|
36
|
-
* 복원을 트리거하고 완료 여부를 돌려주는 게 아래 `useAuthHydrated()`다.
|
|
37
|
-
*/
|
|
38
|
-
declare const useAuthStore: zustand.UseBoundStore<Omit<zustand.StoreApi<AuthState>, "setState" | "persist"> & {
|
|
39
|
-
setState(partial: AuthState | Partial<AuthState> | ((state: AuthState) => AuthState | Partial<AuthState>), replace?: false | undefined): unknown;
|
|
40
|
-
setState(state: AuthState | ((state: AuthState) => AuthState), replace: true): unknown;
|
|
41
|
-
persist: {
|
|
42
|
-
setOptions: (options: Partial<zustand_middleware.PersistOptions<AuthState, unknown, unknown>>) => void;
|
|
43
|
-
clearStorage: () => void;
|
|
44
|
-
rehydrate: () => Promise<void> | void;
|
|
45
|
-
hasHydrated: () => boolean;
|
|
46
|
-
onHydrate: (fn: (state: AuthState) => void) => () => void;
|
|
47
|
-
onFinishHydration: (fn: (state: AuthState) => void) => () => void;
|
|
48
|
-
getOptions: () => Partial<zustand_middleware.PersistOptions<AuthState, unknown, unknown>>;
|
|
49
|
-
};
|
|
50
|
-
}>;
|
|
51
|
-
/**
|
|
52
|
-
* 저장된 세션을 복원하고, 복원이 끝났는지를 돌려준다.
|
|
53
|
-
*
|
|
54
|
-
* 로그인 여부에 따라 다르게 그리는 UI는 이 값이 `false`인 동안 **로그아웃 상태로**
|
|
55
|
-
* 그려야 서버 HTML과 어긋나지 않는다.
|
|
56
|
-
*
|
|
57
|
-
* ```tsx
|
|
58
|
-
* const hydrated = useAuthHydrated()
|
|
59
|
-
* const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
|
|
60
|
-
* if (hydrated && isAuthenticated) { ... }
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
|
-
declare function useAuthHydrated(): boolean;
|
|
64
|
-
|
|
65
|
-
interface UiState {
|
|
66
|
-
/** 전역 로딩 오버레이 — 라우트 전환·일괄 작업처럼 화면 전체를 막아야 할 때 */
|
|
67
|
-
globalLoading: boolean;
|
|
68
|
-
setGlobalLoading: (value: boolean) => void;
|
|
69
|
-
/** 관리자형 레이아웃의 사이드 내비 열림 상태 */
|
|
70
|
-
sideNavOpened: boolean;
|
|
71
|
-
openSideNav: () => void;
|
|
72
|
-
closeSideNav: () => void;
|
|
73
|
-
toggleSideNav: () => void;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* 화면 간 공유가 필요한 클라이언트 UI 상태.
|
|
77
|
-
*
|
|
78
|
-
* `SdHeader`의 모바일 드로어는 **일부러 여기 두지 않는다.** 그건 인스턴스 로컬
|
|
79
|
-
* 상태(`useDisclosure`)여야 한다 — 카탈로그처럼 한 페이지에 헤더가 둘 이상 렌더되면
|
|
80
|
-
* 스토어를 공유하는 순간 드로어가 함께 열린다.
|
|
81
|
-
*/
|
|
82
|
-
declare const useUiStore: zustand.UseBoundStore<zustand.StoreApi<UiState>>;
|
|
83
|
-
|
|
84
|
-
interface CommonInfo {
|
|
85
|
-
id: number;
|
|
86
|
-
order: number;
|
|
87
|
-
isShow: boolean;
|
|
88
|
-
createdAt?: Date;
|
|
89
|
-
updatedAt?: Date;
|
|
90
|
-
}
|
|
91
|
-
interface NavItem extends CommonInfo {
|
|
92
|
-
label: string;
|
|
93
|
-
href?: string;
|
|
94
|
-
highlight?: boolean;
|
|
95
|
-
parentId?: number;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
interface NavState {
|
|
99
|
-
/** 앱 전역 네비게이션 트리. 소비자가 앱 진입 시 `setNavItems`로 한 번 주입한다. */
|
|
100
|
-
navItems: NavItem[];
|
|
101
|
-
setNavItems: (navItems: NavItem[]) => void;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* 네비게이션 상태 스토어 — 렌더 트리 **밖에서** navItems에 접근해야 할 때 쓴다
|
|
105
|
-
* (프로그램적 네비게이션, 라우팅 로직 등). 소비자가 `setNavItems`로 채운다.
|
|
106
|
-
*
|
|
107
|
-
* 트리 **안** 컴포넌트(`SdBreadcrumb`)는 이 스토어가 아니라 `ui/template/NavProvider`의
|
|
108
|
-
* React Context(`useNav`)에서 navItems를 읽는다 — `ui` 번들이 스토어를 import하면
|
|
109
|
-
* `dist/ui`에 인라인돼 `create()`가 두 번 도는 dual-bundle 문제가 생기기 때문이다
|
|
110
|
-
* (store/index.ts 상단 주석 참고). Context는 모듈 상태가 없어 그 문제에서 자유롭다.
|
|
111
|
-
*/
|
|
112
|
-
declare const useNavStore: zustand.UseBoundStore<zustand.StoreApi<NavState>>;
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* 어떤 작업이 끝난 뒤 항상 돌려야 하는 뒷정리 — 라우팅, 모달 닫기, 목록 새로고침 등.
|
|
116
|
-
*
|
|
117
|
-
* 폼에 딸린 개념이 아니다. 제출·삭제·업로드처럼 "끝나면 정리한다"가 필요한 곳이면
|
|
118
|
-
* 어느 스토어에서든 같은 타입과 실행기를 쓴다.
|
|
119
|
-
*/
|
|
120
|
-
/**
|
|
121
|
-
* 뒷정리 함수. **인자를 받지 않는다** — 대개 결과와 무관한 정리·이동이라
|
|
122
|
-
* 기존 함수(`close`, `refetch`, `() => router.push('/')`)를 그대로 꽂을 수 있어야 한다.
|
|
123
|
-
* 반환값은 무시하고, 프로미스면 기다린다.
|
|
124
|
-
*/
|
|
125
|
-
type Finalizer = () => unknown | Promise<unknown>;
|
|
126
|
-
/** 하나 또는 여러 개. 여러 개면 넘긴 순서대로 실행된다. */
|
|
127
|
-
type Finalizers = Finalizer | Finalizer[];
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* 모든 폼이 공유하는 상태 + 제출 파이프라인.
|
|
131
|
-
*
|
|
132
|
-
* 폼 하나하나가 `useState`로 값·에러·제출중 여부를 따로 들고 있으면 제출 처리(중복
|
|
133
|
-
* 클릭 차단, 성공/실패 알림, 성공 후 초기화)가 폼마다 조금씩 달라진다. 그 셋을 여기
|
|
134
|
-
* 한 곳에 묶어 두고, 화면은 `useSdForm()` 하나만 부르면 되게 한다.
|
|
135
|
-
*
|
|
136
|
-
* 스토어는 `formId`로 칸을 나눠 쓰므로 한 화면에 폼이 여러 개 떠 있어도 섞이지 않고,
|
|
137
|
-
* 반대로 **같은 id를 쓰면 서로 다른 컴포넌트가 같은 폼을 공유**한다(마법사 단계 분리,
|
|
138
|
-
* 페이지를 오간 뒤 입력값 복원 등).
|
|
139
|
-
*/
|
|
140
|
-
type FormValues = Record<string, unknown>;
|
|
141
|
-
/** 값이 유효하면 `null`, 아니면 사용자에게 보여줄 메시지를 돌려준다. */
|
|
142
|
-
type FormRule<V extends FormValues> = (value: unknown, values: V) => string | null;
|
|
143
|
-
type FormErrors<V extends FormValues> = Partial<Record<keyof V, string>>;
|
|
144
|
-
interface FormEntry {
|
|
145
|
-
values: FormValues;
|
|
146
|
-
errors: Record<string, string>;
|
|
147
|
-
submitting: boolean;
|
|
148
|
-
/** 제출 실패 메시지 — 폼 상단에 노출한다. 필드 에러는 `errors`가 담는다. */
|
|
149
|
-
error: ReactNode | null;
|
|
150
|
-
}
|
|
151
|
-
interface FormStoreState {
|
|
152
|
-
forms: Record<string, FormEntry>;
|
|
153
|
-
/** 첫 마운트 시 초기값을 심는다. 이미 있으면 건드리지 않는다(입력값 보존). */
|
|
154
|
-
ensureForm: (formId: string, initialValues: FormValues) => void;
|
|
155
|
-
setValue: (formId: string, name: string, value: unknown) => void;
|
|
156
|
-
setValues: (formId: string, values: FormValues) => void;
|
|
157
|
-
setErrors: (formId: string, errors: Record<string, string>) => void;
|
|
158
|
-
setSubmitting: (formId: string, submitting: boolean) => void;
|
|
159
|
-
setError: (formId: string, error: ReactNode | null) => void;
|
|
160
|
-
resetForm: (formId: string, initialValues: FormValues) => void;
|
|
161
|
-
/** 폼 칸 자체를 버린다 — 값까지 지우고 싶을 때만. */
|
|
162
|
-
removeForm: (formId: string) => void;
|
|
163
|
-
}
|
|
164
|
-
declare const useFormStore: zustand.UseBoundStore<zustand.StoreApi<FormStoreState>>;
|
|
165
|
-
interface SdFormOptions<V extends FormValues> {
|
|
166
|
-
/** 스토어에서 이 폼이 쓸 칸의 이름. 같은 id를 쓰는 컴포넌트끼리는 상태를 공유한다. */
|
|
167
|
-
id: string;
|
|
168
|
-
initialValues: V;
|
|
169
|
-
/** 필드별 검증 규칙. 제출 시 한 번에 돌린다. */
|
|
170
|
-
rules?: Partial<Record<keyof V, FormRule<V>>>;
|
|
171
|
-
/** 실제 전송. 예외를 던지면 실패로 처리된다(에러 토스트 + 폼 상단 메시지). */
|
|
172
|
-
onSubmit: (values: V) => void | Promise<void>;
|
|
173
|
-
/** 성공 토스트 메시지. `false`면 토스트를 띄우지 않는다. */
|
|
174
|
-
successMessage?: ReactNode | false;
|
|
175
|
-
/** 실패 메시지 변환. 기본값은 `Error.message`, 없으면 일반 문구. */
|
|
176
|
-
errorMessage?: (error: unknown) => ReactNode;
|
|
177
|
-
/** 성공 후 초기값으로 되돌릴지 */
|
|
178
|
-
resetOnSuccess?: boolean;
|
|
179
|
-
onSuccess?: (values: V) => void;
|
|
180
|
-
/** 전송이 실패했을 때. 예외를 그대로 받는다. */
|
|
181
|
-
onError?: (error: unknown) => void;
|
|
182
|
-
/**
|
|
183
|
-
* 성공·실패와 무관하게 전송이 끝나면 **항상** 실행된다 — `finally`에 해당한다.
|
|
184
|
-
* 라우팅, 모달 닫기, 목록 새로고침처럼 결과와 무관한 뒷정리 자리다.
|
|
185
|
-
*
|
|
186
|
-
* 타입은 폼 전용이 아니라 공용 `Finalizers`(`util/finalize.util.ts`)다 — 인자를 받지
|
|
187
|
-
* 않으므로 기존 함수를 그대로 꽂고(`finalize: closeModal`), 여러 개는 배열로 묶는다.
|
|
188
|
-
* 결과에 따라 갈라져야 하는 일은 `onSuccess` / `onError`에 둔다.
|
|
189
|
-
*
|
|
190
|
-
* 검증에서 걸려 전송 자체를 하지 않은 경우에는 실행되지 않는다(아직 "끝난" 게 아니다).
|
|
191
|
-
* `submitting`이 내려간 **뒤에** 실행되므로, 여기서 `reset()`이나 다른 제출을 불러도 안전하다.
|
|
192
|
-
*/
|
|
193
|
-
finalize?: Finalizers;
|
|
194
|
-
}
|
|
195
|
-
interface SdFormApi<V extends FormValues> {
|
|
196
|
-
values: V;
|
|
197
|
-
errors: FormErrors<V>;
|
|
198
|
-
/** 전송 중 — 버튼 `loading`에 그대로 넘긴다. 이 동안 재제출은 무시된다. */
|
|
199
|
-
submitting: boolean;
|
|
200
|
-
/** 제출 실패 메시지 */
|
|
201
|
-
error: ReactNode | null;
|
|
202
|
-
setValue: <K extends keyof V>(name: K, value: V[K]) => void;
|
|
203
|
-
setValues: (values: Partial<V>) => void;
|
|
204
|
-
reset: () => void;
|
|
205
|
-
/** Mantine 입력 컴포넌트에 펼쳐 넣는다. 체크박스는 `{ type: 'checkbox' }`. */
|
|
206
|
-
getInputProps: (name: keyof V, options?: {
|
|
207
|
-
type?: 'input' | 'checkbox';
|
|
208
|
-
}) => Record<string, unknown>;
|
|
209
|
-
/** `<form onSubmit={...}>`에 그대로 넘긴다. */
|
|
210
|
-
onSubmit: (event?: FormEvent<HTMLFormElement>) => void;
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* 폼 하나를 스토어에 붙이고, 값·에러·제출을 한 묶음으로 돌려준다.
|
|
214
|
-
*
|
|
215
|
-
* 제출은 항상 같은 순서로 흐른다:
|
|
216
|
-
* 검증 → (통과 시) submitting on → `onSubmit` → 성공 토스트·초기화·`onSuccess`
|
|
217
|
-
* → 실패 시 에러 토스트 + 상단 메시지·`onError` → submitting off → `finalize`.
|
|
218
|
-
*/
|
|
219
|
-
declare function useSdForm<V extends FormValues>({ id, initialValues, rules, onSubmit, successMessage, errorMessage, resetOnSuccess, onSuccess, onError, finalize, }: SdFormOptions<V>): SdFormApi<V>;
|
|
220
|
-
/**
|
|
221
|
-
* 자주 쓰는 검증 규칙. `rules`에 그대로 꽂아 쓴다.
|
|
222
|
-
*
|
|
223
|
-
* ```ts
|
|
224
|
-
* rules: { email: formRules.email(), password: formRules.minLength(8) }
|
|
225
|
-
* ```
|
|
226
|
-
*/
|
|
227
|
-
declare const formRules: {
|
|
228
|
-
required: <V extends FormValues>(message?: string) => FormRule<V>;
|
|
229
|
-
email: <V extends FormValues>(message?: string) => FormRule<V>;
|
|
230
|
-
minLength: <V extends FormValues>(length: number, message?: string) => FormRule<V>;
|
|
231
|
-
/** 체크박스 동의 — 반드시 true여야 한다. */
|
|
232
|
-
checked: <V extends FormValues>(message?: string) => FormRule<V>;
|
|
233
|
-
/** 다른 필드와 같은 값인지 — 비밀번호 확인 등. */
|
|
234
|
-
sameAs: <V extends FormValues>(other: keyof V, message?: string) => FormRule<V>;
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
export { type AuthUser, type FormErrors, type FormRule, type FormValues, type SdFormApi, type SdFormOptions, formRules, useAuthHydrated, useAuthStore, useFormStore, useNavStore, useSdForm, useUiStore };
|
package/dist/types/index.cjs
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __copyProps = (to, from, except, desc) => {
|
|
7
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
-
for (let key of __getOwnPropNames(from))
|
|
9
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
-
}
|
|
12
|
-
return to;
|
|
13
|
-
};
|
|
14
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
-
|
|
16
|
-
// types/index.ts
|
|
17
|
-
var types_exports = {};
|
|
18
|
-
module.exports = __toCommonJS(types_exports);
|
package/dist/types/index.d.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
|
|
3
|
-
interface CommonInfo {
|
|
4
|
-
id: number;
|
|
5
|
-
order: number;
|
|
6
|
-
isShow: boolean;
|
|
7
|
-
createdAt?: Date;
|
|
8
|
-
updatedAt?: Date;
|
|
9
|
-
}
|
|
10
|
-
interface NavItem extends CommonInfo {
|
|
11
|
-
label: string;
|
|
12
|
-
href?: string;
|
|
13
|
-
highlight?: boolean;
|
|
14
|
-
parentId?: number;
|
|
15
|
-
}
|
|
16
|
-
interface HeroCta {
|
|
17
|
-
label: string;
|
|
18
|
-
href: string;
|
|
19
|
-
variant?: 'primary' | 'secondary' | 'outline' | 'white';
|
|
20
|
-
icon: boolean;
|
|
21
|
-
}
|
|
22
|
-
interface HeroSlide extends CommonInfo {
|
|
23
|
-
image: string;
|
|
24
|
-
title: ReactNode;
|
|
25
|
-
description: string;
|
|
26
|
-
ctas?: HeroCta[];
|
|
27
|
-
}
|
|
28
|
-
interface FeatureItem extends CommonInfo {
|
|
29
|
-
icon?: ReactNode;
|
|
30
|
-
label?: string;
|
|
31
|
-
title: ReactNode;
|
|
32
|
-
description?: ReactNode;
|
|
33
|
-
}
|
|
34
|
-
interface CompanyAddress {
|
|
35
|
-
label: string;
|
|
36
|
-
address: string;
|
|
37
|
-
order: number;
|
|
38
|
-
embbedUrl?: string;
|
|
39
|
-
}
|
|
40
|
-
type SocialPlatform = 'x' | 'youtube' | 'instagram' | 'facebook' | 'linkedin' | 'github' | 'blog';
|
|
41
|
-
interface SocialItem {
|
|
42
|
-
platform: SocialPlatform;
|
|
43
|
-
url: string;
|
|
44
|
-
label?: string;
|
|
45
|
-
}
|
|
46
|
-
interface CompanyInfo {
|
|
47
|
-
name: string;
|
|
48
|
-
registrationNumber: string;
|
|
49
|
-
addresses: CompanyAddress[];
|
|
50
|
-
tel: string;
|
|
51
|
-
fax?: string;
|
|
52
|
-
email: string;
|
|
53
|
-
copyrightYear: number;
|
|
54
|
-
socials?: SocialItem[];
|
|
55
|
-
}
|
|
56
|
-
interface TimelineEvent extends CommonInfo {
|
|
57
|
-
year: number;
|
|
58
|
-
month: number;
|
|
59
|
-
title?: string;
|
|
60
|
-
description: string;
|
|
61
|
-
}
|
|
62
|
-
interface StepItem {
|
|
63
|
-
title: string;
|
|
64
|
-
description?: string;
|
|
65
|
-
}
|
|
66
|
-
interface TestimonialItem {
|
|
67
|
-
lines: string[];
|
|
68
|
-
name: string;
|
|
69
|
-
role: string;
|
|
70
|
-
company?: string;
|
|
71
|
-
rating?: number;
|
|
72
|
-
avatar?: string;
|
|
73
|
-
}
|
|
74
|
-
interface PricingFeature {
|
|
75
|
-
text: string;
|
|
76
|
-
included: boolean;
|
|
77
|
-
}
|
|
78
|
-
interface PricingItem {
|
|
79
|
-
name: string;
|
|
80
|
-
price: string;
|
|
81
|
-
period?: string;
|
|
82
|
-
description?: string;
|
|
83
|
-
features: PricingFeature[];
|
|
84
|
-
ctaLabel?: string;
|
|
85
|
-
isPopular?: boolean;
|
|
86
|
-
}
|
|
87
|
-
interface FaqItem {
|
|
88
|
-
question: string;
|
|
89
|
-
answer: string;
|
|
90
|
-
}
|
|
91
|
-
interface ClientItem {
|
|
92
|
-
name: string;
|
|
93
|
-
url: string;
|
|
94
|
-
logo: string;
|
|
95
|
-
}
|
|
96
|
-
interface SolutionItem extends CommonInfo {
|
|
97
|
-
category: string;
|
|
98
|
-
title: ReactNode;
|
|
99
|
-
description: string;
|
|
100
|
-
href?: string;
|
|
101
|
-
ctaLabel?: string;
|
|
102
|
-
icon?: ReactNode;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export type { ClientItem, CommonInfo, CompanyAddress, CompanyInfo, FaqItem, FeatureItem, HeroCta, HeroSlide, NavItem, PricingFeature, PricingItem, SocialItem, SocialPlatform, SolutionItem, StepItem, TestimonialItem, TimelineEvent };
|