@kasuvia/sdk 0.0.2 → 0.0.5

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 (77) hide show
  1. package/dist/hooks/index.d.mts +430 -0
  2. package/dist/hooks/index.d.ts +430 -0
  3. package/dist/hooks/index.js +3073 -0
  4. package/dist/hooks/index.js.map +1 -0
  5. package/dist/hooks/index.mjs +2962 -0
  6. package/dist/hooks/index.mjs.map +1 -0
  7. package/dist/index-CxNLI4nw.d.ts +786 -0
  8. package/dist/index-CzVMyUs4.d.mts +492 -0
  9. package/dist/index-CzVMyUs4.d.ts +492 -0
  10. package/dist/index-Dowl5224.d.mts +786 -0
  11. package/dist/index.d.mts +48 -1739
  12. package/dist/index.d.ts +48 -1739
  13. package/dist/index.js +1544 -2359
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.mjs +1527 -2258
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/sdk-context-BuarJbeb.d.ts +75 -0
  18. package/dist/sdk-context-Db8kf1Tv.d.mts +75 -0
  19. package/dist/services/index.d.mts +2 -0
  20. package/dist/services/index.d.ts +2 -0
  21. package/dist/services/index.js +2369 -0
  22. package/dist/services/index.js.map +1 -0
  23. package/dist/services/index.mjs +2252 -0
  24. package/dist/services/index.mjs.map +1 -0
  25. package/package.json +11 -2
  26. package/src/config/index.ts +10 -91
  27. package/src/constants.ts +13 -0
  28. package/src/context/sdk-context.tsx +70 -0
  29. package/src/graphql/collections.graphql.ts +2 -10
  30. package/src/graphql/products.graphql.ts +215 -65
  31. package/src/graphql/wishlist.graphql.ts +7 -6
  32. package/src/hooks/index.ts +2 -0
  33. package/src/hooks/use-auth.ts +82 -158
  34. package/src/hooks/use-bespoke-availability.ts +25 -8
  35. package/src/hooks/use-cart.ts +222 -119
  36. package/src/hooks/use-cms.ts +179 -86
  37. package/src/hooks/use-collections.ts +52 -15
  38. package/src/hooks/use-consultation-stream.ts +4 -3
  39. package/src/hooks/use-consultations.ts +120 -51
  40. package/src/hooks/use-countries.ts +35 -13
  41. package/src/hooks/use-currencies.ts +26 -13
  42. package/src/hooks/use-google-translate.ts +22 -8
  43. package/src/hooks/use-languages.ts +40 -20
  44. package/src/hooks/use-mobile.ts +23 -0
  45. package/src/hooks/use-order-stream.ts +9 -5
  46. package/src/hooks/use-orders.ts +99 -44
  47. package/src/hooks/use-payment.ts +108 -40
  48. package/src/hooks/use-products.ts +251 -53
  49. package/src/hooks/use-service-booking.ts +152 -90
  50. package/src/hooks/use-states.ts +35 -15
  51. package/src/hooks/use-wishlist.ts +92 -91
  52. package/src/index.ts +26 -30
  53. package/src/services/auth.service.ts +209 -167
  54. package/src/services/bespoke.service.ts +12 -4
  55. package/src/services/cart.service.ts +172 -70
  56. package/src/services/cms.service.ts +70 -35
  57. package/src/services/collection.service.ts +22 -5
  58. package/src/services/config.service.ts +34 -14
  59. package/src/services/consultation.service.ts +72 -18
  60. package/src/services/logistics.service.ts +35 -11
  61. package/src/services/notifications.service.ts +27 -12
  62. package/src/services/order.service.ts +74 -15
  63. package/src/services/payment.service.ts +86 -31
  64. package/src/services/product.service.ts +279 -39
  65. package/src/services/service-booking.service.ts +98 -26
  66. package/src/services/wishlist.service.ts +69 -25
  67. package/src/types/auth/index.ts +6 -4
  68. package/src/types/business/index.ts +7 -2
  69. package/src/types/payments/index.ts +3 -3
  70. package/src/types/services/index.ts +2 -5
  71. package/src/types/store/index.ts +38 -3
  72. package/src/utils/error-handler.ts +10 -5
  73. package/src/utils/storage.ts +48 -26
  74. package/src/utils/validators.ts +85 -0
  75. package/src/client/authClient.ts +0 -135
  76. package/src/client/graphqlClient.ts +0 -53
  77. package/src/client/httpClient.ts +0 -298
