@kasuvia/sdk 0.0.2 → 0.0.4
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/dist/hooks/index.d.mts +429 -0
- package/dist/hooks/index.d.ts +429 -0
- package/dist/hooks/index.js +3073 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +2962 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +250 -1161
- package/dist/index.d.ts +250 -1161
- package/dist/index.js +1544 -2359
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1527 -2258
- package/dist/index.mjs.map +1 -1
- package/dist/sdk-context-Cy51kEKT.d.mts +565 -0
- package/dist/sdk-context-Cy51kEKT.d.ts +565 -0
- package/package.json +6 -2
- package/src/config/index.ts +10 -91
- package/src/constants.ts +13 -0
- package/src/context/sdk-context.tsx +70 -0
- package/src/graphql/collections.graphql.ts +2 -10
- package/src/graphql/products.graphql.ts +215 -65
- package/src/graphql/wishlist.graphql.ts +7 -6
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-auth.ts +82 -158
- package/src/hooks/use-bespoke-availability.ts +25 -8
- package/src/hooks/use-cart.ts +222 -119
- package/src/hooks/use-cms.ts +179 -86
- package/src/hooks/use-collections.ts +52 -15
- package/src/hooks/use-consultation-stream.ts +4 -3
- package/src/hooks/use-consultations.ts +120 -51
- package/src/hooks/use-countries.ts +35 -13
- package/src/hooks/use-currencies.ts +26 -13
- package/src/hooks/use-google-translate.ts +22 -8
- package/src/hooks/use-languages.ts +40 -20
- package/src/hooks/use-mobile.ts +23 -0
- package/src/hooks/use-order-stream.ts +9 -5
- package/src/hooks/use-orders.ts +99 -44
- package/src/hooks/use-payment.ts +108 -40
- package/src/hooks/use-products.ts +251 -53
- package/src/hooks/use-service-booking.ts +152 -90
- package/src/hooks/use-states.ts +35 -15
- package/src/hooks/use-wishlist.ts +92 -91
- package/src/index.ts +26 -30
- package/src/services/auth.service.ts +209 -167
- package/src/services/bespoke.service.ts +12 -4
- package/src/services/cart.service.ts +172 -70
- package/src/services/cms.service.ts +70 -35
- package/src/services/collection.service.ts +22 -5
- package/src/services/config.service.ts +34 -14
- package/src/services/consultation.service.ts +72 -18
- package/src/services/logistics.service.ts +35 -11
- package/src/services/notifications.service.ts +27 -12
- package/src/services/order.service.ts +74 -15
- package/src/services/payment.service.ts +86 -31
- package/src/services/product.service.ts +279 -39
- package/src/services/service-booking.service.ts +98 -26
- package/src/services/wishlist.service.ts +69 -25
- package/src/types/auth/index.ts +6 -4
- package/src/types/business/index.ts +7 -2
- package/src/types/payments/index.ts +3 -3
- package/src/types/services/index.ts +2 -5
- package/src/types/store/index.ts +38 -3
- package/src/utils/error-handler.ts +10 -5
- package/src/utils/storage.ts +48 -26
- package/src/utils/validators.ts +85 -0
- package/src/client/authClient.ts +0 -135
- package/src/client/graphqlClient.ts +0 -53
- 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 {
|
|
7
|
-
import {
|
|
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
|
|
11
|
-
query
|
|
12
|
-
|
|
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
|
|
32
|
-
query
|
|
33
|
-
|
|
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 =
|
|
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(
|
|
49
|
-
return data.
|
|
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 =
|
|
57
|
-
const data = await client.request(
|
|
58
|
-
return data.
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
}
|
package/src/types/auth/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Simplified customer auth types for
|
|
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
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
4
|
-
|
|
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
|
|
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 →
|
|
13
|
-
*
|
|
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
|
}
|
package/src/types/store/index.ts
CHANGED
|
@@ -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?:
|
|
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:
|
|
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
|
-
|
|
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
|
}
|
package/src/utils/storage.ts
CHANGED
|
@@ -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
|
|
10
|
-
|
|
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: (
|
|
14
|
+
get: (businessIdOrKey: string, key?: string): string | null => {
|
|
17
15
|
if (typeof window === 'undefined') return null
|
|
18
|
-
|
|
16
|
+
const bId = key ? businessIdOrKey : ''
|
|
17
|
+
const k = key || businessIdOrKey
|
|
18
|
+
return sessionStorage.getItem(getPrefixedKey(bId, k))
|
|
19
19
|
},
|
|
20
|
-
set: (
|
|
20
|
+
set: (businessIdOrKey: string, keyOrVal: string, val?: string): void => {
|
|
21
21
|
if (typeof window === 'undefined') return
|
|
22
|
-
|
|
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: (
|
|
27
|
+
remove: (businessIdOrKey: string, key?: string): void => {
|
|
25
28
|
if (typeof window === 'undefined') return
|
|
26
|
-
|
|
29
|
+
const bId = key ? businessIdOrKey : ''
|
|
30
|
+
const k = key || businessIdOrKey
|
|
31
|
+
sessionStorage.removeItem(getPrefixedKey(bId, k))
|
|
27
32
|
},
|
|
28
|
-
getJSON: <T = unknown>(
|
|
33
|
+
getJSON: <T = unknown>(businessIdOrKey: string, key?: string): T | null => {
|
|
29
34
|
if (typeof window === 'undefined') return null
|
|
30
|
-
const
|
|
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: (
|
|
45
|
+
setJSON: (businessIdOrKey: string, keyOrVal: unknown, val?: unknown): void => {
|
|
39
46
|
if (typeof window === 'undefined') return
|
|
40
|
-
|
|
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: (
|
|
55
|
+
get: (businessIdOrKey: string, key?: string): string | null => {
|
|
46
56
|
if (typeof window === 'undefined') return null
|
|
47
|
-
|
|
57
|
+
const bId = key ? businessIdOrKey : ''
|
|
58
|
+
const k = key || businessIdOrKey
|
|
59
|
+
return localStorage.getItem(getPrefixedKey(bId, k))
|
|
48
60
|
},
|
|
49
|
-
set: (
|
|
61
|
+
set: (businessIdOrKey: string, keyOrVal: string, val?: string): void => {
|
|
50
62
|
if (typeof window === 'undefined') return
|
|
51
|
-
|
|
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: (
|
|
68
|
+
remove: (businessIdOrKey: string, key?: string): void => {
|
|
54
69
|
if (typeof window === 'undefined') return
|
|
55
|
-
|
|
70
|
+
const bId = key ? businessIdOrKey : ''
|
|
71
|
+
const k = key || businessIdOrKey
|
|
72
|
+
localStorage.removeItem(getPrefixedKey(bId, k))
|
|
56
73
|
},
|
|
57
|
-
getJSON: <T = unknown>(
|
|
74
|
+
getJSON: <T = unknown>(businessIdOrKey: string, key?: string): T | null => {
|
|
58
75
|
if (typeof window === 'undefined') return null
|
|
59
|
-
const
|
|
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: (
|
|
86
|
+
setJSON: (businessIdOrKey: string, keyOrVal: unknown, val?: unknown): void => {
|
|
68
87
|
if (typeof window === 'undefined') return
|
|
69
|
-
|
|
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
|
+
}
|