@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 CHANGED
@@ -10,56 +10,72 @@ npm install @fajarmaulana/komerce-lp-helper
10
10
 
11
11
  ## Features
12
12
 
13
- ### HTTP Client & API Hooks
13
+ ### Components
14
14
 
15
- #### `http`
15
+ #### `Form`
16
16
 
17
- A wrapper around `fetch` with support for interceptors, caching, and retries.
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
- ```typescript
20
- import { http } from 'komerce-lp-helper'
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
- // GET request
23
- const users = await http.get('/users')
27
+ **Usage:**
24
28
 
25
- // POST request with body
26
- await http.post('/users', { name: 'John Doe' })
29
+ ```tsx
30
+ import { Form } from '@fajarmaulana/komerce-lp-helper'
27
31
 
28
- // Using cache
29
- const cachedData = http.getCache('my-key')
30
- ```
32
+ const handleSubmit = (formData: FormData) => {
33
+ console.log(formData.get('username'))
34
+ }
31
35
 
32
- #### `createApi`
36
+ ;<Form action={handleSubmit} className="my-form">
37
+ <input name="username" />
38
+ <button type="submit">Submit</button>
39
+ </Form>
40
+ ```
33
41
 
34
- Creates a new API instance with built-in React hooks (`fetch`, `mutation`, `infinite`) for performing typed data
35
- fetching and mutations with progress tracking.
42
+ #### `LazyBackground`
36
43
 
37
- ```tsx
38
- import { createApi } from 'komerce-lp-helper'
44
+ A wrapper component that lazily loads a background image when it enters the viewport using the Intersection Observer
45
+ API.
39
46
 
40
- const api = createApi({ baseURL: '/api' })
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
- // Fetch
43
- const { data, isLoading, refetch } = api.fetch<User[]>('/users')
53
+ **Usage:**
44
54
 
45
- // Mutation (POST, PUT, DELETE)
46
- const { mutate, isLoading, progress } = api.mutation<User, FormData>('/users', { method: 'POST' })
47
-
48
- // Infinite Fetch
49
- const { data, fetchNextPage } = api.infinite<User[]>('/users', {
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. Useful for delaying expensive operations.
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
- ```typescript
62
- import { useDebounce } from 'komerce-lp-helper'
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
- ```typescript
71
- import { useDebounceFunc } from 'komerce-lp-helper'
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
- #### `useRouter`
104
+ | Parameter | Type | Default | Description |
105
+ | --------- | -------- | ------- | ----------------------------------- |
106
+ | `delay` | `number` | `500` | The debounce delay in milliseconds. |
80
107
 
81
- A custom router API built on top of React Router DOM that provides easier navigation methods and state management.
108
+ **Returns:** `(condition: boolean, callback: () => void) => void` - The function to execute.
82
109
 
83
- ```typescript
84
- import { useRouter } from 'komerce-lp-helper'
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
- const { push, replace, back, query, params } = useRouter()
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
- // Navigate with query params
89
- push({ pathname: '/dashboard', query: { tab: 'settings' } })
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
- ```typescript
97
- import { useQueryParams } from 'komerce-lp-helper'
98
- const [queryObj, updateQuery] = useQueryParams<{ page: string }>()
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
- ```typescript
106
- import { useSlider } from 'komerce-lp-helper'
107
- const slider = useSlider({ data: items })
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
- #### `useForm`
271
+ **2. `mutation<TData, TRequest>(url, config?)`**
111
272
 
112
- Manages form fields, retrieval of values, and error handling for both named inputs and standalone fields.
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
- ```typescript
115
- import { useForm } from 'komerce-lp-helper'
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
- const { form, fields, fieldsWithoutName } = useForm<{ email: string }>(['custom-input-id'])
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
- const handleSubmit = e => {
120
- e.preventDefault()
121
- const data = fields()
122
- console.log(data.email.field_value)
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
- Trigger animations or state changes when a section comes into view.
367
+ **4. `batch<T>(initialRequests?)`**
368
+ Execute multiple API requests in parallel.
129
369
 
130
- ```typescript
131
- import { useSectionObserver } from 'komerce-lp-helper'
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
- const ref = useRef(null)
134
- useSectionObserver({ triggerRef: ref, targetId: 'target-section' })
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
- ### Utilities
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
- - `setCookie({ key, value, maxAge })`: Sets a cookie.
144
- - `setCookies([ ... ])`: Sets multiple cookies.
145
- - `getCookie(key)`: Gets and parses a cookie value.
146
- - `getCookies([keys])`: Retrieves multiple cookies.
147
- - `removeCookie(key)`: Removes a cookie.
148
- - `removeCookies([keys])`: Removes multiple cookies.
149
- - `clearCookies()`: Clears all cookies.
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
- - `setLocal(key, value)`: Stores a value (JSON stringified).
156
- - `setLocals([ ... ])`: Stores multiple values.
157
- - `getLocal(key)`: Retrieves and parses a value.
158
- - `getLocals([keys])`: Retrieves multiple values.
159
- - `removeLocal(key)`: Removes an item.
160
- - `clearLocals()`: Clears local storage.
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
- - `checkImage(url)`: Checks if an image URL is valid.
167
- - `convertBlob(blob)`: Converts a Blob to a Base64 string.
168
- - `extension(mimeType)`: Gets file extension from MIME type.
169
- - `filenameWithExtension(blob, prefix)`: Generates a filename.
170
- - `createDownloadAnchor(blob, options)`: Creates an anchor for downloading.
171
- - `downloadBlob(blob, filename)`: Triggers a file download.
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
- - `getById(id)`: Type-safe `document.getElementById`.
178
- - `getByAny(selector)`: Type-safe `document.querySelector`.
179
- - `clickById(id)`: Triggers a click on an element by ID.
180
- - `focusById(id)`: Focuses an element by ID.
181
- - `handleHashLink(hash, currentHash)`: Smooth scrolling for hash links.
182
- - `acronym(name)`: Generates a 2-letter acronym from a name.
183
- - `isNotPrimitive(value)`: Checks if a value is an object/array.
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
- - `provideFieldError(params)`: Validates a field against a set of rules and updates the error element.
190
- - `providePasswordFieldError(params)`: Specialized validation for password strength (length, uppercase, lowercase,
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
- ### Components
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
- - `Form`: A wrapper component for HTML forms.
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
@@ -12,4 +12,5 @@ export * from './utils/error-provider';
12
12
  export * from './utils/file';
13
13
  export * from './utils/general';
14
14
  export * from './utils/local';
15
+ export * from './utils/session';
15
16
  export { default as createApi } from './utils/useApi';
package/dist/index.js CHANGED
@@ -12,4 +12,5 @@ export * from './utils/error-provider';
12
12
  export * from './utils/file';
13
13
  export * from './utils/general';
14
14
  export * from './utils/local';
15
+ export * from './utils/session';
15
16
  export { default as createApi } from './utils/useApi';
@@ -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
  */
@@ -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
  *
@@ -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
+ };
@@ -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: 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.14",
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",