@lasterp/shared 1.0.0-alpha.8 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,28 +1,101 @@
1
1
  interface Item {
2
- itemCode: string;
2
+ item_code: string;
3
3
  area: string;
4
4
  model: string;
5
5
  region: string;
6
6
  grade: string;
7
- gradeIssuer: string;
7
+ grade_issuer: string;
8
8
  color: string;
9
9
  storage: string;
10
10
  memory: string;
11
11
  connectivity: string;
12
- carrierCompatibility: string;
12
+ carrier_compatibility: string;
13
13
  }
14
14
  interface ItemVariant extends Item {
15
- itemVariant: string;
15
+ item_variant: string;
16
16
  }
17
17
  interface Model {
18
- modelNumber: string;
19
- simCardType: string;
18
+ model_number: string;
19
+ sim_card_type: string;
20
20
  connectivity: string;
21
21
  }
22
22
  interface Colour {
23
23
  color: string;
24
24
  hex: string;
25
- colorSystem: string;
25
+ color_system: string;
26
+ }
27
+ interface Globals {
28
+ header: Header;
29
+ footer: Footer;
30
+ }
31
+ interface Brand {
32
+ brandImage?: string;
33
+ }
34
+ interface NavbarItem {
35
+ label: string;
36
+ enableDropdown: boolean;
37
+ enableLink: boolean;
38
+ link?: string;
39
+ dropdownDescription?: string;
40
+ dropdownCTALabel?: string;
41
+ dropdownCTALink?: string;
42
+ groups?: NavbarSubItemGroup[];
43
+ }
44
+ interface NavbarSubItemGroup {
45
+ title: string;
46
+ items: NavbarSubItem[];
47
+ }
48
+ interface NavbarSubItem {
49
+ label: string;
50
+ description?: string;
51
+ image?: string;
52
+ link?: string;
53
+ }
54
+ interface Topbar {
55
+ enabled: boolean;
56
+ layout: string;
57
+ items: TopbarItem[];
58
+ }
59
+ interface TopbarItem {
60
+ icon?: string;
61
+ label: string;
62
+ link?: string;
63
+ }
64
+ interface Header {
65
+ headerType?: string;
66
+ brand: Brand;
67
+ tabs: NavbarItem[];
68
+ topbar: Topbar;
69
+ }
70
+ interface FooterItemGroup {
71
+ title: string;
72
+ items: FooterItem[];
73
+ }
74
+ interface FooterItem {
75
+ label: string;
76
+ link?: string;
77
+ }
78
+ interface Footer {
79
+ footerType?: string;
80
+ groups: FooterItemGroup[];
81
+ copyright?: string;
82
+ address?: string;
83
+ country?: string;
84
+ phone?: string;
85
+ email?: string;
86
+ }
87
+ interface Hero {
88
+ type: string;
89
+ data: Record<string, unknown>;
90
+ }
91
+ interface Block {
92
+ type: string;
93
+ data: Record<string, unknown>;
94
+ }
95
+ interface Page {
96
+ slug: string;
97
+ hero?: Hero;
98
+ blocks: Block[];
26
99
  }
27
100
  import { FrappeApp, FrappeAuth, FrappeCall } from "frappe-js-sdk";
28
101
  import { FrappeDB } from "frappe-js-sdk/lib/db";
@@ -41,6 +114,15 @@ interface FrappeConfig {
41
114
  enableSocket?: boolean;
42
115
  socketPort?: string;
43
116
  }
117
+ interface FrappeError {
118
+ httpStatus: number;
119
+ httpStatusText: string;
120
+ message: string;
121
+ exception: string;
122
+ exc_type?: string;
123
+ traceback?: string;
124
+ _server_messages?: string;
125
+ }
44
126
  interface TokenParams {
45
127
  /** Whether to use token for API calls */
46
128
  useToken: boolean;
@@ -119,11 +201,19 @@ declare const useFrappeGetCall: <T = any>(method: string, params?: Record<string
119
201
  retry?: boolean | number;
120
202
  }, type?: "GET" | "POST") => {
121
203
  data: T | undefined;
122
- error: Error | null;
204
+ error: FrappeError | null;
123
205
  isLoading: boolean;
124
206
  isFetching: boolean;
125
207
  refetch: () => void;
126
208
  };
209
+ interface FrappeMutationResult<T = any> {
210
+ call: (params: Record<string, any>) => Promise<T>;
211
+ result: T | null;
212
+ loading: boolean;
213
+ error: FrappeError | null;
214
+ isCompleted: boolean;
215
+ reset: () => void;
216
+ }
127
217
  /**
128
218
  *
129
219
  * @param method - name of the method to call (POST request) (will be dotted path e.g. "frappe.client.set_value")
@@ -138,7 +228,7 @@ declare const useFrappeGetCall: <T = any>(method: string, params?: Record<string
138
228
  * }
139
229
  *
140
230
  */
141
- declare const useFrappePostCall: unknown;
231
+ declare const useFrappePostCall: <T = any>(method: string) => FrappeMutationResult<T>;
142
232
  /**
143
233
  *
144
234
  * @param method - name of the method to call (PUT request) (will be dotted path e.g. "frappe.client.set_value")
@@ -152,7 +242,7 @@ declare const useFrappePostCall: unknown;
152
242
  * const message = await call({ doctype: "User", docname: "test@example.com", fieldname: "full_name", value: "John Doe" })
153
243
  * }
154
244
  */