@@ -2,14 +2,15 @@
2
2
  * Wishlist Service
3
3
  *
4
4
  * Handles wishlist operations.
5
+ * Stateless - all configuration passed as parameters.
5
6
  */
6
- import { storeManagementAxios } from '../client/httpClient'
7
- import { getGraphQLClient } from '../client/graphqlClient'
7
+ import { GraphQLClient } from 'graphql-request'
8
+ import type { SdkConfig } from '../config'
8
9
  import type { WishlistItem, WishlistListResponse, WishlistCheckResponse } from '../types/store'
9
10
 
10
- const GET_WISHLIST = `
11
- query GetWishlist($page: Int!, $pageSize: Int!) {
12
- wishlist(page: $page, pageSize: $pageSize) {
11
+ const GET_STOREFRONT_WISHLIST = `
12
+ query GetStorefrontWishlist($page: Int!, $pageSize: Int!) {
13
+ storefront_wishlist(page: $page, pageSize: $pageSize) {
13
14
  items {
14
15
  id
15
16
  product_id
@@ -18,6 +19,7 @@ const GET_WISHLIST = `
18
19
  product_name
19
20
  product_slug
20
21
  product_price
22
+ currency
21
23
  product_image
22
24
  }
23
25
  total
@@ -28,57 +30,99 @@ const GET_WISHLIST = `
28
30
  }
29
31
  `
30
32
 
31
- const CHECK_IN_WISHLIST = `
32
- query CheckInWishlist($productId: String!) {
33
- check_in_wishlist(productId: $productId) {
33
+ const CHECK_IN_STOREFRONT_WISHLIST = `
34
+ query CheckInStorefrontWishlist($productId: String!) {
35
+ check_in_storefront_wishlist(productId: $productId) {
34
36
  in_wishlist
35
37
  }
36
38
  }
37
39
  `
38
40
 
41
+ /**
42
+ * Create GraphQL client with provided config
43
+ */
44
+ function createGraphQLClient(config: SdkConfig, accessToken?: string): GraphQLClient {
45
+ const headers: Record<string, string> = {
46
+ 'Content-Type': 'application/json',
47
+ 'Accept': 'application/json',
48
+ 'X-Storefront-Key': config.storefrontApiKey,
49
+ 'X-Business-ID': config.businessId,
50
+ }
51
+ if (accessToken) {
52
+ headers['Authorization'] = `Bearer ${accessToken}`
53
+ }
54
+ return new GraphQLClient(`${config.backendUrls.store}/graphql`, { headers })
55
+ }
56
+
39
57
  /**
40
58
  * Fetch the customer's wishlist for the current business.
41
59
  */
42
- export async function getWishlist(params?: { page?: number; pageSize?: number }): Promise<WishlistListResponse> {
43
- const client = getGraphQLClient()
60
+ export async function getWishlist(config: SdkConfig, accessToken: string, params?: { page?: number; pageSize?: number }): Promise<WishlistListResponse> {
61
+ const client = createGraphQLClient(config, accessToken)
44
62
  const variables = {
45
63
  page: params?.page || 1,
46
64
  pageSize: params?.pageSize || 20,
47
65
  }
48
- const data = await client.request(GET_WISHLIST, variables)
49
- return data.wishlist
66
+ const data = await client.request(GET_STOREFRONT_WISHLIST, variables)
67
+ return data.storefront_wishlist
50
68
  }
51
69
 
52
70
  /**
53
71
  * Check if a product is in the customer's wishlist.
54
72
  */
55
- export async function checkInWishlist(productId: string): Promise<WishlistCheckResponse> {
56
- const client = getGraphQLClient()
57
- const data = await client.request(CHECK_IN_WISHLIST, { productId })
58
- return data.check_in_wishlist
73
+ export async function checkInWishlist(config: SdkConfig, accessToken: string, productId: string): Promise<WishlistCheckResponse> {
74
+ const client = createGraphQLClient(config, accessToken)
75
+ const data = await client.request(CHECK_IN_STOREFRONT_WISHLIST, { productId })
76
+ return data.check_in_storefront_wishlist
59
77
  }
60
78
 
61
79
  /**
62
80
  * Add a product to the customer's wishlist.
63
81
  */
64
- export async function addToWishlist(productId: string, notes?: string): Promise<WishlistItem> {
65
- const { data } = await storeManagementAxios.post<WishlistItem>("wishlist/", {
66
- product_id: productId,
67
- notes: notes || undefined,
82
+ export async function addToWishlist(config: SdkConfig, accessToken: string, productId: string, notes?: string): Promise<WishlistItem> {
83
+ const response = await fetch(`${config.backendUrls.store}/wishlist/`, {
84
+ method: 'POST',
85
+ headers: {
86
+ 'Content-Type': 'application/json',
87
+ 'Authorization': `Bearer ${accessToken}`,
88
+ "X-Storefront-Key": config.storefrontApiKey,
89
+ "X-Business-ID": config.businessId,
90
+ },
91
+ body: JSON.stringify({
92
+ product_id: productId,
93
+ notes: notes || undefined,
94
+ }),
68
95
  })
69
- return data
96
+ if (!response.ok) throw new Error('Failed to add to wishlist')
97
+ return response.json()
70
98
  }
71
99
 
72
100
  /**
73
101
  * Remove an item from the customer's wishlist by item ID.
74
102
  */
75
- export async function removeFromWishlist(itemId: string): Promise<void> {
76
- await storeManagementAxios.delete(`wishlist/${itemId}`)
103
+ export async function removeFromWishlist(config: SdkConfig, accessToken: string, itemId: string): Promise<void> {
104
+ const response = await fetch(`${config.backendUrls.store}/wishlist/${itemId}`, {
105
+ method: 'DELETE',
106
+ headers: {
107
+ 'Authorization': `Bearer ${accessToken}`,
108
+ "X-Storefront-Key": config.storefrontApiKey,
109
+ "X-Business-ID": config.businessId,
110
+ },
111
+ })
112
+ if (!response.ok) throw new Error('Failed to remove from wishlist')
77
113
  }
78
114
 
79
115
  /**
80
116
  * Remove a product from the customer's wishlist by product ID.
81
117
  */
82
- export async function removeProductFromWishlist(productId: string): Promise<void> {
83
- await storeManagementAxios.delete(`wishlist/product/${productId}`)
118
+ export async function removeProductFromWishlist(config: SdkConfig, accessToken: string, productId: string): Promise<void> {
119
+ const response = await fetch(`${config.backendUrls.store}/wishlist/product/${productId}`, {
120
+ method: 'DELETE',
121
+ headers: {
122
+ 'Authorization': `Bearer ${accessToken}`,
123
+ "X-Storefront-Key": config.storefrontApiKey,
124
+ "X-Business-ID": config.businessId,
125
+ },
126
+ })
127
+ if (!response.ok) throw new Error('Failed to remove product from wishlist')
84
128
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Simplified customer auth types for luxe-storefront.
2
+ * Simplified customer auth types for kasuvia-storefront.
3
3
  * Customers only need enough to track their orders — no RBAC, MFA, or complex profiles.
4
4
  */
5
5
 
@@ -33,7 +33,7 @@ export interface CustomerUser {
33
33
  provider: 'google' | 'facebook' | 'email';
34
34
  }
35
35
 
36
- /** GET/PUT /auth/profile/ — matches luxe-auth UserSerializer */
36
+ /** GET/PUT /auth/profile/ — matches kasuvia-auth UserSerializer */
37
37
  export interface AuthUserResponse {
38
38
  id: string;
39
39
  email: string;
@@ -111,8 +111,8 @@ export interface CustomerLoginPayload {
111
111
  /** POST /auth/register/customer/ */
112
112
  export interface CustomerRegisterPayload {
113
113
  tenant_id: string; // config.api.storefrontBusinessId
114
- /** Full name — backend splits this into firstname and lastname on the Profile */
115
- full_name: string;
114
+ first_name?: string;
115
+ last_name?: string;
116
116
  email: string;
117
117
  password: string;
118
118
  }
@@ -144,4 +144,6 @@ export interface AuthContextValue {
144
144
  closeModal: () => void;
145
145
  setCustomer: (user: CustomerUser | null) => void;
146
146
  logout: () => Promise<void>;
147
+ login: (payload: Omit<CustomerLoginPayload, 'tenant_id'>) => Promise<void>;
148
+ register: (payload: Omit<CustomerRegisterPayload, 'tenant_id'>) => Promise<{ requires_verification: boolean }>;
147
149
  }
@@ -1,6 +1,11 @@
1
1
  export interface BusinessConfig {
2
2
  business_id: string;
3
- store_name: string;
4
- store_currency: string;
3
+ storefront_business_id: string;
4
+ name: string;
5
+ currency: string;
6
+ settlement_currency: string;
7
+ country: string;
8
+ region: string;
9
+ is_verified: boolean;
5
10
  is_active: boolean;
6
11
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file types/payments/index.ts
3
- * @description All customer-facing payment types for the Luxe Payment System.
3
+ * @description All customer-facing payment types for the Kasuvia Payment System.
4
4
  *
5
5
  * Auth model:
6
6
  * - X-Storefront-Key → identifies the business storefront
@@ -9,8 +9,8 @@
9
9
  *
10
10
  * Payment flow:
11
11
  * Customer → Business Owner : Monnify (NGN) | Flutterwave (non-NGN)
12
- * Business Owner → Luxe : Subscription (separate, JWT-auth)
13
- * Luxe → Business Owner : Payout (separate, JWT-auth)
12
+ * Business Owner → Kasuvia : Subscription (separate, JWT-auth)
13
+ * Kasuvia → Business Owner : Payout (separate, JWT-auth)
14
14
  */
15
15
 
16
16
  // ── Configuration Types ─────────────────────────────────────────────────────────
@@ -2,9 +2,6 @@
2
2
  * Service booking types for customer-facing storefront
3
3
  */
4
4
 
5
- export type ServiceLocationType = "remote" | "physical" | "both";
6
- export type ServiceType = "appointment" | "project" | "recurring";
7
-
8
5
  export type AppointmentStatus =
9
6
  | "pending"
10
7
  | "confirmed"
@@ -29,7 +26,7 @@ export interface ServiceAppointment {
29
26
  appointment_date: string;
30
27
  start_time: string;
31
28
  end_time: string;
32
- location_type: ServiceLocationType;
29
+ location_type: import("../store").ServiceLocationType;
33
30
  customer_address: Record<string, unknown> | null;
34
31
  meeting_link: string | null;
35
32
  customer_notes: string | null;
@@ -45,7 +42,7 @@ export interface ServiceAppointmentCreatePayload {
45
42
  product_id: string;
46
43
  scheduled_date: string;
47
44
  scheduled_time: string;
48
- location_type?: ServiceLocationType;
45
+ location_type?: import("../store").ServiceLocationType;
49
46
  customer_address?: Record<string, unknown>;
50
47
  customer_notes?: string;
51
48
  }
@@ -5,8 +5,29 @@ export type { DeliveryAddress };
5
5
 
6
6
  export type ProductType = "physical" | "digital" | "service" | "bespoke";
7
7
 
8
+ export enum ServiceTypeEnum {
9
+ APPOINTMENT = 'appointment',
10
+ PROJECT = 'project',
11
+ RECURRING = 'recurring',
12
+ }
13
+
14
+ export type ServiceType = ServiceTypeEnum | 'appointment' | 'project' | 'recurring'
8
15
 
16
+ export enum ServiceLocationTypeEnum {
17
+ REMOTE = 'remote',
18
+ PHYSICAL = 'physical',
19
+ BOTH = 'both',
20
+ }
9
21
 
22
+ export type ServiceLocationType = ServiceLocationTypeEnum | 'remote' | 'physical' | 'both'
23
+
24
+ export enum ServiceRecurringIntervalEnum {
25
+ WEEKLY = 'weekly',
26
+ MONTHLY = 'monthly',
27
+ YEARLY = 'yearly',
28
+ }
29
+
30
+ export type ServiceRecurringInterval = ServiceRecurringIntervalEnum | 'weekly' | 'monthly' | 'yearly' | ''
10
31
 
11
32
  export interface Address {
12
33
  street: string
@@ -22,7 +43,7 @@ export interface Collection {
22
43
  name: string
23
44
  slug: string
24
45
  description?: string
25
- image?: { url: string; alt?: string; id?: string }
46
+ image?: string
26
47
  collection_type?: string
27
48
  rules?: Record<string, unknown>
28
49
  status?: string
@@ -53,7 +74,7 @@ export interface ProductVariant {
53
74
  sku?: string
54
75
  attributes: Record<string, string>
55
76
  price?: number
56
- images: Array<{ url: string; alt?: string; id?: string }>
77
+ images: string[]
57
78
  weight_kg?: number
58
79
  is_active: boolean
59
80
  inventory?: Inventory
@@ -81,9 +102,13 @@ export interface Product {
81
102
  requires_shipping: boolean
82
103
  is_consumable: boolean
83
104
  is_bespoke: boolean
105
+ is_physical?: boolean | null // For bespoke products: indicates if custom item requires physical delivery
84
106
  base_price: number
85
107
  currency: string
86
- images: Array<{ url: string; alt?: string; id?: string }>
108
+ sale_price?: number
109
+ sale_start?: string
110
+ sale_end?: string
111
+ images: string[]
87
112
  tags: string[]
88
113
  custom_fields: CustomField[]
89
114
  is_published: boolean
@@ -94,6 +119,16 @@ export interface Product {
94
119
  digital_file_key?: string
95
120
  download_limit?: number
96
121
  download_expires_at?: string
122
+ // Service-specific fields
123
+ service_type?: ServiceType | null
124
+ service_duration_minutes?: number | null
125
+ service_location_type?: ServiceLocationType | null
126
+ service_requires_consultation?: boolean
127
+ service_max_capacity_per_slot?: number | null
128
+ service_buffer_minutes?: number
129
+ service_is_recurring?: boolean
130
+ service_recurring_interval?: ServiceRecurringInterval | null
131
+ service_project_based?: boolean
97
132
  weight_kg: number
98
133
  length_cm?: number
99
134
  width_cm?: number
@@ -1,14 +1,19 @@
1
- import axios from 'axios';
2
-
3
1
  /**
4
2
  * Type-safe way to extract an error message from an API error.
3
+ * Updated for fetch-based errors (no axios dependency).
5
4
  */
6
5
  export function getErrorMessage(error: unknown, fallback: string = "Something went wrong"): string {
7
- if (axios.isAxiosError(error)) {
8
- return error.response?.data?.detail || error.response?.data?.message || error.message || fallback;
9
- }
10
6
  if (error instanceof Error) {
11
7
  return error.message;
12
8
  }
9
+ // Handle fetch Response objects that might be thrown
10
+ if (error instanceof Response) {
11
+ return `HTTP ${error.status}: ${error.statusText}`;
12
+ }
13
+ // Handle objects with detail or message properties (from API responses)
14
+ if (typeof error === 'object' && error !== null) {
15
+ const err = error as { detail?: string; message?: string; status?: number };
16
+ return err.detail || err.message || fallback;
17
+ }
13
18
  return fallback;
14
19
  }
@@ -2,32 +2,39 @@
2
2
  * @file storage.ts
3
3
  * @description Centralized tenant-isolated storage utility for sessionStorage and localStorage.
4
4
  * Automatically prefixes all keys with the storefront Business ID to guarantee multi-tenant segregation.
5
+ * Stateless - requires businessId parameter.
5
6
  */
6
- import { getRuntimeConfig } from '../config'
7
7
 
8
- function getPrefixedKey(key: string): string {
9
- const config = getRuntimeConfig()
10
- const bid = config.businessId
11
- const cleanKey = key.startsWith('luxe_') ? key.substring(5) : key
12
- return bid ? `luxe-sf-${bid}-${cleanKey}` : `luxe-sf-anon-${cleanKey}`
8
+ function getPrefixedKey(businessId: string, key: string): string {
9
+ const cleanKey = key.startsWith('kasuvia_') ? key.substring(5) : key
10
+ return businessId ? `kasuvia-sf-${businessId}-${cleanKey}` : `kasuvia-sf-anon-${cleanKey}`
13
11
  }
14
12
 
15
13
  export const sessionStore = {
16
- get: (key: string): string | null => {
14
+ get: (businessIdOrKey: string, key?: string): string | null => {
17
15
  if (typeof window === 'undefined') return null
18
- return sessionStorage.getItem(getPrefixedKey(key))
16
+ const bId = key ? businessIdOrKey : ''
17
+ const k = key || businessIdOrKey
18
+ return sessionStorage.getItem(getPrefixedKey(bId, k))
19
19
  },
20
- set: (key: string, value: string): void => {
20
+ set: (businessIdOrKey: string, keyOrVal: string, val?: string): void => {
21
21
  if (typeof window === 'undefined') return
22
- sessionStorage.setItem(getPrefixedKey(key), value)
22
+ const bId = val !== undefined ? businessIdOrKey : ''
23
+ const k = val !== undefined ? keyOrVal : businessIdOrKey
24
+ const v = val !== undefined ? val : keyOrVal
25
+ sessionStorage.setItem(getPrefixedKey(bId, k), v)
23
26
  },
24
- remove: (key: string): void => {
27
+ remove: (businessIdOrKey: string, key?: string): void => {
25
28
  if (typeof window === 'undefined') return
26
- sessionStorage.removeItem(getPrefixedKey(key))
29
+ const bId = key ? businessIdOrKey : ''
30
+ const k = key || businessIdOrKey
31
+ sessionStorage.removeItem(getPrefixedKey(bId, k))
27
32
  },
28
- getJSON: <T = unknown>(key: string): T | null => {
33
+ getJSON: <T = unknown>(businessIdOrKey: string, key?: string): T | null => {
29
34
  if (typeof window === 'undefined') return null
30
- const value = sessionStorage.getItem(getPrefixedKey(key))
35
+ const bId = key ? businessIdOrKey : ''
36
+ const k = key || businessIdOrKey
37
+ const value = sessionStorage.getItem(getPrefixedKey(bId, k))
31
38
  if (!value) return null
32
39
  try {
33
40
  return JSON.parse(value) as T
@@ -35,28 +42,40 @@ export const sessionStore = {
35
42
  return null
36
43
  }
37
44
  },
38
- setJSON: (key: string, value: unknown): void => {
45
+ setJSON: (businessIdOrKey: string, keyOrVal: unknown, val?: unknown): void => {
39
46
  if (typeof window === 'undefined') return
40
- sessionStorage.setItem(getPrefixedKey(key), JSON.stringify(value))
47
+ const bId = val !== undefined ? (businessIdOrKey as string) : ''
48
+ const k = val !== undefined ? (keyOrVal as string) : (businessIdOrKey as string)
49
+ const v = val !== undefined ? val : keyOrVal
50
+ sessionStorage.setItem(getPrefixedKey(bId, k), JSON.stringify(v))
41
51
  },
42
52
  }
43
53
 
44
54
  export const localStore = {
45
- get: (key: string): string | null => {
55
+ get: (businessIdOrKey: string, key?: string): string | null => {
46
56
  if (typeof window === 'undefined') return null
47
- return localStorage.getItem(getPrefixedKey(key))
57
+ const bId = key ? businessIdOrKey : ''
58
+ const k = key || businessIdOrKey
59
+ return localStorage.getItem(getPrefixedKey(bId, k))
48
60
  },
49
- set: (key: string, value: string): void => {
61
+ set: (businessIdOrKey: string, keyOrVal: string, val?: string): void => {
50
62
  if (typeof window === 'undefined') return
51
- localStorage.setItem(getPrefixedKey(key), value)
63
+ const bId = val !== undefined ? businessIdOrKey : ''
64
+ const k = val !== undefined ? keyOrVal : businessIdOrKey
65
+ const v = val !== undefined ? val : keyOrVal
66
+ localStorage.setItem(getPrefixedKey(bId, k), v)
52
67
  },
53
- remove: (key: string): void => {
68
+ remove: (businessIdOrKey: string, key?: string): void => {
54
69
  if (typeof window === 'undefined') return
55
- localStorage.removeItem(getPrefixedKey(key))
70
+ const bId = key ? businessIdOrKey : ''
71
+ const k = key || businessIdOrKey
72
+ localStorage.removeItem(getPrefixedKey(bId, k))
56
73
  },
57
- getJSON: <T = unknown>(key: string): T | null => {
74
+ getJSON: <T = unknown>(businessIdOrKey: string, key?: string): T | null => {
58
75
  if (typeof window === 'undefined') return null
59
- const value = localStorage.getItem(getPrefixedKey(key))
76
+ const bId = key ? businessIdOrKey : ''
77
+ const k = key || businessIdOrKey
78
+ const value = localStorage.getItem(getPrefixedKey(bId, k))
60
79
  if (!value) return null
61
80
  try {
62
81
  return JSON.parse(value) as T
@@ -64,8 +83,11 @@ export const localStore = {
64
83
  return null
65
84
  }
66
85
  },
67
- setJSON: (key: string, value: unknown): void => {
86
+ setJSON: (businessIdOrKey: string, keyOrVal: unknown, val?: unknown): void => {
68
87
  if (typeof window === 'undefined') return
69
- localStorage.setItem(getPrefixedKey(key), JSON.stringify(value))
88
+ const bId = val !== undefined ? (businessIdOrKey as string) : ''
89
+ const k = val !== undefined ? (keyOrVal as string) : (businessIdOrKey as string)
90
+ const v = val !== undefined ? val : keyOrVal
91
+ localStorage.setItem(getPrefixedKey(bId, k), JSON.stringify(v))
70
92
  },
71
93
  }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Form validation utilities for kasuvia-sdk
3
+ * Provides realtime validation for email, password, name, confirm password, and message fields
4
+ */
5
+
6
+ export function validateEmail(email: string): string | null {
7
+ if (!email || email.trim() === "") {
8
+ return "Email is required";
9
+ }
10
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
11
+ if (!emailRegex.test(email)) {
12
+ return "Please enter a valid email address";
13
+ }
14
+ return null;
15
+ }
16
+
17
+ export function validatePassword(password: string): string | null {
18
+ if (!password || password.trim() === "") {
19
+ return "Password is required";
20
+ }
21
+ if (password.length < 8) {
22
+ return "Password must be at least 8 characters";
23
+ }
24
+ if (password.length > 72) {
25
+ return "Password cannot exceed 72 characters";
26
+ }
27
+ if (!/[A-Z]/.test(password)) {
28
+ return "Password must contain at least one uppercase letter";
29
+ }
30
+ if (!/[a-z]/.test(password)) {
31
+ return "Password must contain at least one lowercase letter";
32
+ }
33
+ if (!/[0-9]/.test(password)) {
34
+ return "Password must contain at least one number";
35
+ }
36
+ return null;
37
+ }
38
+
39
+ export function validateName(name: string, fieldName: string = "Name"): string | null {
40
+ if (!name || name.trim() === "") {
41
+ return `${fieldName} is required`;
42
+ }
43
+ if (name.trim().length < 2) {
44
+ return `${fieldName} must be at least 2 characters`;
45
+ }
46
+ if (name.trim().length > 50) {
47
+ return `${fieldName} cannot exceed 50 characters`;
48
+ }
49
+ // Allow letters, spaces, hyphens, and apostrophes
50
+ const nameRegex = /^[a-zA-Z\s\-'\u00C0-\u017F]+$/;
51
+ if (!nameRegex.test(name.trim())) {
52
+ return `${fieldName} can only contain letters, spaces, hyphens, and apostrophes`;
53
+ }
54
+ return null;
55
+ }
56
+
57
+ export function validateRequired(value: string, fieldName: string): string | null {
58
+ if (!value || value.trim() === "") {
59
+ return `${fieldName} is required`;
60
+ }
61
+ return null;
62
+ }
63
+
64
+ /**
65
+ * Validates the confirm-password field.
66
+ * Returns null (no error) when the field is empty — avoids nagging the user
67
+ * before they've had a chance to type anything.
68
+ */
69
+ export function validateConfirmPassword(password: string, confirmPassword: string): string | null {
70
+ if (!confirmPassword) return null;
71
+ if (confirmPassword !== password) {
72
+ return "Passwords do not match";
73
+ }
74
+ return null;
75
+ }
76
+
77
+ export function validateMessage(message: string): string | null {
78
+ if (!message || message.trim() === "") {
79
+ return "Message is required";
80
+ }
81
+ if (message.trim().length < 10) {
82
+ return "Message must be at least 10 characters";
83
+ }
84
+ return null;
85
+ }