@fajarmaulana/komerce-lp-helper 0.4.14 → 0.4.16
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 +470 -100
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/cookie.js +1 -3
- package/dist/utils/local.d.ts +6 -0
- package/dist/utils/local.js +6 -0
- package/dist/utils/session.d.ts +46 -0
- package/dist/utils/session.js +60 -0
- package/dist/utils/useApi.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,56 +10,72 @@ npm install @fajarmaulana/komerce-lp-helper
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
###
|
|
13
|
+
### Components
|
|
14
14
|
|
|
15
|
-
#### `
|
|
15
|
+
#### `Form`
|
|
16
16
|
|
|
17
|
-
A
|
|
17
|
+
A reusable form component that simplifies form submission handling by preventing default browser submit behavior,
|
|
18
|
+
collecting form values into a `FormData` object, and passing it to an action callback.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
| Prop | Type | Description |
|
|
21
|
+
| ---------- | ------------------------------- | ------------------------------------------------- |
|
|
22
|
+
| `action` | `(formData: FormData) => void` | Function called when the form is submitted. |
|
|
23
|
+
| `ref` | `Ref<HTMLFormElement>` | Exposes the underlying `<form>` element. |
|
|
24
|
+
| `children` | `ReactNode` | The form’s inner content (inputs, buttons, etc.). |
|
|
25
|
+
| `...props` | `ComponentPropsWithRef<'form'>` | All standard HTML form attributes. |
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
const users = await http.get('/users')
|
|
27
|
+
**Usage:**
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
```tsx
|
|
30
|
+
import { Form } from '@fajarmaulana/komerce-lp-helper'
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
const handleSubmit = (formData: FormData) => {
|
|
33
|
+
console.log(formData.get('username'))
|
|
34
|
+
}
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
;<Form action={handleSubmit} className="my-form">
|
|
37
|
+
<input name="username" />
|
|
38
|
+
<button type="submit">Submit</button>
|
|
39
|
+
</Form>
|
|
40
|
+
```
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
fetching and mutations with progress tracking.
|
|
42
|
+
#### `LazyBackground`
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
A wrapper component that lazily loads a background image when it enters the viewport using the Intersection Observer
|
|
45
|
+
API.
|
|
39
46
|
|
|
40
|
-
|
|
47
|
+
| Prop | Type | Description |
|
|
48
|
+
| ----------- | ----------- | ----------------------------------------------------------- |
|
|
49
|
+
| `url` | `string` | The image URL to be lazily loaded as the background. |
|
|
50
|
+
| `children` | `ReactNode` | Optional elements or content rendered inside the container. |
|
|
51
|
+
| `className` | `string` | Additional CSS class names applied to the outer container. |
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
const { data, isLoading, refetch } = api.fetch<User[]>('/users')
|
|
53
|
+
**Usage:**
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
initialOffset: 0,
|
|
51
|
-
setOffset: (lastItems, allItems, lastOffset) => (lastItems.length ? lastOffset + 1 : null),
|
|
52
|
-
})
|
|
55
|
+
```tsx
|
|
56
|
+
import { LazyBackground } from '@fajarmaulana/komerce-lp-helper'
|
|
57
|
+
;<LazyBackground url="https://example.com/bg.jpg" className="h-64 w-full">
|
|
58
|
+
<h1>Content inside lazy background</h1>
|
|
59
|
+
</LazyBackground>
|
|
53
60
|
```
|
|
54
61
|
|
|
55
62
|
### Hooks
|
|
56
63
|
|
|
57
64
|
#### `useDebounce`
|
|
58
65
|
|
|
59
|
-
Returns a debounced version of a value.
|
|
66
|
+
Returns a debounced version of a value. It updates the returned value only after a specified delay has passed without
|
|
67
|
+
any changes to the input value.
|
|
60
68
|
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
| Parameter | Type | Default | Description |
|
|
70
|
+
| --------- | -------- | ---------- | ----------------------------------- |
|
|
71
|
+
| `value` | `T` | (Required) | The input value to debounce. |
|
|
72
|
+
| `delay` | `number` | `500` | The debounce delay in milliseconds. |
|
|
73
|
+
|
|
74
|
+
**Returns:** `T` - The debounced value.
|
|
75
|
+
|
|
76
|
+
**Usage:**
|
|
77
|
+
|
|
78
|
+
```ts
|
|
63
79
|
const debouncedSearchTerm = useDebounce(searchTerm, 500)
|
|
64
80
|
```
|
|
65
81
|
|
|
@@ -67,134 +83,488 @@ const debouncedSearchTerm = useDebounce(searchTerm, 500)
|
|
|
67
83
|
|
|
68
84
|
Returns a debounced version of a callback function.
|
|
69
85
|
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
| Parameter | Type | Default | Description |
|
|
87
|
+
| ---------- | -------------- | ---------- | ------------------------------------------------------ |
|
|
88
|
+
| `callback` | `T` (Function) | (Required) | The original function to debounce. |
|
|
89
|
+
| `delay` | `number` | (Required) | Delay in milliseconds before the callback is executed. |
|
|
90
|
+
|
|
91
|
+
**Returns:** `(...args: Parameters<T>) => void` - A debounced function.
|
|
92
|
+
|
|
93
|
+
**Usage:**
|
|
94
|
+
|
|
95
|
+
```ts
|
|
72
96
|
const debouncedSearch = useDebounceFunc(q => fetchResults(q), 300)
|
|
73
97
|
```
|
|
74
98
|
|
|
75
99
|
#### `useConditionalDebounce`
|
|
76
100
|
|
|
77
|
-
Conditionally executes a callback function after a specified debounce delay.
|
|
101
|
+
Conditionally executes a callback function after a specified debounce delay. Only triggers the callback if a given
|
|
102
|
+
condition is `true`.
|
|
78
103
|
|
|
79
|
-
|
|
104
|
+
| Parameter | Type | Default | Description |
|
|
105
|
+
| --------- | -------- | ------- | ----------------------------------- |
|
|
106
|
+
| `delay` | `number` | `500` | The debounce delay in milliseconds. |
|
|
80
107
|
|
|
81
|
-
|
|
108
|
+
**Returns:** `(condition: boolean, callback: () => void) => void` - The function to execute.
|
|
82
109
|
|
|
83
|
-
|
|
84
|
-
|
|
110
|
+
**Usage:**
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const run = useConditionalDebounce(300)
|
|
114
|
+
run(isValid, () => submitData())
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### `useForm`
|
|
118
|
+
|
|
119
|
+
Manages form fields, retrieval of values, and error handling for both named inputs and standalone fields.
|
|
120
|
+
|
|
121
|
+
| Parameter | Type | Default | Description |
|
|
122
|
+
| ---------------------- | ---------- | ------- | ------------------------------------------------------ |
|
|
123
|
+
| `fieldsWithoutNameIds` | `string[]` | `[]` | List of field IDs that exist outside the form element. |
|
|
124
|
+
|
|
125
|
+
**Returns:**
|
|
126
|
+
|
|
127
|
+
| Property | Type | Description |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `form` | `RefObject<HTMLFormElement>` | Ref to attach to the target `<form>`. |
|
|
130
|
+
| `fields` | `() => TFields<T>` | Function returning the values and elements of named form inputs. |
|
|
131
|
+
| `fieldsWithoutName` | `() => TFields<U>` | Function returning the values and elements of external inputs. |
|
|
85
132
|
|
|
86
|
-
|
|
133
|
+
_Note: `TFields` returns an object where keys are the field names and values are of type `TFieldItem<T>` containing
|
|
134
|
+
`field_value`, `field_id`, `field_error`, and `field_info`. Create a input component with small element with id
|
|
135
|
+
`{fieldName}_error` to show error message, and with id `{fieldName}_info` to show info message._
|
|
87
136
|
|
|
88
|
-
|
|
89
|
-
|
|
137
|
+
**Usage:**
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
const { form, fields, fieldsWithoutName } = useForm<{ email: string }>(['custom-input-id'])
|
|
141
|
+
|
|
142
|
+
const handleSubmit = e => {
|
|
143
|
+
e.preventDefault()
|
|
144
|
+
const data = fields()
|
|
145
|
+
console.log(data.email.field_value)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
;<form ref={form} onSubmit={handleSubmit}>
|
|
149
|
+
<input name="email" id="email" />
|
|
150
|
+
<span id="email_error"></span>
|
|
151
|
+
</form>
|
|
90
152
|
```
|
|
91
153
|
|
|
154
|
+
#### `useRouter`
|
|
155
|
+
|
|
156
|
+
Custom router API built on top of React Router DOM that provides easier navigation methods and state management.
|
|
157
|
+
|
|
158
|
+
**Returns:**
|
|
159
|
+
|
|
160
|
+
| Property | Type | Description |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `push` | `IRouterPush` | Navigate to a new page (string or object with pathname, query, hash). |
|
|
163
|
+
| `route` | `(pathname, query?, options?) => void` | Navigate to a new page with query parameters. |
|
|
164
|
+
| `replace` | `IRouterPush` | Replace the current history entry. |
|
|
165
|
+
| `back` | `() => void` | Go back to previous page. |
|
|
166
|
+
| `next` | `() => void` | Go forward to next page. |
|
|
167
|
+
| `go` | `(num: number) => void` | Jump to specific history index. |
|
|
168
|
+
| `refresh` | `() => void` | Reload the current page. |
|
|
169
|
+
| `path` | `string` | The current pathname without query/hash. |
|
|
170
|
+
| `hash` | `string` | The hash portion of current URL. |
|
|
171
|
+
| `fullpath` | `string` | Full path including query and hash. |
|
|
172
|
+
| `origin` | `string` | The base URL. |
|
|
173
|
+
| `href` | `string` | Full URL including origin. |
|
|
174
|
+
| `query` | `TRouterQuery` | Parsed query parameters as object. |
|
|
175
|
+
| `params` | `Record<string, string>` | Current route parameters. |
|
|
176
|
+
| `beforePopState` | `(cb) => void` | Hook to cancel navigation if callback returns false. |
|
|
177
|
+
|
|
92
178
|
#### `useQueryParams`
|
|
93
179
|
|
|
94
180
|
A hook for reading and updating query parameters in the URL locally.
|
|
95
181
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
182
|
+
| Parameter | Type | Default | Description |
|
|
183
|
+
| --------------- | ------------ | ----------- | --------------------------------------------- |
|
|
184
|
+
| `defaultValues` | `Partial<T>` | `undefined` | Optional default values for query parameters. |
|
|
185
|
+
|
|
186
|
+
**Returns:** `[T, (updates: Partial<T>) => void]` - Tuple containing query object and updater function.
|
|
187
|
+
|
|
188
|
+
#### `useSectionObserver`
|
|
189
|
+
|
|
190
|
+
Uses the Intersection Observer API to detect when a trigger element comes into view and updates the `data-active`
|
|
191
|
+
attribute of a target element.
|
|
192
|
+
|
|
193
|
+
| Parameter | Type | Default | Description |
|
|
194
|
+
| ------------ | ------------------------ | ---------- | --------------------------------------------------- |
|
|
195
|
+
| `triggerRef` | `RefObject<HTMLElement>` | (Required) | Ref pointing to the element triggering observation. |
|
|
196
|
+
| `targetId` | `string` | (Required) | ID of the target element to toggle `data-active`. |
|
|
197
|
+
| `threshold` | `number` | `0.8` | Visibility threshold (0 to 1). |
|
|
100
198
|
|
|
101
199
|
#### `useSlider`
|
|
102
200
|
|
|
103
201
|
Manages logic for custom slider components, including touch/drag support and navigation.
|
|
104
202
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
203
|
+
| Parameter | Type | Default | Description |
|
|
204
|
+
| --------------- | ------------ | ----------- | ----------------------------------------------------- |
|
|
205
|
+
| `data` | `any[]` | (Required) | Array of data to display. |
|
|
206
|
+
| `mobileOnly` | `boolean` | `false` | If true, slider only works below `mobileBound` width. |
|
|
207
|
+
| `infiniteSlide` | `boolean` | `false` | Allow infinite sliding. |
|
|
208
|
+
| `isLoading` | `boolean` | `false` | Is data still loading. |
|
|
209
|
+
| `mobileBound` | `number` | `640` | Screen width bound for mobile only mode. |
|
|
210
|
+
| `onNext` | `() => void` | `undefined` | Override the next function. |
|
|
211
|
+
| `onBack` | `() => void` | `undefined` | Override the back function. |
|
|
212
|
+
|
|
213
|
+
**Returns:**
|
|
214
|
+
|
|
215
|
+
| Property | Type | Description |
|
|
216
|
+
|---|---|---|
|
|
217
|
+
| `currentSlide` | `number` | The current active slide index. |
|
|
218
|
+
| `movement` | `number` | Current drag movement value. |
|
|
219
|
+
| `grab` | `boolean` | Whether the slider is currently being grabbed/dragged. |
|
|
220
|
+
| `disableLeftArrow` | `boolean` | Whether left arrow should be disabled. |
|
|
221
|
+
| `disableRightArrow`| `boolean` | Whether right arrow should be disabled. |
|
|
222
|
+
| `setCurrentSlide` | `(val) => void` | Manually set the slide. |
|
|
223
|
+
| `startSlide` | `(param) => void` | Call on mouse down / touch start. |
|
|
224
|
+
| `moveSlide` | `(param) => void` | Call on mouse move / touch move. |
|
|
225
|
+
| `endSlide` | `(param) => void` | Call on mouse up / touch end. |
|
|
226
|
+
| `next`, `back` | `() => void` | Navigate to next/prev slide. |
|
|
227
|
+
|
|
228
|
+
### Utilities
|
|
229
|
+
|
|
230
|
+
#### HTTP Client & API
|
|
231
|
+
|
|
232
|
+
**`http`** A robust wrapper around `fetch` with caching, retry, and interceptors.
|
|
233
|
+
|
|
234
|
+
| Method | Parameters | Returns | Description |
|
|
235
|
+
| -------------------------------- | --------------------------------------------------- | -------------------------- | ----------------------------- |
|
|
236
|
+
| `get` | `url: string`, `config?: Omit<THttpConfig, 'body'>` | `Promise<TApiResponse<T>>` | Performs a GET request. |
|
|
237
|
+
| `post`, `put`, `patch`, `delete` | `url: string`, `body?: U`, `config?: THttpConfig` | `Promise<TApiResponse<T>>` | Performs a mutation request. |
|
|
238
|
+
| `request` | `config: TApiConfig` | `Promise<TApiResponse<T>>` | Full configuration request. |
|
|
239
|
+
| `getCache` | `key: string` | `T \| undefined` | Retrieves cached data. |
|
|
240
|
+
| `setCache` | `key: string, data: T, ttl?: number` | `void` | Manually store data in cache. |
|
|
241
|
+
| `removeCache` | `key: string` | `void` | Remove specific cache entry. |
|
|
242
|
+
| `clearCache` | | `void` | Clears all cache. |
|
|
243
|
+
| `create` | `options?: TApiInstanceOptions` | `IApiInstance` | Creates a new instance. |
|
|
244
|
+
|
|
245
|
+
**`createApi(options)`** Creates a new API instance with built-in React hooks for data fetching, mutation, and infinite
|
|
246
|
+
pagination. Returns an object with the following hooks:
|
|
247
|
+
|
|
248
|
+
**1. `fetch<T>(url, config?, enabled?)`**
|
|
249
|
+
|
|
250
|
+
| Parameter | Type | Default | Description |
|
|
251
|
+
|---|---|---|---|
|
|
252
|
+
| `url` | `string` | (Required) | The endpoint URL. Changing this automatically triggers a new request. |
|
|
253
|
+
| `config` | `Omit<THttpConfig, 'onUpload' \| 'onDownload'>` | `undefined` | Optional HTTP configuration (headers, params, cache, etc.). |
|
|
254
|
+
| `enabled` | `boolean` | `true` | Whether the fetch should run automatically on mount or when URL changes. |
|
|
255
|
+
|
|
256
|
+
**Returns:**
|
|
257
|
+
| Property | Type | Description |
|
|
258
|
+
|---|---|---|
|
|
259
|
+
| `data` | `T \| null` | Fetched data. |
|
|
260
|
+
| `error` | `unknown` | Any encountered error. |
|
|
261
|
+
| `isLoading`| `boolean` | Request state. |
|
|
262
|
+
| `cacheKey` | `string \| null` | Identifier for cache. |
|
|
263
|
+
| `refetch` | `() => Promise<TFetchState<T>>` | Manually trigger a fresh fetch. |
|
|
264
|
+
| `setData` | `(data) => void` | Manually update state data. |
|
|
265
|
+
|
|
266
|
+
**Example:**
|
|
267
|
+
```tsx
|
|
268
|
+
const { data, isLoading, refetch } = api.fetch<User[]>('/users', { params: { status: 'active' } })
|
|
108
269
|
```
|
|
109
270
|
|
|
110
|
-
|
|
271
|
+
**2. `mutation<TData, TRequest>(url, config?)`**
|
|
111
272
|
|
|
112
|
-
|
|
273
|
+
| Parameter | Type | Default | Description |
|
|
274
|
+
|---|---|---|---|
|
|
275
|
+
| `url` | `string` | (Required) | Endpoint URL for the mutation. |
|
|
276
|
+
| `config` | `TMutationOptions` | `undefined` | Optional mutation config (method, headers, progress, etc.). |
|
|
113
277
|
|
|
114
|
-
|
|
115
|
-
|
|
278
|
+
**Returns:**
|
|
279
|
+
| Property | Type | Description |
|
|
280
|
+
|---|---|---|
|
|
281
|
+
| `mutate` | `(request?: TRequest) => Promise<TApiResponse<TData>>` | Trigger the mutation. |
|
|
282
|
+
| `isLoading`| `boolean` | In progress state. |
|
|
283
|
+
| `progress` | `TProgress \| null` | Upload/Download progress. |
|
|
116
284
|
|
|
117
|
-
|
|
285
|
+
**Example:**
|
|
286
|
+
```tsx
|
|
287
|
+
const { mutate, isLoading, progress } = api.mutation<User, FormData>('/users/upload', {
|
|
288
|
+
method: 'POST',
|
|
289
|
+
progress: 'upload'
|
|
290
|
+
})
|
|
118
291
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
292
|
+
// To trigger:
|
|
293
|
+
// await mutate(formData)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**3. `infinite<T, TOffset>(url, options, config?, enabled?)`**
|
|
297
|
+
|
|
298
|
+
| Parameter | Type | Default | Description |
|
|
299
|
+
|---|---|---|---|
|
|
300
|
+
| `url` | `string` | (Required) | Endpoint URL. Changing this resets items and fetches from the beginning. |
|
|
301
|
+
| `options` | `TInfiniteFetchOptions` | (Required) | Options containing `initialOffset`, `offsetKey`, and `setOffset` logic. |
|
|
302
|
+
| `config` | `Omit<THttpConfig, 'onUpload' \| 'onDownload'>` | `undefined` | Optional HTTP configuration. |
|
|
303
|
+
| `enabled` | `boolean` | `true` | Whether the fetch should run automatically. |
|
|
304
|
+
|
|
305
|
+
**Returns:**
|
|
306
|
+
| Property | Type | Description |
|
|
307
|
+
|---|---|---|
|
|
308
|
+
| `data` | `T \| null` | Aggregated items. |
|
|
309
|
+
| `error` | `unknown` | Any encountered error. |
|
|
310
|
+
| `isLoading` | `boolean` | Initial page request state. |
|
|
311
|
+
| `hasNextPage`| `boolean` | More pages available. |
|
|
312
|
+
| `isFetchingNextPage`| `boolean` | Fetching next page state. |
|
|
313
|
+
| `fetchNextPage`| `() => Promise<void>` | Trigger fetch for next offset. |
|
|
314
|
+
| `refetch` | `() => Promise<void>` | Reset state and trigger a fresh fetch from initial offset. |
|
|
315
|
+
| `setData` | `(updater: T \| null \| ((prev: T \| null) => T \| null)) => void` | Manually update local cache data. |
|
|
316
|
+
| `reset` | `() => void` | Reset all local states to initial values. |
|
|
317
|
+
|
|
318
|
+
**Example:**
|
|
319
|
+
```tsx
|
|
320
|
+
import { createApi } from '@fajarmaulana/komerce-lp-helper'
|
|
321
|
+
|
|
322
|
+
const api = createApi({ baseURL: 'https://api.example.com' })
|
|
323
|
+
|
|
324
|
+
const UserList = () => {
|
|
325
|
+
const {
|
|
326
|
+
data: users,
|
|
327
|
+
error,
|
|
328
|
+
isLoading,
|
|
329
|
+
hasNextPage,
|
|
330
|
+
isFetchingNextPage,
|
|
331
|
+
fetchNextPage,
|
|
332
|
+
refetch,
|
|
333
|
+
} = api.infinite<User[], number>(
|
|
334
|
+
'/users',
|
|
335
|
+
{
|
|
336
|
+
initialOffset: 1,
|
|
337
|
+
offsetKey: 'page',
|
|
338
|
+
setOffset: (lastItems, allItems, lastOffset) => {
|
|
339
|
+
// Return next offset (page) or null if there is no more data
|
|
340
|
+
return lastItems && lastItems.length >= 10 ? lastOffset + 1 : null
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
if (isLoading) return <p>Loading...</p>
|
|
346
|
+
if (error) return <p>Error loading users</p>
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
<div>
|
|
350
|
+
<button onClick={refetch}>Refetch</button>
|
|
351
|
+
<ul>
|
|
352
|
+
{users?.map(user => (
|
|
353
|
+
<li key={user.id}>{user.name}</li>
|
|
354
|
+
))}
|
|
355
|
+
</ul>
|
|
356
|
+
{hasNextPage && (
|
|
357
|
+
<button onClick={fetchNextPage} disabled={isFetchingNextPage}>
|
|
358
|
+
{isFetchingNextPage ? 'Loading more...' : 'Load More'}
|
|
359
|
+
</button>
|
|
360
|
+
)}
|
|
361
|
+
</div>
|
|
362
|
+
)
|
|
123
363
|
}
|
|
124
364
|
```
|
|
125
365
|
|
|
126
|
-
#### `useSectionObserver`
|
|
127
366
|
|
|
128
|
-
|
|
367
|
+
**4. `batch<T>(initialRequests?)`**
|
|
368
|
+
Execute multiple API requests in parallel.
|
|
129
369
|
|
|
130
|
-
|
|
131
|
-
|
|
370
|
+
| Parameter | Type | Default | Description |
|
|
371
|
+
|---|---|---|---|
|
|
372
|
+
| `initialRequests` | `{ [K in keyof T]: string \| TApiConfig }` | `undefined` | Array of URLs or full request configurations. |
|
|
132
373
|
|
|
133
|
-
|
|
134
|
-
|
|
374
|
+
**Returns:**
|
|
375
|
+
| Property | Type | Description |
|
|
376
|
+
|---|---|---|
|
|
377
|
+
| `mutate` | `(overrideRequests?) => Promise<TBatchResponse<T>>` | Trigger the batch execution. |
|
|
378
|
+
| `isLoading`| `boolean` | Whether any request in the batch is in progress. |
|
|
379
|
+
| `error` | `unknown` | The first error encountered. |
|
|
380
|
+
| `cacheKeys`| `(string \| null)[]` | Array of cache keys for each request. |
|
|
381
|
+
|
|
382
|
+
**Example:**
|
|
383
|
+
```tsx
|
|
384
|
+
const { mutate, isLoading } = api.batch<[User[], Post[]]>([
|
|
385
|
+
'/users',
|
|
386
|
+
{ url: '/posts', method: 'GET' }
|
|
387
|
+
])
|
|
388
|
+
|
|
389
|
+
// To trigger:
|
|
390
|
+
// const [usersResponse, postsResponse] = await mutate()
|
|
135
391
|
```
|
|
136
392
|
|
|
137
|
-
|
|
393
|
+
#### Interceptor Setup Example
|
|
394
|
+
|
|
395
|
+
You can set up custom interceptors on an API instance to attach tokens globally, or handle specific error codes:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { ApiMeta, createApi, getCookie, type TApiConfig } from '@fajarmaulana/komerce-lp-helper'
|
|
399
|
+
|
|
400
|
+
import { INTERNAL_API } from '@/constants/env'
|
|
401
|
+
|
|
402
|
+
import { COOKIE_KEY_JWT, forceLogout } from './auth'
|
|
403
|
+
|
|
404
|
+
const PUBLIC_ENDPOINTS: Record<string, string[]> = {
|
|
405
|
+
[INTERNAL_API.baseURL!]: ['/auth/api/v1/live-chat/verify-token'],
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const isPublicEndpoint = (path: string, baseUrl: string) => {
|
|
409
|
+
const publicPaths = PUBLIC_ENDPOINTS[baseUrl]
|
|
410
|
+
return publicPaths && publicPaths.some(publicPath => path.startsWith(publicPath))
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const interceptor = {
|
|
414
|
+
request: (config: TApiConfig) => {
|
|
415
|
+
if (!config.baseURL || isPublicEndpoint(config.url, config.baseURL)) return config
|
|
416
|
+
|
|
417
|
+
const token = getCookie<string>(COOKIE_KEY_JWT)
|
|
418
|
+
if (!token) {
|
|
419
|
+
forceLogout()
|
|
420
|
+
throw new Error('Token expired or missing')
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
...config,
|
|
425
|
+
headers: {
|
|
426
|
+
...config.headers,
|
|
427
|
+
Authorization: `Bearer ${token}`,
|
|
428
|
+
},
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
error: async (error: unknown) => {
|
|
432
|
+
if (error instanceof ApiMeta && [401, 403].includes(error.code)) forceLogout()
|
|
433
|
+
|
|
434
|
+
const isNetworkError =
|
|
435
|
+
error instanceof Error &&
|
|
436
|
+
(error.message === 'Network Error' || (error as { code?: string }).code === 'ERR_NETWORK')
|
|
437
|
+
|
|
438
|
+
if (isNetworkError && window.location.pathname !== '/error-network') {
|
|
439
|
+
window.location.replace('/error-network')
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return Promise.reject(error)
|
|
443
|
+
},
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const internalApi = createApi({ ...INTERNAL_API })
|
|
447
|
+
internalApi.setInterceptors(interceptor)
|
|
448
|
+
|
|
449
|
+
export { internalApi }
|
|
450
|
+
```
|
|
138
451
|
|
|
139
452
|
#### Cookie
|
|
140
453
|
|
|
141
454
|
Managed wrappers for `document.cookie`.
|
|
142
455
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
456
|
+
| Function | Parameters | Description |
|
|
457
|
+
| --------------- | ------------------------------------ | ----------------------------------------- |
|
|
458
|
+
| `setCookie` | `{ key, value, maxAge?: number, sameSite?: Lax/Strict/None }` | Sets a cookie. Value is JSON stringified. |
|
|
459
|
+
| `setCookies` | `items: TCookie[]` | Sets multiple cookies. |
|
|
460
|
+
| `getCookie` | `key: string` | Gets and parses a cookie value. |
|
|
461
|
+
| `getCookies` | `keys: string[]` | Retrieves multiple cookies as an object. |
|
|
462
|
+
| `removeCookie` | `key: string` | Removes a cookie. |
|
|
463
|
+
| `removeCookies` | `keys: string[]` | Removes multiple cookies. |
|
|
464
|
+
| `clearCookies` | | Clears all cookies. |
|
|
150
465
|
|
|
151
466
|
#### Local Storage
|
|
152
467
|
|
|
153
468
|
Type-safe wrappers for `localStorage`.
|
|
154
469
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
470
|
+
| Function | Parameters | Description |
|
|
471
|
+
| -------------- | ------------------------- | --------------------------------------- |
|
|
472
|
+
| `setLocal` | `key: string, value: T` | Stores a value (JSON stringified). |
|
|
473
|
+
| `setLocals` | `items: { key, value }[]` | Stores multiple values. |
|
|
474
|
+
| `getLocal` | `key: string` | Retrieves and parses a value. |
|
|
475
|
+
| `getLocals` | `keys: string[]` | Retrieves multiple values as an object. |
|
|
476
|
+
| `removeLocal` | `key: string` | Removes an item. |
|
|
477
|
+
| `removeLocals` | `keys: string[]` | Removes multiple items. |
|
|
478
|
+
| `clearLocals` | | Clears local storage. |
|
|
479
|
+
|
|
480
|
+
#### Session Storage
|
|
481
|
+
|
|
482
|
+
Type-safe wrappers for `sessionStorage`.
|
|
483
|
+
|
|
484
|
+
| Function | Parameters | Description |
|
|
485
|
+
| ---------------- | ------------------------- | --------------------------------------- |
|
|
486
|
+
| `setSession` | `key: string, value: T` | Stores a value (JSON stringified). |
|
|
487
|
+
| `setSessions` | `items: { key, value }[]` | Stores multiple values. |
|
|
488
|
+
| `getSession` | `key: string` | Retrieves and parses a value. |
|
|
489
|
+
| `getSessions` | `keys: string[]` | Retrieves multiple values as an object. |
|
|
490
|
+
| `removeSession` | `key: string` | Removes an item. |
|
|
491
|
+
| `removeSessions` | `keys: string[]` | Removes multiple items. |
|
|
492
|
+
| `clearSessions` | | Clears session storage. |
|
|
161
493
|
|
|
162
494
|
#### File
|
|
163
495
|
|
|
164
496
|
Helpers for file and blob manipulation.
|
|
165
497
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
498
|
+
| Function | Parameters | Returns | Description |
|
|
499
|
+
| ----------------------- | -------------------------------------------- | --------------------- | ------------------------------------------------------- |
|
|
500
|
+
| `checkImage` | `url: string` | `Promise<string>` | Checks if an image URL is valid (returns URL or empty). |
|
|
501
|
+
| `convertBlob` | `blob: Blob` | `Promise<string>` | Converts a Blob to a Base64 string. |
|
|
502
|
+
| `getExtension` | `mimeType: string` | `string` | Gets file extension from MIME type. |
|
|
503
|
+
| `filenameWithExtension` | `blob: Blob, prefix?: string` | `string` | Generates a filename. |
|
|
504
|
+
| `createDownloadAnchor` | `blob: Blob, options?: { filename, target }` | `{ anchor, blobUrl }` | Creates an anchor for downloading. |
|
|
505
|
+
| `downloadBlob` | `blob: Blob, filename: string` | `void` | Triggers a file download. |
|
|
172
506
|
|
|
173
507
|
#### General
|
|
174
508
|
|
|
175
509
|
Common DOM and string utilities.
|
|
176
510
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
511
|
+
| Function | Parameters | Returns | Description |
|
|
512
|
+
| ---------------- | ------------------------------------------ | ---------------- | ------------------------------------------- |
|
|
513
|
+
| `getById` | `id: string` | `HTMLElement` | Type-safe `document.getElementById`. |
|
|
514
|
+
| `getByAny` | `selector: string` | `HTMLElement` | Type-safe `document.querySelector`. |
|
|
515
|
+
| `clickById` | `id: string` | `void` | Triggers a click on an element by ID. |
|
|
516
|
+
| `focusById` | `id: string` | `void` | Focuses an element by ID. |
|
|
517
|
+
| `handleHashLink` | `hash: string, currentHash: string, func?` | `void` | Smooth scrolling for hash links. |
|
|
518
|
+
| `acronym` | `name: string` | `string` | Generates a 1-2 letter acronym from a name. |
|
|
519
|
+
| `isNotPrimitive` | `value: unknown` | `boolean` | Checks if a value is an object/array. |
|
|
520
|
+
| `toWA` | `message: string, options?: { urlOnly?: boolean; phoneNumber?: string }` | `string \| void` | Get WhatsApp URL or open in new window. |
|
|
184
521
|
|
|
185
|
-
#### Form Validation
|
|
522
|
+
#### Form Validation (Error Provider)
|
|
186
523
|
|
|
187
|
-
Helpers for validating inputs and displaying error messages.
|
|
524
|
+
Helpers for validating inputs and displaying error messages dynamically.
|
|
188
525
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
numbers, symbols).
|
|
526
|
+
**`provideFieldError(params)`** Validates a field against a set of rules, updates the target error element, and modifies
|
|
527
|
+
the target `<label>` border color.
|
|
192
528
|
|
|
193
|
-
|
|
529
|
+
- `params`: `{ field_id, field_value, field_error, rules }`
|
|
530
|
+
- `rules`: A mapping of error messages to their boolean validation results (`true` = invalid).
|
|
531
|
+
|
|
532
|
+
**Example Rules:**
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
export const VALID_EMAIL = {
|
|
536
|
+
re: /^(?![.])[A-Za-z0-9._-]+(?<![.])@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,4}$/,
|
|
537
|
+
text: 'masukkan email yang valid',
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export const ALPHASPACE = {
|
|
541
|
+
re: /^[A-Za-z]+(?: [A-Za-z]+)*$/,
|
|
542
|
+
text: ' hanya boleh berisi huruf dan spasi antar kata',
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export const REGISTER_RULES = {
|
|
546
|
+
emailRules: (email: string) => ({
|
|
547
|
+
'email harus diisi': !email,
|
|
548
|
+
[VALID_EMAIL.text]: !VALID_EMAIL.re.test(email),
|
|
549
|
+
}),
|
|
550
|
+
fullnameRules: (fullname: string) => ({
|
|
551
|
+
'nama harus diisi': !fullname,
|
|
552
|
+
'panjang nama minimal adalah 3 karakter': fullname.length < 3,
|
|
553
|
+
[`nama ${ALPHASPACE.text}`]: !ALPHASPACE.re.test(fullname),
|
|
554
|
+
'panjang nama maksimal adalah 40 karakter': fullname.length > 40,
|
|
555
|
+
}),
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
**`providePasswordFieldError(params)`** Specialized validation for password strength (length, uppercase, lowercase,
|
|
560
|
+
numbers, symbols, no spaces).
|
|
561
|
+
|
|
562
|
+
- `params`: `{ field_id, field_value, field_error }`
|
|
563
|
+
|
|
564
|
+
## 👨💻 Author & Creator
|
|
194
565
|
|
|
195
|
-
-
|
|
196
|
-
- `LazyBackground`: Component for lazy loading background images.
|
|
566
|
+
* **Fajar Maulana** - *Initial Work & Creator* - [fajarmaulana-dev](https://github.com/fajarmaulana-dev)
|
|
197
567
|
|
|
198
|
-
## License
|
|
568
|
+
## 📄 License
|
|
199
569
|
|
|
200
|
-
MIT
|
|
570
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/utils/cookie.js
CHANGED
|
@@ -61,9 +61,7 @@ export const removeCookie = (key) => {
|
|
|
61
61
|
*
|
|
62
62
|
* @param keys - Array of keys to remove.
|
|
63
63
|
*/
|
|
64
|
-
export const removeCookies = (keys) =>
|
|
65
|
-
keys.forEach(key => removeCookie(key));
|
|
66
|
-
};
|
|
64
|
+
export const removeCookies = (keys) => keys.forEach(key => removeCookie(key));
|
|
67
65
|
/**
|
|
68
66
|
* Clears all cookies by setting their max age to 0.
|
|
69
67
|
*/
|
package/dist/utils/local.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ export declare const clearLocals: () => void;
|
|
|
8
8
|
* @param key - The key of the item to remove.
|
|
9
9
|
*/
|
|
10
10
|
export declare const removeLocal: (key: string) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Removes multiple localStorage items by keys.
|
|
13
|
+
*
|
|
14
|
+
* @param keys - Array of keys to remove.
|
|
15
|
+
*/
|
|
16
|
+
export declare const removeLocals: (keys: string[]) => void;
|
|
11
17
|
/**
|
|
12
18
|
* Sets an item in localStorage after serializing it to JSON.
|
|
13
19
|
*
|
package/dist/utils/local.js
CHANGED
|
@@ -8,6 +8,12 @@ export const clearLocals = () => localStorage.clear();
|
|
|
8
8
|
* @param key - The key of the item to remove.
|
|
9
9
|
*/
|
|
10
10
|
export const removeLocal = (key) => localStorage.removeItem(key);
|
|
11
|
+
/**
|
|
12
|
+
* Removes multiple localStorage items by keys.
|
|
13
|
+
*
|
|
14
|
+
* @param keys - Array of keys to remove.
|
|
15
|
+
*/
|
|
16
|
+
export const removeLocals = (keys) => keys.forEach(key => removeLocal(key));
|
|
11
17
|
/**
|
|
12
18
|
* Sets an item in localStorage after serializing it to JSON.
|
|
13
19
|
*
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clear sessionStorage.
|
|
3
|
+
*/
|
|
4
|
+
export declare const clearSessions: () => void;
|
|
5
|
+
/**
|
|
6
|
+
* Removes an item from sessionStorage by key.
|
|
7
|
+
*
|
|
8
|
+
* @param key - The key of the item to remove.
|
|
9
|
+
*/
|
|
10
|
+
export declare const removeSession: (key: string) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Removes multiple sessionStorage items by keys.
|
|
13
|
+
*
|
|
14
|
+
* @param keys - Array of keys to remove.
|
|
15
|
+
*/
|
|
16
|
+
export declare const removeSessions: (keys: string[]) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Sets an item in sessionStorage after serializing it to JSON.
|
|
19
|
+
*
|
|
20
|
+
* @param key - The key under which to store the value.
|
|
21
|
+
* @param value - The value to store (will be JSON-stringified).
|
|
22
|
+
*/
|
|
23
|
+
export declare const setSession: <T>(key: string, value: T) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Sets multiple items in sessionStorage after serializing it to JSON.
|
|
26
|
+
*
|
|
27
|
+
* @param items - Array of session storage items to set.
|
|
28
|
+
*/
|
|
29
|
+
export declare const setSessions: <T extends Record<string, unknown>>(items: { [K in keyof T]: {
|
|
30
|
+
key: K & string;
|
|
31
|
+
value: T[K];
|
|
32
|
+
}; }[keyof T][]) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Retrieves and parses a JSON item from sessionStorage by key.
|
|
35
|
+
*
|
|
36
|
+
* @param key - The key of the item to retrieve.
|
|
37
|
+
* @returns The parsed value from sessionStorage, or null if not found.
|
|
38
|
+
*/
|
|
39
|
+
export declare const getSession: <T>(key: string) => T | null;
|
|
40
|
+
/**
|
|
41
|
+
* Retrieves and parses multiple JSON items from sessionStorage by key.
|
|
42
|
+
*
|
|
43
|
+
* @param keys - The keys of the items to retrieve.
|
|
44
|
+
* @returns The parsed values from sessionStorage.
|
|
45
|
+
*/
|
|
46
|
+
export declare const getSessions: <T extends Record<string, unknown>, K extends keyof T = keyof T>(keys: readonly K[]) => { [P in K]: T[P] | null; };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clear sessionStorage.
|
|
3
|
+
*/
|
|
4
|
+
export const clearSessions = () => sessionStorage.clear();
|
|
5
|
+
/**
|
|
6
|
+
* Removes an item from sessionStorage by key.
|
|
7
|
+
*
|
|
8
|
+
* @param key - The key of the item to remove.
|
|
9
|
+
*/
|
|
10
|
+
export const removeSession = (key) => sessionStorage.removeItem(key);
|
|
11
|
+
/**
|
|
12
|
+
* Removes multiple sessionStorage items by keys.
|
|
13
|
+
*
|
|
14
|
+
* @param keys - Array of keys to remove.
|
|
15
|
+
*/
|
|
16
|
+
export const removeSessions = (keys) => keys.forEach(key => removeSession(key));
|
|
17
|
+
/**
|
|
18
|
+
* Sets an item in sessionStorage after serializing it to JSON.
|
|
19
|
+
*
|
|
20
|
+
* @param key - The key under which to store the value.
|
|
21
|
+
* @param value - The value to store (will be JSON-stringified).
|
|
22
|
+
*/
|
|
23
|
+
export const setSession = (key, value) => sessionStorage.setItem(key, JSON.stringify(value));
|
|
24
|
+
/**
|
|
25
|
+
* Sets multiple items in sessionStorage after serializing it to JSON.
|
|
26
|
+
*
|
|
27
|
+
* @param items - Array of session storage items to set.
|
|
28
|
+
*/
|
|
29
|
+
export const setSessions = (items) => {
|
|
30
|
+
items.forEach(item => setSession(item.key, item.value));
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Retrieves and parses a JSON item from sessionStorage by key.
|
|
34
|
+
*
|
|
35
|
+
* @param key - The key of the item to retrieve.
|
|
36
|
+
* @returns The parsed value from sessionStorage, or null if not found.
|
|
37
|
+
*/
|
|
38
|
+
export const getSession = (key) => {
|
|
39
|
+
const item = sessionStorage.getItem(key);
|
|
40
|
+
try {
|
|
41
|
+
if (item)
|
|
42
|
+
return JSON.parse(item);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return item;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Retrieves and parses multiple JSON items from sessionStorage by key.
|
|
51
|
+
*
|
|
52
|
+
* @param keys - The keys of the items to retrieve.
|
|
53
|
+
* @returns The parsed values from sessionStorage.
|
|
54
|
+
*/
|
|
55
|
+
export const getSessions = (keys) => {
|
|
56
|
+
return keys.reduce((acc, key) => {
|
|
57
|
+
acc[key] = getSession(key);
|
|
58
|
+
return acc;
|
|
59
|
+
}, {});
|
|
60
|
+
};
|
package/dist/utils/useApi.js
CHANGED
|
@@ -193,7 +193,7 @@ export default function createApi(options = {}) {
|
|
|
193
193
|
onDownload: shouldTrackDownload
|
|
194
194
|
? progress => {
|
|
195
195
|
if (isMountedRef.current) {
|
|
196
|
-
setState(s => ({ ...s, progress
|
|
196
|
+
setState(s => ({ ...s, progress }));
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
: undefined,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fajarmaulana/komerce-lp-helper",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.16",
|
|
4
4
|
"description": "Helper functions, hooks, and utils for Komerce LP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"tsc-alias": "^1.8.16",
|
|
50
50
|
"typescript": "^5.2.0"
|
|
51
51
|
},
|
|
52
|
-
"author": "Fajar Maulana",
|
|
52
|
+
"author": "Fajar Maulana <fajarmaulana.dev@gmail.com> (https://github.com/fajarmaulana-dev)",
|
|
53
53
|
"license": "MIT",
|
|
54
54
|
"keywords": [
|
|
55
55
|
"react",
|