155
- declare const useFrappePutCall: unknown;
245
+ declare const useFrappePutCall: <T = any>(method: string) => FrappeMutationResult<T>;
156
246
  /**
157
247
  *
158
248
  * @param method - name of the method to call (DELETE request) (will be dotted path e.g. "frappe.client.delete")
@@ -166,7 +256,7 @@ declare const useFrappePutCall: unknown;
166
256
  * const message = await call({ doctype: "User", docname: "test@example.com" })
167
257
  * }
168
258
  */
169
- declare const useFrappeDeleteCall: unknown;
259
+ declare const useFrappeDeleteCall: <T = any>(method: string) => FrappeMutationResult<T>;
170
260
  import { Filter } from "frappe-js-sdk/lib/db/types";
171
261
  /**
172
262
  * Hook to fetch number of documents from the database
@@ -188,7 +278,7 @@ declare const useFrappeGetDocCount: <T = any>(doctype: string, filters?: Filter<
188
278
  retry?: boolean | number;
189
279
  }) => {
190
280
  data: number | undefined;
191
- error: Error | null;
281
+ error: FrappeError | null;
192
282
  isLoading: boolean;
193
283
  isFetching: boolean;
194
284
  refetch: () => void;
@@ -209,7 +299,7 @@ import { FrappeDoc } from "frappe-js-sdk/lib/db/types";
209
299
  declare const useFrappeCreateDoc: <T = any>() => {
210
300
  createDoc: (doctype: string, doc: T) => Promise<FrappeDoc<T>>;
211
301
  loading: boolean;
212
- error: Error | null | undefined;
302
+ error: FrappeError | null;
213
303
  isCompleted: boolean;
214
304
  reset: () => void;
215
305
  };
@@ -230,7 +320,7 @@ declare const useFrappeDeleteDoc: <T = any>() => {
230
320
  message: string;
231
321
  }>;
232
322
  loading: boolean;
233
- error: Error | null | undefined;
323
+ error: FrappeError | null;
234
324
  isCompleted: boolean;
235
325
  reset: () => void;
236
326
  };
@@ -382,7 +472,7 @@ declare const useFrappeGetDoc: <T = any>(doctype: string, name?: string, options
382
472
  retry?: boolean | number;
383
473
  }) => {
384
474
  data: FrappeDoc2<T> | undefined;
385
- error: Error | null;
475
+ error: FrappeError | null;
386
476
  isLoading: boolean;
387
477
  isFetching: boolean;
388
478
  refetch: () => void;
@@ -421,7 +511,7 @@ declare const useFrappeGetDocList: <
421
511
  retry?: boolean | number;
422
512
  }) => {
423
513
  data: T[] | undefined;
424
- error: Error | null;
514
+ error: FrappeError | null;
425
515
  isLoading: boolean;
426
516
  isFetching: boolean;
427
517
  refetch: () => void;
@@ -471,7 +561,7 @@ declare const useSearch: (doctype: string, text: string, filters?: Filter2[], li
471
561
  data: {
472
562
  message: SearchResult[];
473
563
  } | undefined;
474
- error: Error | null;
564
+ error: FrappeError | null;
475
565
  isLoading: boolean;
476
566
  isFetching: boolean;
477
567
  refetch: () => void;
@@ -492,83 +582,66 @@ import { FrappeDoc as FrappeDoc4 } from "frappe-js-sdk/lib/db/types";
492
582
  declare const useFrappeUpdateDoc: <T = any>() => {
493
583
  updateDoc: (doctype: string, docname: string | null, doc: Partial<T>) => Promise<FrappeDoc4<T>>;
494
584
  loading: boolean;
495
- error: Error | null | undefined;
585
+ error: FrappeError | null;
496
586
  isCompleted: boolean;
497
587
  reset: () => void;
498
588
  };
499
- interface Globals {
500
- header: Header;
501
- footer: Footer;
502
- }
503
- interface Brand {
504
- brandImage?: string;
505
- }
506
- interface NavbarItem {
507
- label: string;
508
- enableDropdown: boolean;
509
- enableLink: boolean;
510
- link?: string;
511
- dropdownDescription?: string;
512
- dropdownCTALabel?: string;
513
- dropdownCTALink?: string;
514
- groups?: NavbarSubItemGroup[];
515
- }
516
- interface NavbarSubItemGroup {
517
- title: string;
518
- items: NavbarSubItem[];
519
- }
520
- interface NavbarSubItem {
521
- label: string;
522
- description?: string;
523
- image?: string;
524
- link?: string;
525
- }
526
- interface Topbar {
527
- enabled: boolean;
528
- layout: string;
529
- items: TopbarItem[];
589
+ interface VariantSelectorProps<T extends {
590
+ id: string;
591
+ specs: Record<string, string>;
592
+ }> {
593
+ variants: T[];
594
+ attributes: {
595
+ key: string;
596
+ values: string[];
597
+ }[];
598
+ defaultId?: string;
530
599
  }
531
- interface TopbarItem {
532
- icon?: string;
533
- label: string;
534
- link?: string;
535
- }
536
- interface Header {
537
- headerType?: string;
538
- brand: Brand;
539
- tabs: NavbarItem[];
540
- topbar: Topbar;
600
+ interface VariantSelectorOption {
601
+ key: string;
602
+ value: string;
603
+ state: "selected" | "available" | "unavailable";
541
604
  }
542
- interface FooterItemGroup {
543
- title: string;
544
- items: FooterItem[];
605
+ interface VariantSelectorResult {
606
+ variantId?: string;
607
+ onOptionSelect: (key: string, value: string) => void;
608
+ getOptions: (key: string) => VariantSelectorOption[];
545
609
  }
546
- interface FooterItem {
547
- label: string;
548
- link?: string;
610
+ declare const useVariantSelector: <T extends {
611
+ id: string;
612
+ specs: Record<string, string>;
613
+ }>(props: VariantSelectorProps<T>) => VariantSelectorResult;
614
+ interface Category {
615
+ name: string;
549
616
  }
550
- interface Footer {
551
- footerType?: string;
552
- groups: FooterItemGroup[];
553
- copyright?: string;
554
- address?: string;
555
- country?: string;
556
- phone?: string;
557
- email?: string;
617
+ interface Product {
618
+ name: string;
619
+ image: string;
620
+ item_code: string;
621
+ description: string;
558
622
  }
559
- interface Hero {
560
- type: string;
561
- data: Record<string, unknown>;
623
+ interface ProductVariant extends ItemVariant {
624
+ name: string;
625
+ currency: string;
626
+ rate: number;
627
+ qty: number;
628
+ images: string[];
629
+ specs: Record<string, string>;
562
630
  }
563
- interface Block {
564
- type: string;
565
- data: Record<string, unknown>;
631
+ interface ShopContext {
632
+ categories: Category[];
633
+ grades: string[];
634
+ selected_category?: Category;
635
+ selected_grade?: string;
636
+ products: Product[];
566
637
  }
567
- interface Page {
568
- slug: string;
569
- hero?: Hero;
570
- blocks: Block[];
638
+ interface ProductContext {
639
+ product: Product;
640
+ specs: Record<string, string[]>;
641
+ variants: ProductVariant[];
642
+ models: Record<string, Model>;
643
+ colors: Record<string, Colour>;
571
644
  }
572
645
  import { camelize, decamelize, camelizeKeys, decamelizeKeys } from "humps";
573
646
  declare function equalsIgnoreCase(str1: string, str2: string): boolean;
574
- export { useSearch, useFrappeUpdateDoc, useFrappePutCall, useFrappePrefetchDoc, useFrappePostCall, useFrappeGetDocList, useFrappeGetDocCount, useFrappeGetDoc, useFrappeGetCall, useFrappeFileUpload, useFrappeEventListener, useFrappeDocumentEventListener, useFrappeDocTypeEventListener, useFrappeDeleteDoc, useFrappeDeleteCall, useFrappeCreateDoc, useFrappeAuth, equalsIgnoreCase, decamelizeKeys, decamelize, camelizeKeys, camelize, ViewerEventData, UseFrappeFileUploadReturnType, TopbarItem, Topbar, TokenParams, SearchResult, Page, NavbarSubItemGroup, NavbarSubItem, NavbarItem, Model, ItemVariant, Item, Hero, Header, Globals, FrappeProviderProps, FrappeProvider, FrappeFileUploadResponse, FrappeContext, FrappeConfig, FooterItemGroup, FooterItem, Footer, DocumentUpdateEventData, DocTypeListUpdateEventData, Colour, Brand, Block };
647
+ export { useVariantSelector, useSearch, useFrappeUpdateDoc, useFrappePutCall, useFrappePrefetchDoc, useFrappePostCall, useFrappeGetDocList, useFrappeGetDocCount, useFrappeGetDoc, useFrappeGetCall, useFrappeFileUpload, useFrappeEventListener, useFrappeDocumentEventListener, useFrappeDocTypeEventListener, useFrappeDeleteDoc, useFrappeDeleteCall, useFrappeCreateDoc, useFrappeAuth, equalsIgnoreCase, decamelizeKeys, decamelize, camelizeKeys, camelize, ViewerEventData, VariantSelectorResult, VariantSelectorProps, VariantSelectorOption, UseFrappeFileUploadReturnType, TopbarItem, Topbar, TokenParams, ShopContext, SearchResult, ProductVariant, ProductContext, Product, Page, NavbarSubItemGroup, NavbarSubItem, NavbarItem, Model, ItemVariant, Item, Hero, Header, Globals, FrappeProviderProps, FrappeProvider, FrappeMutationResult, FrappeFileUploadResponse, FrappeError, FrappeContext, FrappeConfig, FooterItemGroup, FooterItem, Footer, DocumentUpdateEventData, DocTypeListUpdateEventData, Colour, Category, Brand, Block };
@@ -46,7 +46,7 @@ class SocketIO {
46
46
  }
47
47
 
48
48
  // src/frappe/provider.tsx
49
- import { jsxDEV } from "react/jsx-dev-runtime";
49
+ import { jsx } from "react/jsx-runtime";
50
50
 
51
51
  var FrappeContext = createContext(null);
52
52
  var FrappeProvider = ({
@@ -71,10 +71,10 @@ var FrappeProvider = ({
71
71
  socket: enableSocket ? new SocketIO(url, siteName, socketPort, tokenParams).socket : undefined
72
72
  };
73
73
  }, [url, tokenParams, enableSocket, socketPort, siteName, customHeaders]);
74
- return /* @__PURE__ */ jsxDEV(FrappeContext.Provider, {
74
+ return /* @__PURE__ */ jsx(FrappeContext.Provider, {
75
75
  value: frappeConfig,
76
76
  children
77
- }, undefined, false, undefined, this);
77
+ });
78
78
  };
