kmod-cli 1.0.10

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.
Files changed (66) hide show
  1. package/README.md +53 -0
  2. package/bin/gen-components.js +68 -0
  3. package/bin/index.js +153 -0
  4. package/component-templates/components/access-denied.tsx +130 -0
  5. package/component-templates/components/breadcumb.tsx +42 -0
  6. package/component-templates/components/count-down.tsx +94 -0
  7. package/component-templates/components/count-input.tsx +221 -0
  8. package/component-templates/components/date-range-calendar/button.tsx +61 -0
  9. package/component-templates/components/date-range-calendar/calendar.tsx +132 -0
  10. package/component-templates/components/date-range-calendar/date-input.tsx +259 -0
  11. package/component-templates/components/date-range-calendar/date-range-picker.tsx +594 -0
  12. package/component-templates/components/date-range-calendar/label.tsx +31 -0
  13. package/component-templates/components/date-range-calendar/popover.tsx +32 -0
  14. package/component-templates/components/date-range-calendar/select.tsx +125 -0
  15. package/component-templates/components/date-range-calendar/switch.tsx +30 -0
  16. package/component-templates/components/datetime-picker/button.tsx +61 -0
  17. package/component-templates/components/datetime-picker/calendar.tsx +156 -0
  18. package/component-templates/components/datetime-picker/datetime-picker.tsx +75 -0
  19. package/component-templates/components/datetime-picker/input.tsx +20 -0
  20. package/component-templates/components/datetime-picker/label.tsx +18 -0
  21. package/component-templates/components/datetime-picker/period-input.tsx +62 -0
  22. package/component-templates/components/datetime-picker/popover.tsx +32 -0
  23. package/component-templates/components/datetime-picker/select.tsx +125 -0
  24. package/component-templates/components/datetime-picker/time-picker-input.tsx +131 -0
  25. package/component-templates/components/datetime-picker/time-picker-utils.tsx +204 -0
  26. package/component-templates/components/datetime-picker/time-picker.tsx +59 -0
  27. package/component-templates/components/gradient-outline.tsx +233 -0
  28. package/component-templates/components/gradient-svg.tsx +157 -0
  29. package/component-templates/components/grid-layout.tsx +69 -0
  30. package/component-templates/components/hydrate-guard.tsx +40 -0
  31. package/component-templates/components/image.tsx +92 -0
  32. package/component-templates/components/loader-slash-gradient.tsx +85 -0
  33. package/component-templates/components/masonry-gallery.tsx +221 -0
  34. package/component-templates/components/modal.tsx +110 -0
  35. package/component-templates/components/multi-select.tsx +447 -0
  36. package/component-templates/components/non-hydration.tsx +27 -0
  37. package/component-templates/components/portal.tsx +34 -0
  38. package/component-templates/components/segments-circle.tsx +235 -0
  39. package/component-templates/components/single-select.tsx +248 -0
  40. package/component-templates/components/stroke-circle.tsx +57 -0
  41. package/component-templates/components/table/column-table.tsx +15 -0
  42. package/component-templates/components/table/data-table.tsx +339 -0
  43. package/component-templates/components/table/readme.tsx +95 -0
  44. package/component-templates/components/table/table.tsx +60 -0
  45. package/component-templates/components/text-hover-effect.tsx +120 -0
  46. package/component-templates/components/timout-loader.tsx +52 -0
  47. package/component-templates/components/toast.tsx +994 -0
  48. package/component-templates/configs/config.ts +33 -0
  49. package/component-templates/configs/feature-config.tsx +432 -0
  50. package/component-templates/configs/keys.ts +7 -0
  51. package/component-templates/core/api-service.ts +202 -0
  52. package/component-templates/core/calculate.ts +18 -0
  53. package/component-templates/core/idb.ts +166 -0
  54. package/component-templates/core/storage.ts +213 -0
  55. package/component-templates/hooks/count-down.ts +38 -0
  56. package/component-templates/hooks/fade-on-scroll.ts +52 -0
  57. package/component-templates/hooks/safe-action.ts +59 -0
  58. package/component-templates/hooks/spam-guard.ts +31 -0
  59. package/component-templates/lib/utils.ts +6 -0
  60. package/component-templates/providers/feature-guard.tsx +432 -0
  61. package/component-templates/queries/query.tsx +775 -0
  62. package/component-templates/utils/colors/color-by-text.ts +307 -0
  63. package/component-templates/utils/colors/stripe-effect.ts +100 -0
  64. package/component-templates/utils/hash/hash-aes.ts +35 -0
  65. package/components.json +348 -0
  66. package/package.json +60 -0
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import {
3
+ createFeatureDefaults,
4
+ FeatureConfig,
5
+ FlagMap,
6
+ } from './feature-config';
7
+ import { FeatureKey } from './keys';
8
+
9
+ const disableFlags: FlagMap<FeatureKey> = {
10
+ [FeatureKey.HomeCreateProduct]: true,
11
+ [FeatureKey.HomeCreateCustomer]: true,
12
+ [FeatureKey.HomeCreateOrder]: false,
13
+ [FeatureKey.HomeCreateInvoice]: true,
14
+ [FeatureKey.HomeCreatePayment]: true,
15
+ }
16
+
17
+
18
+
19
+ const defaultFlags = createFeatureDefaults(FeatureKey);
20
+
21
+ export const {
22
+ Feature,
23
+ FeatureLock,
24
+ FeatureProvider,
25
+ useFeature,
26
+ useFlags,
27
+ refresh,
28
+ setFlags,
29
+ sources,
30
+ } = FeatureConfig({
31
+ keys: { ...FeatureKey },
32
+ initialFlags: {...defaultFlags, ...disableFlags},
33
+ });
@@ -0,0 +1,432 @@
1
+ // A reusable, library-style Feature Flags system for React/TypeScript
2
+ // ---------------------------------------------------------------
3
+ // Quick start:
4
+ //
5
+ // import { FeatureConfig } from "./feature-config";
6
+ // import { FeatureKey } from "./feature-keys";
7
+ //
8
+ // export const {
9
+ // FeatureProvider,
10
+ // Feature,
11
+ // FeatureLock,
12
+ // useFeature,
13
+ // useFlags,
14
+ // refresh,
15
+ // setFlags,
16
+ // sources,
17
+ // } = FeatureConfig({
18
+ // keys: FeatureKey, // enum or keys array
19
+ // sources: [
20
+ // sources.local({
21
+ // flags: {
22
+ // [FeatureKey.NewDashboard]: false,
23
+ // [FeatureKey.BetaButton]: true,
24
+ // },
25
+ // priority: 0,
26
+ // }),
27
+ // sources.storage({ storageKey: "feature_flags", priority: 5 }),
28
+ // sources.remote({
29
+ // priority: 10, // remote override local
30
+ // fetch: async () => {
31
+ // const res = await fetch("/api/feature-flags");
32
+ // return (await res.json()) as Record<string, boolean>;
33
+ // },
34
+ // transform: (raw) => raw, // optional map
35
+ // }),
36
+ // ],
37
+ // strategy: "last-wins", // "any-true" | "all-true" | custom reducer
38
+ // strict: "warn", // "error" | "warn" | "silent"
39
+ // });
40
+ //
41
+ // // App root
42
+ // <FeatureProvider>{children}</FeatureProvider>
43
+ //
44
+ // // Usage
45
+ // <Feature feature={FeatureKey.NewDashboard}>New UI</Feature>
46
+ // <FeatureLock feature={FeatureKey.BetaButton}>Beta locked</FeatureLock>
47
+ // ---------------------------------------------------------------
48
+
49
+ "use client";
50
+ import {
51
+ createContext,
52
+ ReactNode,
53
+ useContext,
54
+ useEffect,
55
+ useMemo,
56
+ useState,
57
+ } from 'react';
58
+
59
+ // -----------------------------
60
+ // Types
61
+ // -----------------------------
62
+ export type EnumLike = Record<string, string | number> | readonly string[];
63
+
64
+ type InferKeys<E extends EnumLike> = E extends readonly string[]
65
+ ? E[number]
66
+ : E extends Record<string, infer V>
67
+ ? Extract<V, string> | Extract<V, number> extends infer U
68
+ ? Extract<U, string> // we only accept string keys for flags
69
+ : never
70
+ : never;
71
+
72
+ export type FlagMap<K extends string> = Partial<Record<K, boolean>>;
73
+
74
+ export type FeatureSource<K extends string> = {
75
+ name?: string;
76
+ priority?: number; // higher runs later if last-wins
77
+ load: (ctx: {
78
+ abortSignal?: AbortSignal;
79
+ }) => Promise<FlagMap<K>> | FlagMap<K>;
80
+ save?: (flags: FlagMap<K>) => Promise<void> | void; // optional, for writable stores
81
+ };
82
+
83
+ export type MergeStrategy<K extends string> =
84
+ | "last-wins"
85
+ | "any-true"
86
+ | "all-true"
87
+ | ((entries: { name: string; flags: FlagMap<K> }[]) => FlagMap<K>);
88
+
89
+ export type StrictMode = "error" | "warn" | "silent";
90
+
91
+ export interface FeatureOptions<
92
+ E extends EnumLike,
93
+ K extends string = InferKeys<E> & string
94
+ > {
95
+ keys: E; // enum object or readonly string[]
96
+ sources?: FeatureSource<K>[];
97
+ strategy?: MergeStrategy<K>;
98
+ strict?: StrictMode;
99
+ initialFlags?: FlagMap<K>; // SSR hydration or preloaded
100
+ }
101
+
102
+ // -----------------------------
103
+ // Helpers
104
+ // -----------------------------
105
+ function normalizeEnum<E extends EnumLike>(e: E): string[] {
106
+ if (Array.isArray(e)) return e as string[];
107
+ // TS enum has both keys and reverse mapping for numeric enums –
108
+ // we're interested in string values only.
109
+ return Object.values(e).filter((v): v is string => typeof v === "string");
110
+ }
111
+
112
+ function filterUnknownKeys<K extends string>(
113
+ keys: readonly string[],
114
+ flags: FlagMap<K>,
115
+ strict: StrictMode,
116
+ sourceName: string
117
+ ): FlagMap<K> {
118
+ const allowed = new Set(keys);
119
+ const out: FlagMap<K> = {};
120
+ for (const [k, v] of Object.entries(flags)) {
121
+ if (allowed.has(k)) {
122
+ (out as any)[k] = !!v;
123
+ } else if (strict !== "silent") {
124
+ const msg = `[FeatureConfig] Unknown key "${k}" from ${sourceName}`;
125
+ strict === "error" ? console.error(msg) : console.warn(msg);
126
+ }
127
+ }
128
+ return out;
129
+ }
130
+
131
+ function mergeFlags<K extends string>(
132
+ entries: { name: string; flags: FlagMap<K> }[],
133
+ strategy: MergeStrategy<K>
134
+ ): FlagMap<K> {
135
+ if (typeof strategy === "function") return strategy(entries);
136
+
137
+ if (strategy === "any-true") {
138
+ const out: FlagMap<K> = {};
139
+ for (const e of entries) {
140
+ for (const [k, v] of Object.entries(e.flags)) {
141
+ (out as any)[k] = Boolean((out as any)[k]) || Boolean(v);
142
+ }
143
+ }
144
+ return out;
145
+ }
146
+
147
+ if (strategy === "all-true") {
148
+ // Start with union of keys, then AND them across sources
149
+ const out: Record<string, boolean> = {};
150
+ const allKeys = new Set<string>();
151
+ entries.forEach((e) => Object.keys(e.flags).forEach((k) => allKeys.add(k)));
152
+ allKeys.forEach((k) => {
153
+ out[k] = entries.every((e) => e.flags[k as K] === true);
154
+ });
155
+ return out as FlagMap<K>;
156
+ }
157
+
158
+ // default: last-wins (based on order of entries)
159
+ const out: FlagMap<K> = {};
160
+ for (const e of entries) Object.assign(out, e.flags);
161
+ return out;
162
+ }
163
+
164
+ // -----------------------------
165
+ // Built-in Sources (adapters)
166
+ // -----------------------------
167
+ function localSource<K extends string>(opts: {
168
+ flags: FlagMap<K>;
169
+ priority?: number;
170
+ name?: string;
171
+ }): FeatureSource<K> {
172
+ const { flags, priority = 0, name = "local" } = opts;
173
+ return {
174
+ name,
175
+ priority,
176
+ load: () => flags,
177
+ };
178
+ }
179
+
180
+ function storageSource<K extends string>(opts: {
181
+ storageKey: string;
182
+ priority?: number;
183
+ name?: string;
184
+ storage?: Storage | null; // default localStorage if available
185
+ }): FeatureSource<K> {
186
+ const { storageKey, priority = 0, name = "storage", storage } = opts;
187
+ return {
188
+ name,
189
+ priority,
190
+ load: () => {
191
+ try {
192
+ const s =
193
+ storage ??
194
+ (typeof window !== "undefined" ? window.localStorage : null);
195
+ const raw = s?.getItem(storageKey);
196
+ return raw ? (JSON.parse(raw) as FlagMap<K>) : {};
197
+ } catch (e) {
198
+ console.warn(`[FeatureConfig] storageSource load error`, e);
199
+ return {};
200
+ }
201
+ },
202
+ };
203
+ }
204
+
205
+ function remoteSource<K extends string>(opts: {
206
+ fetch: (ctx: {
207
+ abortSignal?: AbortSignal;
208
+ }) => Promise<Record<string, boolean>>;
209
+ transform?: (raw: Record<string, boolean>) => FlagMap<K>;
210
+ priority?: number;
211
+ name?: string;
212
+ }): FeatureSource<K> {
213
+ const { fetch, transform, priority = 10, name = "remote" } = opts;
214
+ return {
215
+ name,
216
+ priority,
217
+ load: async ({ abortSignal }) => {
218
+ const raw = await fetch({ abortSignal });
219
+ return transform ? transform(raw) : (raw as FlagMap<K>);
220
+ },
221
+ };
222
+ }
223
+
224
+ // -----------------------------
225
+ // Factory – FeatureConfig
226
+ // -----------------------------
227
+ export function FeatureConfig<
228
+ E extends EnumLike,
229
+ K extends string = InferKeys<E> & string
230
+ >(options: FeatureOptions<E, K>) {
231
+ const allKeys = normalizeEnum(options.keys);
232
+ const strict: StrictMode = options.strict ?? "warn";
233
+ const strategy: MergeStrategy<K> = options.strategy ?? "last-wins";
234
+
235
+ // Context shape
236
+ type Ctx = {
237
+ flags: FlagMap<K>;
238
+ loading: boolean;
239
+ refresh: () => Promise<void>;
240
+ setFlags: (patch: FlagMap<K> | ((prev: FlagMap<K>) => FlagMap<K>)) => void;
241
+ isEnabled: (k: K) => boolean;
242
+ };
243
+
244
+ const FeatureContext = createContext<Ctx | null>(null);
245
+
246
+ function FeatureProvider({ children }: { children: ReactNode }) {
247
+ const [flags, _setFlags] = useState<FlagMap<K>>(options.initialFlags ?? {});
248
+ const [loading, setLoading] = useState<boolean>(true);
249
+ const sourcesSorted = useMemo(
250
+ () =>
251
+ (options.sources ?? [])
252
+ .map((s, i) => ({
253
+ i,
254
+ s,
255
+ priority: s.priority ?? 0,
256
+ name: s.name ?? `source_${i}`,
257
+ }))
258
+ .sort((a, b) => a.priority - b.priority),
259
+ // eslint-disable-next-line react-hooks/exhaustive-deps
260
+ []
261
+ );
262
+
263
+ const safeSetFlags = (
264
+ next: FlagMap<K> | ((prev: FlagMap<K>) => FlagMap<K>)
265
+ ) => {
266
+ _setFlags((prev) => {
267
+ const nextFlags =
268
+ typeof next === "function" ? (next as any)(prev) : next;
269
+ return nextFlags;
270
+ });
271
+ };
272
+
273
+ const refresh = async (): Promise<void> => {
274
+ if (!sourcesSorted.length) {
275
+ // even without sources, enforce key filtering on initial flags
276
+ setLoading(false);
277
+ return;
278
+ }
279
+ setLoading(true);
280
+ const ctrl = new AbortController();
281
+ const collected: { name: string; flags: FlagMap<K> }[] = [];
282
+
283
+ try {
284
+ for (const { s, name } of sourcesSorted) {
285
+ try {
286
+ const data = await s.load({ abortSignal: ctrl.signal });
287
+ const filtered = filterUnknownKeys<K>(allKeys, data, strict, name);
288
+ collected.push({ name, flags: filtered });
289
+ } catch (e) {
290
+ console.warn(`[FeatureConfig] Source "${name}" load failed`, e);
291
+ collected.push({ name, flags: {} });
292
+ }
293
+ }
294
+ const merged = mergeFlags<K>(collected, strategy);
295
+ // also filter initial flags/options
296
+ const initialFiltered = filterUnknownKeys<K>(
297
+ allKeys,
298
+ flags,
299
+ strict,
300
+ "initial"
301
+ );
302
+ _setFlags({ ...initialFiltered, ...merged });
303
+ } finally {
304
+ setLoading(false);
305
+ }
306
+ };
307
+
308
+ useEffect(() => {
309
+ // On mount: validate initial flags & then fetch from sources
310
+ const initialFiltered = filterUnknownKeys<K>(
311
+ allKeys,
312
+ options.initialFlags ?? {},
313
+ strict,
314
+ "initial"
315
+ );
316
+ if (Object.keys(initialFiltered).length) _setFlags(initialFiltered);
317
+ // Fetch async sources
318
+ refresh();
319
+ // eslint-disable-next-line react-hooks/exhaustive-deps
320
+ }, []);
321
+
322
+ const isEnabled = (k: K) => Boolean(flags[k]);
323
+
324
+ const ctx: Ctx = {
325
+ flags,
326
+ loading,
327
+ refresh,
328
+ setFlags: safeSetFlags,
329
+ isEnabled,
330
+ };
331
+
332
+ return (
333
+ <FeatureContext.Provider value={ctx}>{children}</FeatureContext.Provider>
334
+ );
335
+ }
336
+
337
+ // Hooks & Components
338
+ function useFlags() {
339
+ const ctx = useContext(FeatureContext);
340
+ if (!ctx) throw new Error("useFlags must be used inside FeatureProvider");
341
+ return ctx;
342
+ }
343
+
344
+ function useFeature(key: K) {
345
+ const { isEnabled, loading } = useFlags();
346
+ return { enabled: isEnabled(key), loading };
347
+ }
348
+
349
+ type GateProps = {
350
+ feature: K;
351
+ fallback?: ReactNode;
352
+ loadingFallback?: ReactNode;
353
+ children: ReactNode;
354
+ };
355
+
356
+ function Feature({ feature, fallback = null, loadingFallback = null, children }: GateProps) {
357
+ const { enabled, loading } = useFeature(feature);
358
+ if (loading) return <>{loadingFallback}</>;
359
+ return <>{enabled ? children : fallback}</>;
360
+ }
361
+
362
+ function FeatureLock({ feature, fallback = null, loadingFallback = null, children }: GateProps) {
363
+ const { enabled, loading } = useFeature(feature);
364
+ if (loading) return <>{loadingFallback}</>;
365
+ return <>{enabled ? fallback : children}</>;
366
+ }
367
+
368
+ // public helpers (instance-scoped)
369
+ const api = {
370
+ FeatureProvider,
371
+ useFeature,
372
+ useFlags,
373
+ Feature,
374
+ FeatureLock,
375
+ refresh: () => {
376
+ const ctx = (FeatureContext as any)._currentValue as ReturnType<
377
+ typeof useFlags
378
+ > | null;
379
+ // Note: in strict React this isn't guaranteed across roots; prefer calling useFlags() in components.
380
+ if (ctx?.refresh) return ctx.refresh();
381
+ return Promise.resolve();
382
+ },
383
+ setFlags: (patch: FlagMap<K> | ((prev: FlagMap<K>) => FlagMap<K>)) => {
384
+ const ctx = (FeatureContext as any)._currentValue as ReturnType<
385
+ typeof useFlags
386
+ > | null;
387
+ if (ctx?.setFlags) ctx.setFlags(patch);
388
+ },
389
+ sources: {
390
+ local: localSource<K>,
391
+ storage: storageSource<K>,
392
+ remote: remoteSource<K>,
393
+ },
394
+ } as const;
395
+
396
+ return api;
397
+ }
398
+
399
+ // feature-config-builder
400
+ /**
401
+ * automatically create feature defaults from enum
402
+ * @param keysEnum - enum of feature keys
403
+ * @param manualDefaults - manual overrides for defaults
404
+ * @param globalDefault - global default for all features (default: true)
405
+ * @returns - feature defaults object
406
+ */
407
+ export function createFeatureDefaults<
408
+ T extends Record<string, string> | Record<string, number>
409
+ >(
410
+ keysEnum: T,
411
+ manualDefaults: Partial<Record<T[keyof T], boolean>> = {},
412
+ globalDefault: boolean = true
413
+ ): Record<T[keyof T], boolean> {
414
+ const keys = Object.values(keysEnum) as T[keyof T][];
415
+ const defaults: Record<T[keyof T], boolean> = {} as any;
416
+
417
+ for (const key of keys) {
418
+ defaults[key] = globalDefault;
419
+ }
420
+
421
+ // merge manual override
422
+ return { ...defaults, ...manualDefaults };
423
+ }
424
+
425
+ // -----------------------------
426
+ // Example enum (you can delete this from your build and place in a separate file)
427
+ // -----------------------------
428
+ // export enum FeatureKey {
429
+ // NewDashboard = "newDashboard",
430
+ // BetaButton = "betaButton",
431
+ // AiAssistant = "aiAssistant",
432
+ // }
@@ -0,0 +1,7 @@
1
+ export enum FeatureKey {
2
+ HomeCreateProduct = "homeCreateProduct",
3
+ HomeCreateCustomer = "homeCreateCustomer",
4
+ HomeCreateOrder = "homeCreateOrder",
5
+ HomeCreateInvoice = "homeCreateInvoice",
6
+ HomeCreatePayment = "homeCreatePayment",
7
+ }
@@ -0,0 +1,202 @@
1
+ // const response = await apiHandler.get('/users');
2
+ // const response = await apiHandler.post('/users', { name: 'John Doe' });
3
+ // const response = await apiHandler.put('/users/1', { name: 'Jane Doe' });
4
+ // const response = await apiHandler.delete('/users/1');
5
+
6
+ import axios, {
7
+ AxiosInstance,
8
+ AxiosRequestConfig,
9
+ AxiosResponse,
10
+ } from 'axios';
11
+
12
+ /** Generic API response */
13
+ export interface ApiResponse<T> {
14
+ data: T;
15
+ message: string;
16
+ status: number;
17
+ }
18
+
19
+ /** Custom error class */
20
+ export class ApiError extends Error {
21
+ data: any;
22
+ status: number;
23
+
24
+ constructor(message: string, data: any, status: number) {
25
+ super(message);
26
+ this.name = "ApiError";
27
+ this.data = data;
28
+ this.status = status;
29
+ }
30
+ }
31
+
32
+ export type ApiServiceOptions = {
33
+ baseURL?: string;
34
+ /** Callback trả về token runtime, có thể từ localStorage, cookie, context, etc. */
35
+ getToken?: () => string | null;
36
+ }
37
+
38
+ export class ApiServices {
39
+ private axiosInstance: AxiosInstance;
40
+ private getToken?: () => string | null;
41
+
42
+ /**
43
+ * Constructor
44
+ *
45
+ * @param {ApiServiceOptions} [options] Options
46
+ * @param {string} [options.baseURL] Base URL of API
47
+ * @param {() => string | null} [options.getToken] Callback trả về token runtime
48
+ */
49
+ constructor(options?: ApiServiceOptions) {
50
+ const baseURL =
51
+ options?.baseURL ||
52
+ (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_API_URL : undefined) ||
53
+ "/api";
54
+
55
+ this.getToken = options?.getToken;
56
+
57
+ this.axiosInstance = axios.create({ baseURL });
58
+ this.initializeInterceptors();
59
+ }
60
+
61
+ /**
62
+ * Initialize interceptors
63
+ *
64
+ * @private
65
+ * @memberof ApiServices
66
+ */
67
+ private initializeInterceptors() {
68
+ this.axiosInstance.interceptors.request.use((config) => {
69
+ const token = this.getToken?.();
70
+ if (token) {
71
+ if (!config.headers) config.headers = { ...{} } as typeof config.headers;
72
+ config.headers["Authorization"] = `Bearer ${token}`;
73
+ }
74
+ return config;
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Send a request to API
80
+ *
81
+ * @param {("get" | "post" | "put" | "delete")} method Request method
82
+ * @param {string} url Request URL
83
+ * @param {any} [data] Data to send in request body
84
+ * @param {AxiosRequestConfig} [config] Configuration for axios
85
+ * @returns {Promise<ApiResponse<T>>} Promise resolves to ApiResponse
86
+ * @throws {ApiError} If request fails
87
+ *
88
+ * @private
89
+ * @memberof ApiServices
90
+ */
91
+ private async request<T>(
92
+ method: "get" | "post" | "put" | "delete" | "patch",
93
+ url: string,
94
+ data?: any,
95
+ config?: AxiosRequestConfig
96
+ ): Promise<ApiResponse<T>> {
97
+ try {
98
+ const response: AxiosResponse<T> =
99
+ method === "get"
100
+ ? await this.axiosInstance.get(url, config)
101
+ : method === "post"
102
+ ? await this.axiosInstance.post(url, data, config)
103
+ : method === "put"
104
+ ? await this.axiosInstance.put(url, data, config)
105
+ : await this.axiosInstance.delete(url, config);
106
+
107
+ return { data: response.data, message: "Success", status: response.status };
108
+ } catch (error: any) {
109
+ if (error.response) {
110
+ throw new ApiError(error.response.data?.message || "Server Error", error.response.data, error.response.status);
111
+ } else if (error.request) {
112
+ throw new ApiError("No response from server", null, 0);
113
+ } else {
114
+ throw new ApiError(error.message, null, -1);
115
+ }
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Send a GET request to API
121
+ *
122
+ * @param {string} url Request URL
123
+ * @param {AxiosRequestConfig} [config] Configuration for axios
124
+ * @returns {Promise<ApiResponse<T>>} Promise resolves to ApiResponse
125
+ * @throws {ApiError} If request fails
126
+ *
127
+ * @public
128
+ * @memberof ApiServices
129
+ */
130
+ get<T>(url: string, config?: AxiosRequestConfig) {
131
+ return this.request<T>("get", url, undefined, config);
132
+ }
133
+
134
+ /**
135
+ * Send a POST request to API
136
+ *
137
+ * @param {string} url Request URL
138
+ * @param {any} data Request body
139
+ * @param {AxiosRequestConfig} [config] Configuration for axios
140
+ * @returns {Promise<ApiResponse<T>>} Promise resolves to ApiResponse
141
+ * @throws {ApiError} If request fails
142
+ *
143
+ * @public
144
+ * @memberof ApiServices
145
+ */
146
+ post<T>(url: string, data: any, config?: AxiosRequestConfig) {
147
+ return this.request<T>("post", url, data, config);
148
+ }
149
+
150
+ /**
151
+ * Send a PUT request to API
152
+ *
153
+ * @param {string} url Request URL
154
+ * @param {any} data Request body
155
+ * @param {AxiosRequestConfig} [config] Configuration for axios
156
+ * @returns {Promise<ApiResponse<T>>} Promise resolves to ApiResponse
157
+ * @throws {ApiError} If request fails
158
+ *
159
+ * @public
160
+ * @memberof ApiServices
161
+ */
162
+
163
+ put<T>(url: string, data: any, config?: AxiosRequestConfig) {
164
+ return this.request<T>("put", url, data, config);
165
+ }
166
+
167
+ /**
168
+ * Send a PATCH request to API
169
+ *
170
+ * @param {string} url Request URL
171
+ * @param {any} data Request body
172
+ * @param {AxiosRequestConfig} [config] Configuration for axios
173
+ * @returns {Promise<ApiResponse<T>>} Promise resolves to ApiResponse
174
+ * @throws {ApiError} If request fails
175
+ *
176
+ * @public
177
+ * @memberof ApiServices
178
+ */
179
+ patch<T>(url: string, data: any, config?: AxiosRequestConfig) {
180
+ return this.request<T>("patch", url, data, config);
181
+ }
182
+
183
+ /**
184
+ * Send a DELETE request to API
185
+ *
186
+ * @param {string} url Request URL
187
+ * @param {AxiosRequestConfig} [config] Configuration for axios
188
+ * @returns {Promise<ApiResponse<T>>} Promise resolves to ApiResponse
189
+ * @throws {ApiError} If request fails
190
+ *
191
+ * @public
192
+ * @memberof ApiServices
193
+ */
194
+ delete<T>(url: string, config?: AxiosRequestConfig) {
195
+ return this.request<T>("delete", url, undefined, config);
196
+ }
197
+ }
198
+
199
+ /** Default singleton handler, uses token from localStorage */
200
+ export const apiHandler = new ApiServices({
201
+ getToken: () => (typeof window !== "undefined" ? localStorage.getItem("token") : null),
202
+ });
@@ -0,0 +1,18 @@
1
+ export type SegmentsNumberProps = {
2
+ percent?: number;
3
+ segmentsCount?: number;
4
+ }
5
+ /**
6
+ * Calculate the number of segments required for a progress circle.
7
+ * @param {{percent?: number; segmentsCount?: number;}} [props] - Optional props for calculation.
8
+ * @param {number} [props.percent=90] - The percentage of circle to be taken up by the progress.
9
+ * @param {number} [props.segmentsCount=10] - The number of segments to use in the circle.
10
+ * @returns {number} The number of segments required.
11
+ */
12
+ export const segmentsNumber = ({percent, segmentsCount}: SegmentsNumberProps): number => {
13
+ if(!percent || !segmentsCount) {
14
+ return 0
15
+ }
16
+ const segments = (segmentsCount * (percent / 100) * segmentsCount).toFixed(0)
17
+ return Number(segments);
18
+ }