79
79
  // src/frappe/hooks/auth.ts
80
80
  import { useCallback, useContext, useState, useEffect } from "react";
@@ -465,12 +465,118 @@ var useFrappeUpdateDoc = () => {
465
465
  reset
466
466
  };
467
467
  };
468
+ // src/hooks/use-variant-selector/hook.ts
469
+ import { useCallback as useCallback4, useMemo as useMemo2, useState as useState5 } from "react";
470
+
471
+ // src/hooks/use-variant-selector/utils.ts
472
+ function findVariant(variants, specs, caseInsensitive) {
473
+ return variants.find((variant) => {
474
+ return Object.entries(specs).every(([key, value]) => {
475
+ const vv = variant.specs[key];
476
+ if (!vv)
477
+ return false;
478
+ if (caseInsensitive) {
479
+ return vv.toLowerCase() === value.toLowerCase();
480
+ }
481
+ return vv === value;
482
+ });
483
+ });
484
+ }
485
+ function findVariants(variants, specs, caseInsensitive) {
486
+ return variants.filter((variant) => {
487
+ return Object.entries(specs).every(([key, value]) => {
488
+ const vv = variant.specs[key];
489
+ if (!vv)
490
+ return false;
491
+ if (caseInsensitive) {
492
+ return vv.toLowerCase() === value.toLowerCase();
493
+ }
494
+ return vv === value;
495
+ });
496
+ });
497
+ }
498
+
499
+ // src/hooks/use-variant-selector/hook.ts
500
+ var useVariantSelector = (props) => {
501
+ const { variants, attributes, defaultId } = props;
502
+ const [selectedSpecs, setSelectedSpecs] = useState5(() => {
503
+ if (defaultId) {
504
+ const variant = variants.find((v) => v.id === defaultId);
505
+ return variant?.specs || {};
506
+ }
507
+ return {};
508
+ });
509
+ const variantId = useMemo2(() => {
510
+ const complete = attributes.every((attr) => selectedSpecs[attr.key]);
511
+ if (!complete)
512
+ return;
513
+ const variant = findVariant(variants, selectedSpecs);
514
+ return variant?.id;
515
+ }, [variants, selectedSpecs, attributes]);
516
+ const options = useMemo2(() => {
517
+ const result = {};
518
+ attributes.forEach((attr, attrIndex) => {
519
+ const constraints = {};
520
+ for (let i = 0;i < attrIndex; i++) {
521
+ const key = attributes[i]?.key;
522
+ if (key && selectedSpecs[key])
523
+ constraints[key] = selectedSpecs[key];
524
+ }
525
+ const matchingVariants = findVariants(variants, constraints);
526
+ const availableValues = new Set(matchingVariants.map((v) => v.specs[attr.key]));
527
+ result[attr.key] = attr.values.map((value) => {
528
+ const isSelected = selectedSpecs[attr.key] === value;
529
+ const isAvailable = availableValues.has(value);
530
+ return {
531
+ key: attr.key,
532
+ value,
533
+ state: isSelected ? "selected" : isAvailable ? "available" : "unavailable"
534
+ };
535
+ });
536
+ });
537
+ return result;
538
+ }, [variants, attributes, selectedSpecs]);
539
+ const onOptionSelect = useCallback4((key, value) => {
540
+ setSelectedSpecs((prev) => {
541
+ const newSpecs = { ...prev, [key]: value };
542
+ const attrIndex = attributes.findIndex((a) => a.key === key);
543
+ for (let i = attrIndex + 1;i < attributes.length; i++) {
544
+ const downstreamAttr = attributes[i];
545
+ if (!downstreamAttr)
546
+ continue;
547
+ const constraints = {};
548
+ for (let j = 0;j < i; j++) {
549
+ const k = attributes[j]?.key;
550
+ if (k && newSpecs[k])
551
+ constraints[k] = newSpecs[k];
552
+ }
553
+ const matching = findVariants(variants, constraints);
554
+ const availableValues = new Set(matching.map((v) => v.specs[downstreamAttr.key]));
555
+ if (!availableValues.has(newSpecs[downstreamAttr.key])) {
556
+ const firstAvailable = downstreamAttr.values.find((v) => availableValues.has(v));
557
+ if (firstAvailable) {
558
+ newSpecs[downstreamAttr.key] = firstAvailable;
559
+ } else {
560
+ delete newSpecs[downstreamAttr.key];
561
+ }
562
+ }
563
+ }
564
+ return newSpecs;
565
+ });
566
+ }, [attributes, variants]);
567
+ return {
568
+ variantId,
569
+ onOptionSelect,
570
+ getOptions: (key) => options[key] || []
571
+ };
572
+ };
468
573
  // src/utils/char.ts
469
574
  import { camelize, decamelize, camelizeKeys, decamelizeKeys } from "humps";
470
575
  function equalsIgnoreCase(str1, str2) {
471
576
  return str1.localeCompare(str2, undefined, { sensitivity: "accent" }) === 0;
472
577
  }
473
578
  export {
579
+ useVariantSelector,
474
580
  useSearch,
475
581
  useFrappeUpdateDoc,
476
582
  useFrappePutCall,
@@ -1,28 +1,101 @@
1
1
  interface Item {
2
- itemCode: string;
2
+ item_code: string;
3
3
  area: string;
4
4
  model: string;
5
5
  region: string;
6
6
  grade: string;
7
- gradeIssuer: string;
7
+ grade_issuer: string;
8
8
  color: string;
9
9
  storage: string;
10
10
  memory: string;
11
11
  connectivity: string;
12
- carrierCompatibility: string;
12
+ carrier_compatibility: string;
13
13
  }
14
14
  interface ItemVariant extends Item {
15
- itemVariant: string;
15
+ item_variant: string;
16
16
  }
17
17
  interface Model {
18
- modelNumber: string;
19
- simCardType: string;
18
+ model_number: string;
19
+ sim_card_type: string;
20
20
  connectivity: string;
21
21
  }
22
22
  interface Colour {
23
23
  color: string;
24
24
  hex: string;
25
- colorSystem: string;
25
+ color_system: string;
26
+ }
27
+ interface Globals {
28
+ header: Header;
29
+ footer: Footer;
30
+ }
31
+ interface Brand {
32
+ brandImage?: string;
33
+ }
34
+ interface NavbarItem {
35
+ label: string;
36
+ enableDropdown: boolean;
37
+ enableLink: boolean;
38
+ link?: string;
39
+ dropdownDescription?: string;
40
+ dropdownCTALabel?: string;
41
+ dropdownCTALink?: string;
42
+ groups?: NavbarSubItemGroup[];
43
+ }
44
+ interface NavbarSubItemGroup {
45
+ title: string;
46
+ items: NavbarSubItem[];
47
+ }
48
+ interface NavbarSubItem {
49
+ label: string;
50
+ description?: string;
51
+ image?: string;
52
+ link?: string;
53
+ }
54
+ interface Topbar {
55
+ enabled: boolean;
56
+ layout: string;
57
+ items: TopbarItem[];
58
+ }
59
+ interface TopbarItem {
60
+ icon?: string;
61
+ label: string;
62
+ link?: string;
63
+ }
64
+ interface Header {
65
+ headerType?: string;
66
+ brand: Brand;
67
+ tabs: NavbarItem[];
68
+ topbar: Topbar;
69
+ }
70
+ interface FooterItemGroup {
71
+ title: string;
72
+ items: FooterItem[];
73
+ }
74
+ interface FooterItem {
75
+ label: string;
76
+ link?: string;
77
+ }
78
+ interface Footer {
79
+ footerType?: string;
80
+ groups: FooterItemGroup[];
81
+ copyright?: string;
82
+ address?: string;
83
+ country?: string;
84
+ phone?: string;
85
+ email?: string;
86
+ }
87
+ interface Hero {
88
+ type: string;
89
+ data: Record<string, unknown>;
90
+ }
91
+ interface Block {
92
+ type: string;
93
+ data: Record<string, unknown>;
94
+ }
95
+ interface Page {
96
+ slug: string;
97
+ hero?: Hero;
98
+ blocks: Block[];
26
99
  }
27
100
  import { FrappeApp, FrappeAuth, FrappeCall } from "frappe-js-sdk";
28
101
  import { FrappeDB } from "frappe-js-sdk/lib/db";
@@ -41,6 +114,15 @@ interface FrappeConfig {
41
114
  enableSocket?: boolean;
42
115
  socketPort?: string;
43
116
  }
117
+ interface FrappeError {
118
+ httpStatus: number;
119
+ httpStatusText: string;
120
+ message: string;
121
+ exception: string;
122
+ exc_type?: string;
123
+ traceback?: string;
124
+ _server_messages?: string;
125
+ }
44
126
  interface TokenParams {
45
127
  /** Whether to use token for API calls */
46
128
  useToken: boolean;
@@ -119,11 +201,19 @@ declare const useFrappeGetCall: <T = any>(method: string, params?: Record<string
119
201
  retry?: boolean | number;
120
202
  }, type?: "GET" | "POST") => {
121
203
  data: T | undefined;
122
- error: Error | null;
204
+ error: FrappeError | null;
123
205
  isLoading: boolean;
124
206
  isFetching: boolean;
125
207
  refetch: () => void;
126
208
  };
209
+ interface FrappeMutationResult<T = any> {
210
+ call: (params: Record<string, any>) => Promise<T>;
211
+ result: T | null;
212
+ loading: boolean;
213
+ error: FrappeError | null;
214
+ isCompleted: boolean;
215
+ reset: () => void;
216
+ }
127
217
  /**
128
218
  *
129
219
  * @param method - name of the method to call (POST request) (will be dotted path e.g. "frappe.client.set_value")
@@ -138,7 +228,7 @@ declare const useFrappeGetCall: <T = any>(method: string, params?: Record<string
138
228
  * }
139
229
  *
140
230
  */
141
- declare const useFrappePostCall: unknown;
231
+ declare const useFrappePostCall: <T = any>(method: string) => FrappeMutationResult<T>;
142
232
  /**
143
233
  *
144
234
  * @param method - name of the method to call (PUT request) (will be dotted path e.g. "frappe.client.set_value")
@@ -152,7 +242,7 @@ declare const useFrappePostCall: unknown;
152
242
  * const message = await call({ doctype: "User", docname: "test@example.com", fieldname: "full_name", value: "John Doe" })
153
243
  * }
154
244
  */
155
- declare const useFrappePutCall: unknown;
245
+ declare const useFrappePutCall: <T = any>(method: string) => FrappeMutationResult<T>;
156
246
  /**
157
247
  *
158
248
  * @param method - name of the method to call (DELETE request) (will be dotted path e.g. "frappe.client.delete")
@@ -166,7 +256,7 @@ declare const useFrappePutCall: unknown;
166
256
  * const message = await call({ doctype: "User", docname: "test@example.com" })
167
257
  * }
168
258
  */
169
- declare const useFrappeDeleteCall: unknown;
259
+ declare const useFrappeDeleteCall: <T = any>(method: string) => FrappeMutationResult<T>;
170
260
  import { Filter } from "frappe-js-sdk/lib/db/types";
171
261
  /**
172
262
  * Hook to fetch number of documents from the database
@@ -188,7 +278,7 @@ declare const useFrappeGetDocCount: <T = any>(doctype: string, filters?: Filter<
188
278
  retry?: boolean | number;
189
279
  }) => {
190
280
  data: number | undefined;
191
- error: Error | null;
281
+ error: FrappeError | null;
192
282
  isLoading: boolean;
193
283
  isFetching: boolean;
194
284
  refetch: () => void;
@@ -209,7 +299,7 @@ import { FrappeDoc } from "frappe-js-sdk/lib/db/types";
209
299
  declare const useFrappeCreateDoc: <T = any>() => {
210
300
  createDoc: (doctype: string, doc: T) => Promise<FrappeDoc<T>>;
211
301
  loading: boolean;
212
- error: Error | null | undefined;
302
+ error: FrappeError | null;
213
303
  isCompleted: boolean;
214
304
  reset: () => void;
215
305
  };
@@ -230,7 +320,7 @@ declare const useFrappeDeleteDoc: <T = any>() => {
230
320
  message: string;
231
321
  }>;
232
322
  loading: boolean;
233
- error: Error | null | undefined;
323
+ error: FrappeError | null;
234
324
  isCompleted: boolean;
235
325
  reset: () => void;
236
326
  };
@@ -382,7 +472,7 @@ declare const useFrappeGetDoc: <T = any>(doctype: string, name?: string, options
382
472
  retry?: boolean | number;
383
473
  }) => {
384
474
  data: FrappeDoc2<T> | undefined;
385
- error: Error | null;
475
+ error: FrappeError | null;
386
476
  isLoading: boolean;
387
477
  isFetching: boolean;
388
478
  refetch: () => void;
@@ -421,7 +511,7 @@ declare const useFrappeGetDocList: <
421
511
  retry?: boolean | number;
422
512
  }) => {
423
513
  data: T[] | undefined;
424
- error: Error | null;
514
+ error: FrappeError | null;
425
515
  isLoading: boolean;
426
516
  isFetching: boolean;
427
517
  refetch: () => void;
@@ -471,7 +561,7 @@ declare const useSearch: (doctype: string, text: string, filters?: Filter2[], li
471
561
  data: {
472
562
  message: SearchResult[];
473
563
  } | undefined;
474
- error: Error | null;
564
+ error: FrappeError | null;
475
565
  isLoading: boolean;
476
566
  isFetching: boolean;
477
567
  refetch: () => void;
@@ -492,83 +582,66 @@ import { FrappeDoc as FrappeDoc4 } from "frappe-js-sdk/lib/db/types";
492
582
  declare const useFrappeUpdateDoc: <T = any>() => {
493
583
  updateDoc: (doctype: string, docname: string | null, doc: Partial<T>) => Promise<FrappeDoc4<T>>;
494
584
  loading: boolean;
495
- error: Error | null | undefined;
585
+ error: FrappeError | null;
496
586
  isCompleted: boolean;
497
587
  reset: () => void;
498
588
  };
499
- interface Globals {
500
- header: Header;
501
- footer: Footer;
502
- }
503
- interface Brand {
504
- brandImage?: string;
505
- }
506
- interface NavbarItem {
507
- label: string;
508
- enableDropdown: boolean;
509
- enableLink: boolean;
510
- link?: string;
511
- dropdownDescription?: string;
512
- dropdownCTALabel?: string;
513
- dropdownCTALink?: string;
514
- groups?: NavbarSubItemGroup[];
515
- }
516
- interface NavbarSubItemGroup {
517
- title: string;
518
- items: NavbarSubItem[];
519
- }
520
- interface NavbarSubItem {
521
- label: string;
522
- description?: string;
523
- image?: string;
524
- link?: string;
525
- }
526
- interface Topbar {
527
- enabled: boolean;
528
- layout: string;
529
- items: TopbarItem[];
589
+ interface VariantSelectorProps<T extends {
590
+ id: string;
591
+ specs: Record<string, string>;
592
+ }> {
593
+ variants: T[];
594
+ attributes: {
595
+ key: string;
596
+ values: string[];
597
+ }[];
598
+ defaultId?: string;
530
599
  }
531
- interface TopbarItem {
532
- icon?: string;
533
- label: string;
534
- link?: string;
535
- }
536
- interface Header {
537
- headerType?: string;
538
- brand: Brand;
539
- tabs: NavbarItem[];
540
- topbar: Topbar;
600
+ interface VariantSelectorOption {
601
+ key: string;
602
+ value: string;
603
+ state: "selected" | "available" | "unavailable";
541
604
  }
542
- interface FooterItemGroup {
543
- title: string;
544
- items: FooterItem[];
605
+ interface VariantSelectorResult {
606
+ variantId?: string;
607
+ onOptionSelect: (key: string, value: string) => void;
608
+ getOptions: (key: string) => VariantSelectorOption[];
545
609
  }
546
- interface FooterItem {
547
- label: string;
548
- link?: string;
610
+ declare const useVariantSelector: <T extends {
611
+ id: string;
612
+ specs: Record<string, string>;
613
+ }>(props: VariantSelectorProps<T>) => VariantSelectorResult;
614
+ interface Category {
615
+ name: string;
549
616
  }
550
- interface Footer {
551
- footerType?: string;
552
- groups: FooterItemGroup[];
553
- copyright?: string;
554
- address?: string;
555
- country?: string;
556
- phone?: string;
557
- email?: string;
617
+ interface Product {
618
+ name: string;
619
+ image: string;
620
+ item_code: string;
621
+ description: string;
558
622
  }
559
- interface Hero {
560
- type: string;
561
- data: Record<string, unknown>;
623
+ interface ProductVariant extends ItemVariant {
624
+ name: string;
625
+ currency: string;
626
+ rate: number;
627
+ qty: number;
628
+ images: string[];
629
+ specs: Record<string, string>;
562
630
  }
563
- interface Block {
564
- type: string;
565
- data: Record<string, unknown>;
631
+ interface ShopContext {
632
+ categories: Category[];
633
+ grades: string[];
634
+ selected_category?: Category;
635
+ selected_grade?: string;
636
+ products: Product[];
566
637
  }
567
- interface Page {
568
- slug: string;
569
- hero?: Hero;
570
- blocks: Block[];
638
+ interface ProductContext {
639
+ product: Product;
640
+ specs: Record<string, string[]>;
641
+ variants: ProductVariant[];
642
+ models: Record<string, Model>;
643
+ colors: Record<string, Colour>;
571
644
  }
572
645
  import { camelize, decamelize, camelizeKeys, decamelizeKeys } from "humps";
573
646
  declare function equalsIgnoreCase(str1: string, str2: string): boolean;
574
- export { useSearch, useFrappeUpdateDoc, useFrappePutCall, useFrappePrefetchDoc, useFrappePostCall, useFrappeGetDocList, useFrappeGetDocCount, useFrappeGetDoc, useFrappeGetCall, useFrappeFileUpload, useFrappeEventListener, useFrappeDocumentEventListener, useFrappeDocTypeEventListener, useFrappeDeleteDoc, useFrappeDeleteCall, useFrappeCreateDoc, useFrappeAuth, equalsIgnoreCase, decamelizeKeys, decamelize, camelizeKeys, camelize, ViewerEventData, UseFrappeFileUploadReturnType, TopbarItem, Topbar, TokenParams, SearchResult, Page, NavbarSubItemGroup, NavbarSubItem, NavbarItem, Model, ItemVariant, Item, Hero, Header, Globals, FrappeProviderProps, FrappeProvider, FrappeFileUploadResponse, FrappeContext, FrappeConfig, FooterItemGroup, FooterItem, Footer, DocumentUpdateEventData, DocTypeListUpdateEventData, Colour, Brand, Block };
647
+ export { useVariantSelector, useSearch, useFrappeUpdateDoc, useFrappePutCall, useFrappePrefetchDoc, useFrappePostCall, useFrappeGetDocList, useFrappeGetDocCount, useFrappeGetDoc, useFrappeGetCall, useFrappeFileUpload, useFrappeEventListener, useFrappeDocumentEventListener, useFrappeDocTypeEventListener, useFrappeDeleteDoc, useFrappeDeleteCall, useFrappeCreateDoc, useFrappeAuth, equalsIgnoreCase, decamelizeKeys, decamelize, camelizeKeys, camelize, ViewerEventData, VariantSelectorResult, VariantSelectorProps, VariantSelectorOption, UseFrappeFileUploadReturnType, TopbarItem, Topbar, TokenParams, ShopContext, SearchResult, ProductVariant, ProductContext, Product, Page, NavbarSubItemGroup, NavbarSubItem, NavbarItem, Model, ItemVariant, Item, Hero, Header, Globals, FrappeProviderProps, FrappeProvider, FrappeMutationResult, FrappeFileUploadResponse, FrappeError, FrappeContext, FrappeConfig, FooterItemGroup, FooterItem, Footer, DocumentUpdateEventData, DocTypeListUpdateEventData, Colour, Category, Brand, Block };
package/dist/rn/index.js CHANGED
@@ -46,7 +46,7 @@ class SocketIO {
46
46
  }
47
47
 
48
48
  // src/frappe/provider.tsx
49
- import { jsxDEV } from "react/jsx-dev-runtime";
49
+ import { jsx } from "react/jsx-runtime";
50
50
 
51
51
  var FrappeContext = createContext(null);
52
52
  var FrappeProvider = ({
@@ -71,10 +71,10 @@ var FrappeProvider = ({
71
71
  socket: enableSocket ? new SocketIO(url, siteName, socketPort, tokenParams).socket : undefined
72
72
  };
73
73
  }, [url, tokenParams, enableSocket, socketPort, siteName, customHeaders]);
74
- return /* @__PURE__ */ jsxDEV(FrappeContext.Provider, {
74
+ return /* @__PURE__ */ jsx(FrappeContext.Provider, {
75
75
  value: frappeConfig,
76
76
  children
77
- }, undefined, false, undefined, this);
77
+ });
78
78
  };
79
79
  // src/frappe/hooks/auth.ts
80
80
  import { useCallback, useContext, useState, useEffect } from "react";
@@ -465,12 +465,118 @@ var useFrappeUpdateDoc = () => {
465
465
  reset
466
466
  };
467
467
  };
468
+ // src/hooks/use-variant-selector/hook.ts
469
+ import { useCallback as useCallback4, useMemo as useMemo2, useState as useState5 } from "react";
470
+
471
+ // src/hooks/use-variant-selector/utils.ts
472
+ function findVariant(variants, specs, caseInsensitive) {
473
+ return variants.find((variant) => {
474
+ return Object.entries(specs).every(([key, value]) => {
475
+ const vv = variant.specs[key];
476
+ if (!vv)
477
+ return false;
478
+ if (caseInsensitive) {
479
+ return vv.toLowerCase() === value.toLowerCase();
480
+ }
481
+ return vv === value;
482
+ });
483
+ });
484
+ }
485
+ function findVariants(variants, specs, caseInsensitive) {
486
+ return variants.filter((variant) => {
487
+ return Object.entries(specs).every(([key, value]) => {
488
+ const vv = variant.specs[key];
489
+ if (!vv)
490
+ return false;
491
+ if (caseInsensitive) {
492
+ return vv.toLowerCase() === value.toLowerCase();
493
+ }
494
+ return vv === value;
495
+ });
496
+ });
497
+ }
498
+
499
+ // src/hooks/use-variant-selector/hook.ts
500
+ var useVariantSelector = (props) => {
501
+ const { variants, attributes, defaultId } = props;
502
+ const [selectedSpecs, setSelectedSpecs] = useState5(() => {
503
+ if (defaultId) {
504
+ const variant = variants.find((v) => v.id === defaultId);
505
+ return variant?.specs || {};
506
+ }
507
+ return {};
508
+ });
509
+ const variantId = useMemo2(() => {
510
+ const complete = attributes.every((attr) => selectedSpecs[attr.key]);
511
+ if (!complete)
512
+ return;
513
+ const variant = findVariant(variants, selectedSpecs);
514
+ return variant?.id;
515
+ }, [variants, selectedSpecs, attributes]);
516
+ const options = useMemo2(() => {
517
+ const result = {};
518
+ attributes.forEach((attr, attrIndex) => {
519
+ const constraints = {};
520
+ for (let i = 0;i < attrIndex; i++) {
521
+ const key = attributes[i]?.key;
522
+ if (key && selectedSpecs[key])
523
+ constraints[key] = selectedSpecs[key];
524
+ }
525
+ const matchingVariants = findVariants(variants, constraints);
526
+ const availableValues = new Set(matchingVariants.map((v) => v.specs[attr.key]));
527
+ result[attr.key] = attr.values.map((value) => {
528
+ const isSelected = selectedSpecs[attr.key] === value;
529
+ const isAvailable = availableValues.has(value);
530
+ return {
531
+ key: attr.key,
532
+ value,
533
+ state: isSelected ? "selected" : isAvailable ? "available" : "unavailable"
534
+ };
535
+ });
536
+ });
537
+ return result;
538
+ }, [variants, attributes, selectedSpecs]);
539
+ const onOptionSelect = useCallback4((key, value) => {
540
+ setSelectedSpecs((prev) => {
541
+ const newSpecs = { ...prev, [key]: value };
542
+ const attrIndex = attributes.findIndex((a) => a.key === key);
543
+ for (let i = attrIndex + 1;i < attributes.length; i++) {
544
+ const downstreamAttr = attributes[i];
545
+ if (!downstreamAttr)
546
+ continue;
547
+ const constraints = {};
548
+ for (let j = 0;j < i; j++) {
549
+ const k = attributes[j]?.key;
550
+ if (k && newSpecs[k])
551
+ constraints[k] = newSpecs[k];
552
+ }
553
+ const matching = findVariants(variants, constraints);
554
+ const availableValues = new Set(matching.map((v) => v.specs[downstreamAttr.key]));
555
+ if (!availableValues.has(newSpecs[downstreamAttr.key])) {
556
+ const firstAvailable = downstreamAttr.values.find((v) => availableValues.has(v));
557
+ if (firstAvailable) {
558
+ newSpecs[downstreamAttr.key] = firstAvailable;
559
+ } else {
560
+ delete newSpecs[downstreamAttr.key];
561
+ }
562
+ }
563
+ }
564
+ return newSpecs;
565
+ });
566
+ }, [attributes, variants]);
567
+ return {
568
+ variantId,
569
+ onOptionSelect,
570
+ getOptions: (key) => options[key] || []
571
+ };
572
+ };
468
573
  // src/utils/char.ts
469
574
  import { camelize, decamelize, camelizeKeys, decamelizeKeys } from "humps";
470
575
  function equalsIgnoreCase(str1, str2) {
471
576
  return str1.localeCompare(str2, undefined, { sensitivity: "accent" }) === 0;
472
577
  }
473
578
  export {
579
+ useVariantSelector,
474
580
  useSearch,
475
581
  useFrappeUpdateDoc,
476
582
  useFrappePutCall,
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@lasterp/shared",
3
- "version": "1.0.0-alpha.8",
3
+ "version": "1.0.0-beta.0",
4
4
  "description": "Shared repo for webapp and native app",
5
5
  "license": "MIT",
6
6
  "files": [
7
7
  "dist"
8
8
  ],
9
9
  "scripts": {
10
- "build": "bunup",
10
+ "build": "NODE_ENV=production bunup",
11
11
  "dev": "bunup --watch",
12
12
  "type-check": "tsc --noEmit"
13
13
  },
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- declare function greet(name: string): string;
2
- export { greet };
package/dist/index.js DELETED
@@ -1,7 +0,0 @@
1
- // src/index.ts
2
- function greet(name) {
3
- return `Hello, ${name}!`;
4
- }
5
- export {
6
- greet
7
- };