omni-sync-sdk 0.3.0 → 0.4.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.
Files changed (2) hide show
  1. package/README.md +1324 -78
  2. package/package.json +24 -3
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # omni-sync-sdk
2
2
 
3
- SDK for integrating vibe coding stores with Omni-Sync Platform.
3
+ Official SDK for building e-commerce storefronts with **OmniSync Platform**.
4
+
5
+ This SDK provides a complete solution for vibe-coded sites, AI-built stores (Cursor, Lovable, v0), and custom storefronts to connect to OmniSync's unified commerce API.
4
6
 
5
7
  ## Installation
6
8
 
@@ -12,99 +14,1274 @@ pnpm add omni-sync-sdk
12
14
  yarn add omni-sync-sdk
13
15
  ```
14
16
 
17
+ ---
18
+
15
19
  ## Quick Start
16
20
 
21
+ ### For Vibe-Coded Sites (Recommended)
22
+
17
23
  ```typescript
18
24
  import { OmniSyncClient } from 'omni-sync-sdk';
19
25
 
26
+ // Initialize with your Connection ID
20
27
  const omni = new OmniSyncClient({
21
- apiKey: process.env.OMNI_SYNC_API_KEY!,
28
+ connectionId: 'vc_YOUR_CONNECTION_ID',
22
29
  });
23
30
 
24
- // Get products
31
+ // Fetch products
25
32
  const { data: products } = await omni.getProducts();
26
33
 
27
- // Create an order
28
- const order = await omni.createOrder({
29
- items: [{ productId: products[0].id, quantity: 2, price: 99.99 }],
30
- customer: { email: 'customer@example.com', name: 'John Doe' },
31
- totalAmount: 199.98,
34
+ // Create a cart
35
+ const cart = await omni.createCart();
36
+
37
+ // Add item to cart
38
+ await omni.addToCart(cart.id, {
39
+ productId: products[0].id,
40
+ quantity: 1,
32
41
  });
33
42
  ```
34
43
 
35
- ## Products
44
+ ---
45
+
46
+ ## Complete Store Setup
47
+
48
+ ### Step 1: Create the OmniSync Client
49
+
50
+ Create a file `lib/omni-sync.ts`:
36
51
 
37
52
  ```typescript
38
- // List products with pagination
39
- const { data, meta } = await omni.getProducts({
53
+ import { OmniSyncClient } from 'omni-sync-sdk';
54
+
55
+ export const omni = new OmniSyncClient({
56
+ connectionId: 'vc_YOUR_CONNECTION_ID', // Your Connection ID from OmniSync
57
+ });
58
+
59
+ // ----- Cart Helpers -----
60
+
61
+ export function getCartId(): string | null {
62
+ if (typeof window === 'undefined') return null;
63
+ return localStorage.getItem('cartId');
64
+ }
65
+
66
+ export function setCartId(id: string): void {
67
+ localStorage.setItem('cartId', id);
68
+ }
69
+
70
+ export function clearCartId(): void {
71
+ localStorage.removeItem('cartId');
72
+ }
73
+
74
+ // ----- Customer Token Helpers -----
75
+
76
+ export function setCustomerToken(token: string | null): void {
77
+ if (token) {
78
+ localStorage.setItem('customerToken', token);
79
+ omni.setCustomerToken(token);
80
+ } else {
81
+ localStorage.removeItem('customerToken');
82
+ omni.clearCustomerToken();
83
+ }
84
+ }
85
+
86
+ export function restoreCustomerToken(): string | null {
87
+ const token = localStorage.getItem('customerToken');
88
+ if (token) omni.setCustomerToken(token);
89
+ return token;
90
+ }
91
+
92
+ export function isLoggedIn(): boolean {
93
+ return !!localStorage.getItem('customerToken');
94
+ }
95
+ ```
96
+
97
+ ---
98
+
99
+ ## API Reference
100
+
101
+ ### Products
102
+
103
+ #### Get Products (with pagination)
104
+
105
+ ```typescript
106
+ import { omni } from '@/lib/omni-sync';
107
+ import type { Product, PaginatedResponse } from 'omni-sync-sdk';
108
+
109
+ const response: PaginatedResponse<Product> = await omni.getProducts({
40
110
  page: 1,
41
- limit: 20,
42
- status: 'active',
43
- search: 'shirt',
111
+ limit: 12,
112
+ search: 'shirt', // Optional: search by name
113
+ status: 'active', // Optional: 'active' | 'draft' | 'archived'
114
+ type: 'SIMPLE', // Optional: 'SIMPLE' | 'VARIABLE'
115
+ sortBy: 'createdAt', // Optional: 'name' | 'createdAt' | 'updatedAt' | 'basePrice'
116
+ sortOrder: 'desc', // Optional: 'asc' | 'desc'
117
+ });
118
+
119
+ console.log(response.data); // Product[]
120
+ console.log(response.meta.total); // Total number of products
121
+ console.log(response.meta.totalPages); // Total pages
122
+ ```
123
+
124
+ #### Get Single Product
125
+
126
+ ```typescript
127
+ const product: Product = await omni.getProduct('product_id');
128
+
129
+ console.log(product.name);
130
+ console.log(product.basePrice);
131
+ console.log(product.salePrice); // null if no sale
132
+ console.log(product.images); // ProductImage[]
133
+ console.log(product.variants); // ProductVariant[] (for VARIABLE products)
134
+ console.log(product.inventory); // { total, reserved, available }
135
+ ```
136
+
137
+ #### Product Type Definition
138
+
139
+ ```typescript
140
+ interface Product {
141
+ id: string;
142
+ name: string;
143
+ description?: string | null;
144
+ sku: string;
145
+ basePrice: number;
146
+ salePrice?: number | null;
147
+ status: 'active' | 'draft' | 'archived';
148
+ type: 'SIMPLE' | 'VARIABLE';
149
+ images?: ProductImage[];
150
+ inventory?: InventoryInfo | null;
151
+ variants?: ProductVariant[];
152
+ categories?: string[];
153
+ tags?: string[];
154
+ createdAt: string;
155
+ updatedAt: string;
156
+ }
157
+
158
+ interface ProductImage {
159
+ url: string;
160
+ position?: number;
161
+ isMain?: boolean;
162
+ }
163
+
164
+ interface ProductVariant {
165
+ id: string;
166
+ sku?: string | null;
167
+ name?: string | null;
168
+ price?: number | null;
169
+ salePrice?: number | null;
170
+ attributes?: Record<string, string>;
171
+ inventory?: InventoryInfo | null;
172
+ }
173
+
174
+ interface InventoryInfo {
175
+ total: number;
176
+ reserved: number;
177
+ available: number;
178
+ }
179
+ ```
180
+
181
+ ---
182
+
183
+ ### Cart
184
+
185
+ #### Create Cart
186
+
187
+ ```typescript
188
+ const cart = await omni.createCart();
189
+ setCartId(cart.id); // Save to localStorage
190
+ ```
191
+
192
+ #### Get Cart
193
+
194
+ ```typescript
195
+ const cartId = getCartId();
196
+ if (cartId) {
197
+ const cart = await omni.getCart(cartId);
198
+ console.log(cart.items); // CartItem[]
199
+ console.log(cart.itemCount); // Total items
200
+ console.log(cart.subtotal); // Subtotal amount
201
+ }
202
+ ```
203
+
204
+ #### Add to Cart
205
+
206
+ ```typescript
207
+ const cart = await omni.addToCart(cartId, {
208
+ productId: 'product_id',
209
+ variantId: 'variant_id', // Optional: for VARIABLE products
210
+ quantity: 2,
211
+ notes: 'Gift wrap please', // Optional
212
+ });
213
+ ```
214
+
215
+ #### Update Cart Item
216
+
217
+ ```typescript
218
+ const cart = await omni.updateCartItem(cartId, itemId, {
219
+ quantity: 3,
220
+ });
221
+ ```
222
+
223
+ #### Remove Cart Item
224
+
225
+ ```typescript
226
+ const cart = await omni.removeCartItem(cartId, itemId);
227
+ ```
228
+
229
+ #### Apply Coupon
230
+
231
+ ```typescript
232
+ const cart = await omni.applyCoupon(cartId, 'SAVE20');
233
+ console.log(cart.discountAmount); // Discount applied
234
+ console.log(cart.couponCode); // 'SAVE20'
235
+ ```
236
+
237
+ #### Remove Coupon
238
+
239
+ ```typescript
240
+ const cart = await omni.removeCoupon(cartId);
241
+ ```
242
+
243
+ #### Cart Type Definition
244
+
245
+ ```typescript
246
+ interface Cart {
247
+ id: string;
248
+ sessionToken?: string | null;
249
+ customerId?: string | null;
250
+ status: 'ACTIVE' | 'MERGED' | 'CONVERTED' | 'ABANDONED';
251
+ currency: string;
252
+ subtotal: string;
253
+ discountAmount: string;
254
+ couponCode?: string | null;
255
+ items: CartItem[];
256
+ itemCount: number;
257
+ createdAt: string;
258
+ updatedAt: string;
259
+ }
260
+
261
+ interface CartItem {
262
+ id: string;
263
+ productId: string;
264
+ variantId?: string | null;
265
+ quantity: number;
266
+ unitPrice: string;
267
+ discountAmount: string;
268
+ notes?: string | null;
269
+ product: {
270
+ id: string;
271
+ name: string;
272
+ sku: string;
273
+ images?: unknown[];
274
+ };
275
+ variant?: {
276
+ id: string;
277
+ name?: string | null;
278
+ sku?: string | null;
279
+ } | null;
280
+ }
281
+ ```
282
+
283
+ ---
284
+
285
+ ### Checkout
286
+
287
+ #### Create Checkout from Cart
288
+
289
+ ```typescript
290
+ const checkout = await omni.createCheckout({
291
+ cartId: cartId,
44
292
  });
293
+ ```
45
294
 
46
- // Get single product
47
- const product = await omni.getProduct('prod_123');
295
+ #### Set Customer Information
48
296
 
49
- // Create product
50
- const newProduct = await omni.createProduct({
51
- name: 'Blue T-Shirt',
52
- sku: 'TSHIRT-BLUE-M',
53
- basePrice: 29.99,
54
- status: 'active',
297
+ ```typescript
298
+ const checkout = await omni.setCheckoutCustomer(checkoutId, {
299
+ email: 'customer@example.com',
300
+ firstName: 'John',
301
+ lastName: 'Doe',
302
+ phone: '+1234567890', // Optional
55
303
  });
304
+ ```
305
+
306
+ #### Set Shipping Address
307
+
308
+ ```typescript
309
+ const { checkout, rates } = await omni.setShippingAddress(checkoutId, {
310
+ firstName: 'John',
311
+ lastName: 'Doe',
312
+ line1: '123 Main St',
313
+ line2: 'Apt 4B', // Optional
314
+ city: 'New York',
315
+ region: 'NY', // State/Province
316
+ postalCode: '10001',
317
+ country: 'US',
318
+ phone: '+1234567890', // Optional
319
+ });
320
+
321
+ // rates contains available shipping options
322
+ console.log(rates); // ShippingRate[]
323
+ ```
324
+
325
+ #### Select Shipping Method
326
+
327
+ ```typescript
328
+ const checkout = await omni.selectShippingMethod(checkoutId, rates[0].id);
329
+ ```
330
+
331
+ #### Set Billing Address
332
+
333
+ ```typescript
334
+ // Same as shipping
335
+ const checkout = await omni.setBillingAddress(checkoutId, {
336
+ ...shippingAddress,
337
+ sameAsShipping: true, // Optional shortcut
338
+ });
339
+ ```
340
+
341
+ #### Complete Checkout
342
+
343
+ ```typescript
344
+ const { orderId } = await omni.completeCheckout(checkoutId);
345
+ clearCartId(); // Clear cart from localStorage
346
+ console.log('Order created:', orderId);
347
+ ```
348
+
349
+ #### Checkout Type Definition
350
+
351
+ ```typescript
352
+ interface Checkout {
353
+ id: string;
354
+ status: CheckoutStatus;
355
+ email?: string | null;
356
+ shippingAddress?: CheckoutAddress | null;
357
+ billingAddress?: CheckoutAddress | null;
358
+ shippingMethod?: ShippingRate | null;
359
+ currency: string;
360
+ subtotal: string;
361
+ discountAmount: string;
362
+ shippingAmount: string;
363
+ taxAmount: string;
364
+ total: string;
365
+ couponCode?: string | null;
366
+ items: CheckoutLineItem[];
367
+ itemCount: number;
368
+ availableShippingRates?: ShippingRate[];
369
+ }
56
370
 
57
- // Update product
58
- const updated = await omni.updateProduct('prod_123', {
59
- salePrice: 24.99,
371
+ type CheckoutStatus =
372
+ | 'PENDING'
373
+ | 'SHIPPING_SET'
374
+ | 'PAYMENT_PENDING'
375
+ | 'COMPLETED'
376
+ | 'FAILED';
377
+
378
+ interface ShippingRate {
379
+ id: string;
380
+ name: string;
381
+ description?: string | null;
382
+ price: string;
383
+ currency: string;
384
+ estimatedDays?: number | null;
385
+ }
386
+ ```
387
+
388
+ ---
389
+
390
+ ### Customer Authentication
391
+
392
+ #### Register Customer
393
+
394
+ ```typescript
395
+ const auth = await omni.registerCustomer({
396
+ email: 'customer@example.com',
397
+ password: 'securepassword123',
398
+ firstName: 'John',
399
+ lastName: 'Doe',
60
400
  });
61
401
 
62
- // Delete product
63
- await omni.deleteProduct('prod_123');
402
+ setCustomerToken(auth.token);
403
+ console.log('Registered:', auth.customer.email);
404
+ ```
405
+
406
+ #### Login Customer
407
+
408
+ ```typescript
409
+ const auth = await omni.loginCustomer('customer@example.com', 'password123');
410
+ setCustomerToken(auth.token);
411
+ ```
412
+
413
+ #### Logout Customer
414
+
415
+ ```typescript
416
+ setCustomerToken(null);
417
+ ```
418
+
419
+ #### Get Customer Profile
420
+
421
+ ```typescript
422
+ restoreCustomerToken(); // Restore from localStorage
423
+ const profile = await omni.getMyProfile();
424
+
425
+ console.log(profile.firstName);
426
+ console.log(profile.email);
427
+ console.log(profile.addresses);
64
428
  ```
65
429
 
66
- ## Orders
430
+ #### Get Customer Orders
67
431
 
68
432
  ```typescript
69
- // Create order (auto-deducts inventory, syncs to all platforms)
70
- const order = await omni.createOrder({
71
- items: [
72
- { productId: 'prod_123', quantity: 1, price: 29.99 },
73
- { productId: 'prod_456', quantity: 2, price: 49.99 },
74
- ],
433
+ const { data: orders, meta } = await omni.getMyOrders({
434
+ page: 1,
435
+ limit: 10,
436
+ });
437
+ ```
438
+
439
+ #### Auth Response Type
440
+
441
+ ```typescript
442
+ interface CustomerAuthResponse {
75
443
  customer: {
76
- email: 'john@example.com',
77
- name: 'John Doe',
78
- phone: '+1234567890',
79
- },
80
- totalAmount: 129.97,
444
+ id: string;
445
+ email: string;
446
+ firstName?: string;
447
+ lastName?: string;
448
+ emailVerified: boolean;
449
+ };
450
+ token: string;
451
+ expiresAt: string;
452
+ }
453
+ ```
454
+
455
+ ---
456
+
457
+ ### Customer Addresses
458
+
459
+ #### Get Addresses
460
+
461
+ ```typescript
462
+ const addresses = await omni.getMyAddresses();
463
+ ```
464
+
465
+ #### Add Address
466
+
467
+ ```typescript
468
+ const address = await omni.addMyAddress({
469
+ firstName: 'John',
470
+ lastName: 'Doe',
471
+ line1: '123 Main St',
472
+ city: 'New York',
473
+ region: 'NY',
474
+ postalCode: '10001',
475
+ country: 'US',
476
+ isDefault: true,
81
477
  });
478
+ ```
479
+
480
+ #### Update Address
82
481
 
83
- // List orders
84
- const { data: orders } = await omni.getOrders({
85
- status: 'pending',
482
+ ```typescript
483
+ const updated = await omni.updateMyAddress(addressId, {
484
+ line1: '456 New Street',
86
485
  });
486
+ ```
487
+
488
+ #### Delete Address
489
+
490
+ ```typescript
491
+ await omni.deleteMyAddress(addressId);
492
+ ```
493
+
494
+ ---
495
+
496
+ ### Store Info
497
+
498
+ ```typescript
499
+ const store = await omni.getStoreInfo();
500
+
501
+ console.log(store.name); // Store name
502
+ console.log(store.currency); // 'USD', 'ILS', etc.
503
+ console.log(store.language); // 'en', 'he', etc.
504
+ ```
505
+
506
+ ---
507
+
508
+ ## Complete Page Examples
509
+
510
+ ### Home Page
511
+
512
+ ```typescript
513
+ 'use client';
514
+ import { useEffect, useState } from 'react';
515
+ import { omni } from '@/lib/omni-sync';
516
+ import type { Product } from 'omni-sync-sdk';
517
+
518
+ export default function HomePage() {
519
+ const [products, setProducts] = useState<Product[]>([]);
520
+ const [loading, setLoading] = useState(true);
521
+ const [error, setError] = useState<string | null>(null);
522
+
523
+ useEffect(() => {
524
+ async function loadProducts() {
525
+ try {
526
+ const { data } = await omni.getProducts({ limit: 8 });
527
+ setProducts(data);
528
+ } catch (err) {
529
+ setError('Failed to load products');
530
+ } finally {
531
+ setLoading(false);
532
+ }
533
+ }
534
+ loadProducts();
535
+ }, []);
536
+
537
+ if (loading) return <div>Loading...</div>;
538
+ if (error) return <div>{error}</div>;
539
+
540
+ return (
541
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-6">
542
+ {products.map((product) => (
543
+ <a key={product.id} href={`/products/${product.id}`} className="group">
544
+ <img
545
+ src={product.images?.[0]?.url || '/placeholder.jpg'}
546
+ alt={product.name}
547
+ className="w-full aspect-square object-cover"
548
+ />
549
+ <h3 className="mt-2 font-medium">{product.name}</h3>
550
+ <p className="text-lg">
551
+ {product.salePrice ? (
552
+ <>
553
+ <span className="text-red-600">${product.salePrice}</span>
554
+ <span className="line-through text-gray-400 ml-2">${product.basePrice}</span>
555
+ </>
556
+ ) : (
557
+ <span>${product.basePrice}</span>
558
+ )}
559
+ </p>
560
+ </a>
561
+ ))}
562
+ </div>
563
+ );
564
+ }
565
+ ```
566
+
567
+ ### Products List with Pagination
568
+
569
+ ```typescript
570
+ 'use client';
571
+ import { useEffect, useState } from 'react';
572
+ import { omni } from '@/lib/omni-sync';
573
+ import type { Product, PaginatedResponse } from 'omni-sync-sdk';
574
+
575
+ export default function ProductsPage() {
576
+ const [data, setData] = useState<PaginatedResponse<Product> | null>(null);
577
+ const [page, setPage] = useState(1);
578
+ const [loading, setLoading] = useState(true);
579
+
580
+ useEffect(() => {
581
+ async function load() {
582
+ setLoading(true);
583
+ try {
584
+ const result = await omni.getProducts({ page, limit: 12 });
585
+ setData(result);
586
+ } finally {
587
+ setLoading(false);
588
+ }
589
+ }
590
+ load();
591
+ }, [page]);
592
+
593
+ if (loading) return <div>Loading...</div>;
594
+ if (!data) return <div>No products found</div>;
595
+
596
+ return (
597
+ <div>
598
+ <div className="grid grid-cols-3 gap-6">
599
+ {data.data.map((product) => (
600
+ <a key={product.id} href={`/products/${product.id}`}>
601
+ <img src={product.images?.[0]?.url} alt={product.name} />
602
+ <h3>{product.name}</h3>
603
+ <p>${product.salePrice || product.basePrice}</p>
604
+ </a>
605
+ ))}
606
+ </div>
607
+
608
+ {/* Pagination */}
609
+ <div className="flex gap-2 mt-8">
610
+ <button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page === 1}>
611
+ Previous
612
+ </button>
613
+ <span>Page {data.meta.page} of {data.meta.totalPages}</span>
614
+ <button onClick={() => setPage(p => p + 1)} disabled={page >= data.meta.totalPages}>
615
+ Next
616
+ </button>
617
+ </div>
618
+ </div>
619
+ );
620
+ }
621
+ ```
622
+
623
+ ### Product Detail with Add to Cart
624
+
625
+ ```typescript
626
+ 'use client';
627
+ import { useEffect, useState } from 'react';
628
+ import { omni, getCartId, setCartId } from '@/lib/omni-sync';
629
+ import type { Product } from 'omni-sync-sdk';
630
+
631
+ export default function ProductPage({ params }: { params: { id: string } }) {
632
+ const [product, setProduct] = useState<Product | null>(null);
633
+ const [selectedVariant, setSelectedVariant] = useState<string | null>(null);
634
+ const [quantity, setQuantity] = useState(1);
635
+ const [loading, setLoading] = useState(true);
636
+ const [adding, setAdding] = useState(false);
637
+
638
+ useEffect(() => {
639
+ async function load() {
640
+ try {
641
+ const p = await omni.getProduct(params.id);
642
+ setProduct(p);
643
+ if (p.variants && p.variants.length > 0) {
644
+ setSelectedVariant(p.variants[0].id);
645
+ }
646
+ } finally {
647
+ setLoading(false);
648
+ }
649
+ }
650
+ load();
651
+ }, [params.id]);
652
+
653
+ const handleAddToCart = async () => {
654
+ if (!product) return;
655
+ setAdding(true);
656
+ try {
657
+ let cartId = getCartId();
658
+
659
+ if (!cartId) {
660
+ const cart = await omni.createCart();
661
+ cartId = cart.id;
662
+ setCartId(cartId);
663
+ }
664
+
665
+ await omni.addToCart(cartId, {
666
+ productId: product.id,
667
+ variantId: selectedVariant || undefined,
668
+ quantity,
669
+ });
670
+
671
+ alert('Added to cart!');
672
+ } catch (err) {
673
+ alert('Failed to add to cart');
674
+ } finally {
675
+ setAdding(false);
676
+ }
677
+ };
678
+
679
+ if (loading) return <div>Loading...</div>;
680
+ if (!product) return <div>Product not found</div>;
681
+
682
+ return (
683
+ <div className="grid grid-cols-2 gap-8">
684
+ {/* Images */}
685
+ <div>
686
+ <img
687
+ src={product.images?.[0]?.url || '/placeholder.jpg'}
688
+ alt={product.name}
689
+ className="w-full"
690
+ />
691
+ </div>
692
+
693
+ {/* Details */}
694
+ <div>
695
+ <h1 className="text-3xl font-bold">{product.name}</h1>
696
+ <p className="text-2xl mt-4">
697
+ ${product.salePrice || product.basePrice}
698
+ </p>
699
+
700
+ {product.description && (
701
+ <p className="mt-4 text-gray-600">{product.description}</p>
702
+ )}
703
+
704
+ {/* Variant Selection */}
705
+ {product.variants && product.variants.length > 0 && (
706
+ <div className="mt-6">
707
+ <label className="block font-medium mb-2">Select Option</label>
708
+ <select
709
+ value={selectedVariant || ''}
710
+ onChange={(e) => setSelectedVariant(e.target.value)}
711
+ className="border rounded p-2 w-full"
712
+ >
713
+ {product.variants.map((v) => (
714
+ <option key={v.id} value={v.id}>
715
+ {v.name || v.sku} - ${v.price || product.basePrice}
716
+ </option>
717
+ ))}
718
+ </select>
719
+ </div>
720
+ )}
721
+
722
+ {/* Quantity */}
723
+ <div className="mt-4">
724
+ <label className="block font-medium mb-2">Quantity</label>
725
+ <input
726
+ type="number"
727
+ min="1"
728
+ value={quantity}
729
+ onChange={(e) => setQuantity(Number(e.target.value))}
730
+ className="border rounded p-2 w-20"
731
+ />
732
+ </div>
733
+
734
+ {/* Add to Cart Button */}
735
+ <button
736
+ onClick={handleAddToCart}
737
+ disabled={adding}
738
+ className="mt-6 w-full bg-black text-white py-3 rounded disabled:opacity-50"
739
+ >
740
+ {adding ? 'Adding...' : 'Add to Cart'}
741
+ </button>
742
+
743
+ {/* Stock Status */}
744
+ {product.inventory && (
745
+ <p className="mt-4 text-sm">
746
+ {product.inventory.available > 0
747
+ ? `${product.inventory.available} in stock`
748
+ : 'Out of stock'}
749
+ </p>
750
+ )}
751
+ </div>
752
+ </div>
753
+ );
754
+ }
755
+ ```
756
+
757
+ ### Cart Page
758
+
759
+ ```typescript
760
+ 'use client';
761
+ import { useEffect, useState } from 'react';
762
+ import { omni, getCartId } from '@/lib/omni-sync';
763
+ import type { Cart } from 'omni-sync-sdk';
764
+
765
+ export default function CartPage() {
766
+ const [cart, setCart] = useState<Cart | null>(null);
767
+ const [loading, setLoading] = useState(true);
768
+ const [updating, setUpdating] = useState<string | null>(null);
769
+
770
+ const loadCart = async () => {
771
+ const cartId = getCartId();
772
+ if (!cartId) {
773
+ setLoading(false);
774
+ return;
775
+ }
776
+ try {
777
+ const c = await omni.getCart(cartId);
778
+ setCart(c);
779
+ } finally {
780
+ setLoading(false);
781
+ }
782
+ };
783
+
784
+ useEffect(() => { loadCart(); }, []);
785
+
786
+ const updateQuantity = async (itemId: string, quantity: number) => {
787
+ if (!cart) return;
788
+ setUpdating(itemId);
789
+ try {
790
+ if (quantity <= 0) {
791
+ await omni.removeCartItem(cart.id, itemId);
792
+ } else {
793
+ await omni.updateCartItem(cart.id, itemId, { quantity });
794
+ }
795
+ await loadCart();
796
+ } finally {
797
+ setUpdating(null);
798
+ }
799
+ };
800
+
801
+ const removeItem = async (itemId: string) => {
802
+ if (!cart) return;
803
+ setUpdating(itemId);
804
+ try {
805
+ await omni.removeCartItem(cart.id, itemId);
806
+ await loadCart();
807
+ } finally {
808
+ setUpdating(null);
809
+ }
810
+ };
811
+
812
+ if (loading) return <div>Loading cart...</div>;
813
+ if (!cart || cart.items.length === 0) {
814
+ return (
815
+ <div className="text-center py-12">
816
+ <h1 className="text-2xl font-bold">Your cart is empty</h1>
817
+ <a href="/products" className="text-blue-600 mt-4 inline-block">Continue Shopping</a>
818
+ </div>
819
+ );
820
+ }
821
+
822
+ return (
823
+ <div>
824
+ <h1 className="text-2xl font-bold mb-6">Shopping Cart</h1>
825
+
826
+ {cart.items.map((item) => (
827
+ <div key={item.id} className="flex items-center gap-4 py-4 border-b">
828
+ <img
829
+ src={item.product.images?.[0]?.url || '/placeholder.jpg'}
830
+ alt={item.product.name}
831
+ className="w-20 h-20 object-cover"
832
+ />
833
+ <div className="flex-1">
834
+ <h3 className="font-medium">{item.product.name}</h3>
835
+ {item.variant && <p className="text-sm text-gray-500">{item.variant.name}</p>}
836
+ <p className="font-bold">${item.unitPrice}</p>
837
+ </div>
838
+ <div className="flex items-center gap-2">
839
+ <button
840
+ onClick={() => updateQuantity(item.id, item.quantity - 1)}
841
+ disabled={updating === item.id}
842
+ className="w-8 h-8 border rounded"
843
+ >-</button>
844
+ <span className="w-8 text-center">{item.quantity}</span>
845
+ <button
846
+ onClick={() => updateQuantity(item.id, item.quantity + 1)}
847
+ disabled={updating === item.id}
848
+ className="w-8 h-8 border rounded"
849
+ >+</button>
850
+ </div>
851
+ <button
852
+ onClick={() => removeItem(item.id)}
853
+ disabled={updating === item.id}
854
+ className="text-red-600"
855
+ >Remove</button>
856
+ </div>
857
+ ))}
858
+
859
+ <div className="mt-6 text-right">
860
+ <p className="text-xl">Subtotal: <strong>${cart.subtotal}</strong></p>
861
+ {cart.discountAmount && Number(cart.discountAmount) > 0 && (
862
+ <p className="text-green-600">Discount: -${cart.discountAmount}</p>
863
+ )}
864
+ <a
865
+ href="/checkout"
866
+ className="mt-4 inline-block bg-black text-white px-8 py-3 rounded"
867
+ >
868
+ Proceed to Checkout
869
+ </a>
870
+ </div>
871
+ </div>
872
+ );
873
+ }
874
+ ```
875
+
876
+ ### Multi-Step Checkout
877
+
878
+ ```typescript
879
+ 'use client';
880
+ import { useEffect, useState } from 'react';
881
+ import { omni, getCartId, clearCartId } from '@/lib/omni-sync';
882
+ import type { Checkout, ShippingRate } from 'omni-sync-sdk';
883
+
884
+ type Step = 'customer' | 'shipping' | 'payment' | 'complete';
885
+
886
+ export default function CheckoutPage() {
887
+ const [checkout, setCheckout] = useState<Checkout | null>(null);
888
+ const [step, setStep] = useState<Step>('customer');
889
+ const [shippingRates, setShippingRates] = useState<ShippingRate[]>([]);
890
+ const [loading, setLoading] = useState(true);
891
+ const [submitting, setSubmitting] = useState(false);
892
+
893
+ // Form state
894
+ const [email, setEmail] = useState('');
895
+ const [firstName, setFirstName] = useState('');
896
+ const [lastName, setLastName] = useState('');
897
+ const [address, setAddress] = useState('');
898
+ const [city, setCity] = useState('');
899
+ const [postalCode, setPostalCode] = useState('');
900
+ const [country, setCountry] = useState('US');
901
+
902
+ useEffect(() => {
903
+ async function initCheckout() {
904
+ const cartId = getCartId();
905
+ if (!cartId) {
906
+ window.location.href = '/cart';
907
+ return;
908
+ }
909
+ try {
910
+ const c = await omni.createCheckout({ cartId });
911
+ setCheckout(c);
912
+ } finally {
913
+ setLoading(false);
914
+ }
915
+ }
916
+ initCheckout();
917
+ }, []);
918
+
919
+ const handleCustomerSubmit = async (e: React.FormEvent) => {
920
+ e.preventDefault();
921
+ if (!checkout) return;
922
+ setSubmitting(true);
923
+ try {
924
+ await omni.setCheckoutCustomer(checkout.id, { email, firstName, lastName });
925
+ setStep('shipping');
926
+ } finally {
927
+ setSubmitting(false);
928
+ }
929
+ };
930
+
931
+ const handleShippingSubmit = async (e: React.FormEvent) => {
932
+ e.preventDefault();
933
+ if (!checkout) return;
934
+ setSubmitting(true);
935
+ try {
936
+ const { rates } = await omni.setShippingAddress(checkout.id, {
937
+ firstName, lastName,
938
+ line1: address,
939
+ city, postalCode, country,
940
+ });
941
+ setShippingRates(rates);
942
+ if (rates.length > 0) {
943
+ await omni.selectShippingMethod(checkout.id, rates[0].id);
944
+ }
945
+ setStep('payment');
946
+ } finally {
947
+ setSubmitting(false);
948
+ }
949
+ };
950
+
951
+ const handleCompleteOrder = async () => {
952
+ if (!checkout) return;
953
+ setSubmitting(true);
954
+ try {
955
+ const { orderId } = await omni.completeCheckout(checkout.id);
956
+ clearCartId();
957
+ setStep('complete');
958
+ } catch (err) {
959
+ alert('Failed to complete order');
960
+ } finally {
961
+ setSubmitting(false);
962
+ }
963
+ };
964
+
965
+ if (loading) return <div>Loading checkout...</div>;
966
+ if (!checkout) return <div>Failed to create checkout</div>;
967
+
968
+ if (step === 'complete') {
969
+ return (
970
+ <div className="text-center py-12">
971
+ <h1 className="text-3xl font-bold text-green-600">Order Complete!</h1>
972
+ <p className="mt-4">Thank you for your purchase.</p>
973
+ <a href="/" className="mt-6 inline-block text-blue-600">Continue Shopping</a>
974
+ </div>
975
+ );
976
+ }
977
+
978
+ return (
979
+ <div className="max-w-2xl mx-auto">
980
+ <h1 className="text-2xl font-bold mb-6">Checkout</h1>
981
+
982
+ {step === 'customer' && (
983
+ <form onSubmit={handleCustomerSubmit} className="space-y-4">
984
+ <input type="email" placeholder="Email" value={email} onChange={e => setEmail(e.target.value)} required className="w-full border p-2 rounded" />
985
+ <div className="grid grid-cols-2 gap-4">
986
+ <input placeholder="First Name" value={firstName} onChange={e => setFirstName(e.target.value)} required className="border p-2 rounded" />
987
+ <input placeholder="Last Name" value={lastName} onChange={e => setLastName(e.target.value)} required className="border p-2 rounded" />
988
+ </div>
989
+ <button type="submit" disabled={submitting} className="w-full bg-black text-white py-3 rounded">
990
+ {submitting ? 'Saving...' : 'Continue to Shipping'}
991
+ </button>
992
+ </form>
993
+ )}
87
994
 
88
- // Update order status
89
- await omni.updateOrder('order_123', { status: 'shipped' });
995
+ {step === 'shipping' && (
996
+ <form onSubmit={handleShippingSubmit} className="space-y-4">
997
+ <input placeholder="Address" value={address} onChange={e => setAddress(e.target.value)} required className="w-full border p-2 rounded" />
998
+ <div className="grid grid-cols-2 gap-4">
999
+ <input placeholder="City" value={city} onChange={e => setCity(e.target.value)} required className="border p-2 rounded" />
1000
+ <input placeholder="Postal Code" value={postalCode} onChange={e => setPostalCode(e.target.value)} required className="border p-2 rounded" />
1001
+ </div>
1002
+ <select value={country} onChange={e => setCountry(e.target.value)} className="w-full border p-2 rounded">
1003
+ <option value="US">United States</option>
1004
+ <option value="IL">Israel</option>
1005
+ <option value="GB">United Kingdom</option>
1006
+ </select>
1007
+ <button type="submit" disabled={submitting} className="w-full bg-black text-white py-3 rounded">
1008
+ {submitting ? 'Calculating Shipping...' : 'Continue to Payment'}
1009
+ </button>
1010
+ </form>
1011
+ )}
1012
+
1013
+ {step === 'payment' && (
1014
+ <div className="space-y-6">
1015
+ <div className="border p-4 rounded">
1016
+ <h3 className="font-bold mb-2">Order Summary</h3>
1017
+ <p>Subtotal: ${checkout.subtotal}</p>
1018
+ <p>Shipping: ${checkout.shippingAmount}</p>
1019
+ <p className="text-xl font-bold mt-2">Total: ${checkout.total}</p>
1020
+ </div>
1021
+ <button onClick={handleCompleteOrder} disabled={submitting} className="w-full bg-green-600 text-white py-3 rounded text-lg">
1022
+ {submitting ? 'Processing...' : 'Complete Order'}
1023
+ </button>
1024
+ </div>
1025
+ )}
1026
+ </div>
1027
+ );
1028
+ }
90
1029
  ```
91
1030
 
92
- ## Inventory
1031
+ ### Login Page
1032
+
1033
+ ```typescript
1034
+ 'use client';
1035
+ import { useState } from 'react';
1036
+ import { omni, setCustomerToken } from '@/lib/omni-sync';
1037
+
1038
+ export default function LoginPage() {
1039
+ const [email, setEmail] = useState('');
1040
+ const [password, setPassword] = useState('');
1041
+ const [error, setError] = useState('');
1042
+ const [loading, setLoading] = useState(false);
1043
+
1044
+ const handleSubmit = async (e: React.FormEvent) => {
1045
+ e.preventDefault();
1046
+ setLoading(true);
1047
+ setError('');
1048
+ try {
1049
+ const auth = await omni.loginCustomer(email, password);
1050
+ setCustomerToken(auth.token);
1051
+ window.location.href = '/account';
1052
+ } catch (err) {
1053
+ setError('Invalid email or password');
1054
+ } finally {
1055
+ setLoading(false);
1056
+ }
1057
+ };
1058
+
1059
+ return (
1060
+ <div className="max-w-md mx-auto mt-12">
1061
+ <h1 className="text-2xl font-bold mb-6">Login</h1>
1062
+ {error && <div className="bg-red-100 text-red-600 p-3 rounded mb-4">{error}</div>}
1063
+ <form onSubmit={handleSubmit} className="space-y-4">
1064
+ <input type="email" placeholder="Email" value={email} onChange={e => setEmail(e.target.value)} required className="w-full border p-2 rounded" />
1065
+ <input type="password" placeholder="Password" value={password} onChange={e => setPassword(e.target.value)} required className="w-full border p-2 rounded" />
1066
+ <button type="submit" disabled={loading} className="w-full bg-black text-white py-3 rounded">
1067
+ {loading ? 'Logging in...' : 'Login'}
1068
+ </button>
1069
+ </form>
1070
+ <p className="mt-4 text-center">
1071
+ Don't have an account? <a href="/register" className="text-blue-600">Register</a>
1072
+ </p>
1073
+ </div>
1074
+ );
1075
+ }
1076
+ ```
1077
+
1078
+ ### Register Page
1079
+
1080
+ ```typescript
1081
+ 'use client';
1082
+ import { useState } from 'react';
1083
+ import { omni, setCustomerToken } from '@/lib/omni-sync';
1084
+
1085
+ export default function RegisterPage() {
1086
+ const [email, setEmail] = useState('');
1087
+ const [password, setPassword] = useState('');
1088
+ const [firstName, setFirstName] = useState('');
1089
+ const [lastName, setLastName] = useState('');
1090
+ const [error, setError] = useState('');
1091
+ const [loading, setLoading] = useState(false);
1092
+
1093
+ const handleSubmit = async (e: React.FormEvent) => {
1094
+ e.preventDefault();
1095
+ setLoading(true);
1096
+ setError('');
1097
+ try {
1098
+ const auth = await omni.registerCustomer({ email, password, firstName, lastName });
1099
+ setCustomerToken(auth.token);
1100
+ window.location.href = '/account';
1101
+ } catch (err) {
1102
+ setError('Registration failed. Email may already be in use.');
1103
+ } finally {
1104
+ setLoading(false);
1105
+ }
1106
+ };
1107
+
1108
+ return (
1109
+ <div className="max-w-md mx-auto mt-12">
1110
+ <h1 className="text-2xl font-bold mb-6">Create Account</h1>
1111
+ {error && <div className="bg-red-100 text-red-600 p-3 rounded mb-4">{error}</div>}
1112
+ <form onSubmit={handleSubmit} className="space-y-4">
1113
+ <div className="grid grid-cols-2 gap-4">
1114
+ <input placeholder="First Name" value={firstName} onChange={e => setFirstName(e.target.value)} required className="border p-2 rounded" />
1115
+ <input placeholder="Last Name" value={lastName} onChange={e => setLastName(e.target.value)} required className="border p-2 rounded" />
1116
+ </div>
1117
+ <input type="email" placeholder="Email" value={email} onChange={e => setEmail(e.target.value)} required className="w-full border p-2 rounded" />
1118
+ <input type="password" placeholder="Password (min 8 characters)" value={password} onChange={e => setPassword(e.target.value)} required minLength={8} className="w-full border p-2 rounded" />
1119
+ <button type="submit" disabled={loading} className="w-full bg-black text-white py-3 rounded">
1120
+ {loading ? 'Creating Account...' : 'Create Account'}
1121
+ </button>
1122
+ </form>
1123
+ <p className="mt-4 text-center">
1124
+ Already have an account? <a href="/login" className="text-blue-600">Login</a>
1125
+ </p>
1126
+ </div>
1127
+ );
1128
+ }
1129
+ ```
1130
+
1131
+ ### Account Page
1132
+
1133
+ ```typescript
1134
+ 'use client';
1135
+ import { useEffect, useState } from 'react';
1136
+ import { omni, restoreCustomerToken, setCustomerToken, isLoggedIn } from '@/lib/omni-sync';
1137
+ import type { CustomerProfile, Order } from 'omni-sync-sdk';
1138
+
1139
+ export default function AccountPage() {
1140
+ const [profile, setProfile] = useState<CustomerProfile | null>(null);
1141
+ const [orders, setOrders] = useState<Order[]>([]);
1142
+ const [loading, setLoading] = useState(true);
1143
+
1144
+ useEffect(() => {
1145
+ restoreCustomerToken();
1146
+ if (!isLoggedIn()) {
1147
+ window.location.href = '/login';
1148
+ return;
1149
+ }
1150
+
1151
+ async function load() {
1152
+ try {
1153
+ const [p, o] = await Promise.all([
1154
+ omni.getMyProfile(),
1155
+ omni.getMyOrders({ limit: 10 }),
1156
+ ]);
1157
+ setProfile(p);
1158
+ setOrders(o.data);
1159
+ } finally {
1160
+ setLoading(false);
1161
+ }
1162
+ }
1163
+ load();
1164
+ }, []);
1165
+
1166
+ const handleLogout = () => {
1167
+ setCustomerToken(null);
1168
+ window.location.href = '/';
1169
+ };
1170
+
1171
+ if (loading) return <div>Loading...</div>;
1172
+ if (!profile) return <div>Please log in</div>;
1173
+
1174
+ return (
1175
+ <div>
1176
+ <div className="flex justify-between items-center mb-8">
1177
+ <h1 className="text-2xl font-bold">My Account</h1>
1178
+ <button onClick={handleLogout} className="text-red-600">Logout</button>
1179
+ </div>
1180
+
1181
+ <div className="grid md:grid-cols-2 gap-8">
1182
+ <div className="border rounded p-6">
1183
+ <h2 className="text-xl font-bold mb-4">Profile</h2>
1184
+ <p><strong>Name:</strong> {profile.firstName} {profile.lastName}</p>
1185
+ <p><strong>Email:</strong> {profile.email}</p>
1186
+ </div>
1187
+
1188
+ <div className="border rounded p-6">
1189
+ <h2 className="text-xl font-bold mb-4">Recent Orders</h2>
1190
+ {orders.length === 0 ? (
1191
+ <p className="text-gray-500">No orders yet</p>
1192
+ ) : (
1193
+ <div className="space-y-4">
1194
+ {orders.map((order) => (
1195
+ <div key={order.id} className="border-b pb-4">
1196
+ <span className="font-medium">#{order.id.slice(-8)}</span>
1197
+ <span className="ml-2 text-sm">{order.status}</span>
1198
+ <p className="font-bold">${order.totalAmount}</p>
1199
+ </div>
1200
+ ))}
1201
+ </div>
1202
+ )}
1203
+ </div>
1204
+ </div>
1205
+ </div>
1206
+ );
1207
+ }
1208
+ ```
1209
+
1210
+ ### Header Component with Cart Count
1211
+
1212
+ ```typescript
1213
+ 'use client';
1214
+ import { useEffect, useState } from 'react';
1215
+ import { omni, getCartId, isLoggedIn } from '@/lib/omni-sync';
1216
+
1217
+ export function Header() {
1218
+ const [cartCount, setCartCount] = useState(0);
1219
+ const [loggedIn, setLoggedIn] = useState(false);
1220
+
1221
+ useEffect(() => {
1222
+ setLoggedIn(isLoggedIn());
1223
+
1224
+ async function loadCart() {
1225
+ const cartId = getCartId();
1226
+ if (cartId) {
1227
+ try {
1228
+ const cart = await omni.getCart(cartId);
1229
+ setCartCount(cart.itemCount);
1230
+ } catch {}
1231
+ }
1232
+ }
1233
+ loadCart();
1234
+ }, []);
1235
+
1236
+ return (
1237
+ <header className="flex justify-between items-center p-4 border-b">
1238
+ <a href="/" className="text-xl font-bold">Store Name</a>
1239
+ <nav className="flex gap-6 items-center">
1240
+ <a href="/products">Shop</a>
1241
+ <a href="/cart" className="relative">
1242
+ Cart
1243
+ {cartCount > 0 && (
1244
+ <span className="absolute -top-2 -right-2 bg-red-600 text-white text-xs w-5 h-5 rounded-full flex items-center justify-center">
1245
+ {cartCount}
1246
+ </span>
1247
+ )}
1248
+ </a>
1249
+ {loggedIn ? (
1250
+ <a href="/account">Account</a>
1251
+ ) : (
1252
+ <a href="/login">Login</a>
1253
+ )}
1254
+ </nav>
1255
+ </header>
1256
+ );
1257
+ }
1258
+ ```
1259
+
1260
+ ---
1261
+
1262
+ ## Error Handling
93
1263
 
94
1264
  ```typescript
95
- // Get current inventory
96
- const inventory = await omni.getInventory('prod_123');
97
- console.log(`Available: ${inventory.available}`);
1265
+ import { OmniSyncClient, OmniSyncError } from 'omni-sync-sdk';
98
1266
 
99
- // Update inventory (syncs to all platforms)
100
- await omni.updateInventory('prod_123', { quantity: 50 });
1267
+ try {
1268
+ const product = await omni.getProduct('invalid_id');
1269
+ } catch (error) {
1270
+ if (error instanceof OmniSyncError) {
1271
+ console.error(`API Error: ${error.message}`);
1272
+ console.error(`Status Code: ${error.statusCode}`);
1273
+ console.error(`Details:`, error.details);
1274
+ }
1275
+ }
101
1276
  ```
102
1277
 
1278
+ ---
1279
+
103
1280
  ## Webhooks
104
1281
 
105
- Receive real-time updates when products, orders, or inventory change on any connected platform.
1282
+ Receive real-time updates when products, orders, or inventory change.
106
1283
 
107
- ### Setup webhook endpoint
1284
+ ### Setup Webhook Endpoint
108
1285
 
109
1286
  ```typescript
110
1287
  // api/webhooks/omni-sync/route.ts (Next.js App Router)
@@ -141,44 +1318,113 @@ export async function POST(req: Request) {
141
1318
 
142
1319
  ### Webhook Events
143
1320
 
144
- | Event | Description |
145
- | ------------------- | ----------------------- |
146
- | `product.created` | New product created |
147
- | `product.updated` | Product details changed |
148
- | `product.deleted` | Product removed |
149
- | `inventory.updated` | Stock levels changed |
150
- | `order.created` | New order received |
151
- | `order.updated` | Order status changed |
1321
+ | Event | Description |
1322
+ |-------|-------------|
1323
+ | `product.created` | New product created |
1324
+ | `product.updated` | Product details changed |
1325
+ | `product.deleted` | Product removed |
1326
+ | `inventory.updated` | Stock levels changed |
1327
+ | `order.created` | New order received |
1328
+ | `order.updated` | Order status changed |
1329
+ | `cart.abandoned` | Cart abandoned (no activity) |
1330
+ | `checkout.completed` | Checkout completed successfully |
152
1331
 
153
- ## Environment Variables
1332
+ ---
154
1333
 
155
- ```env
156
- OMNI_SYNC_API_KEY=omni_your_api_key_here
157
- OMNI_SYNC_WEBHOOK_SECRET=your_webhook_secret_here
158
- ```
1334
+ ## TypeScript Support
159
1335
 
160
- ## Error Handling
1336
+ All types are exported for full TypeScript support:
161
1337
 
162
1338
  ```typescript
163
- import { OmniSyncClient, OmniSyncError } from 'omni-sync-sdk';
1339
+ import type {
1340
+ // Products
1341
+ Product,
1342
+ ProductImage,
1343
+ ProductVariant,
1344
+ InventoryInfo,
1345
+ ProductQueryParams,
1346
+ PaginatedResponse,
164
1347
 
165
- try {
166
- const product = await omni.getProduct('invalid_id');
167
- } catch (error) {
168
- if (error instanceof OmniSyncError) {
169
- console.error(`API Error: ${error.message} (${error.statusCode})`);
170
- }
171
- }
1348
+ // Cart
1349
+ Cart,
1350
+ CartItem,
1351
+ AddToCartDto,
1352
+
1353
+ // Checkout
1354
+ Checkout,
1355
+ CheckoutStatus,
1356
+ ShippingRate,
1357
+ SetShippingAddressDto,
1358
+
1359
+ // Customer
1360
+ Customer,
1361
+ CustomerProfile,
1362
+ CustomerAddress,
1363
+ CustomerAuthResponse,
1364
+
1365
+ // Orders
1366
+ Order,
1367
+ OrderStatus,
1368
+ OrderItem,
1369
+
1370
+ // Webhooks
1371
+ WebhookEvent,
1372
+ WebhookEventType,
1373
+
1374
+ // Errors
1375
+ OmniSyncError,
1376
+ } from 'omni-sync-sdk';
172
1377
  ```
173
1378
 
174
- ## TypeScript Support
1379
+ ---
175
1380
 
176
- Full TypeScript support with exported types:
1381
+ ## Environment Variables
177
1382
 
178
- ```typescript
179
- import type { Product, Order, CreateProductDto, WebhookEvent } from 'omni-sync-sdk';
1383
+ ```env
1384
+ # Required for vibe-coded sites
1385
+ NEXT_PUBLIC_OMNI_CONNECTION_ID=vc_your_connection_id
1386
+
1387
+ # Optional: Override API URL (default: https://api.omni-sync.com)
1388
+ NEXT_PUBLIC_OMNI_API_URL=https://api.omni-sync.com
1389
+
1390
+ # For webhooks (server-side only)
1391
+ OMNI_SYNC_WEBHOOK_SECRET=your_webhook_secret
180
1392
  ```
181
1393
 
1394
+ ---
1395
+
1396
+ ## Required Pages Checklist
1397
+
1398
+ When building a store, implement these pages:
1399
+
1400
+ - [ ] **Home** (`/`) - Product grid
1401
+ - [ ] **Products** (`/products`) - Product list with pagination
1402
+ - [ ] **Product Detail** (`/products/[id]`) - Single product with Add to Cart
1403
+ - [ ] **Cart** (`/cart`) - Cart items, update quantity, remove
1404
+ - [ ] **Checkout** (`/checkout`) - Multi-step checkout flow
1405
+ - [ ] **Login** (`/login`) - Customer login
1406
+ - [ ] **Register** (`/register`) - Customer registration
1407
+ - [ ] **Account** (`/account`) - Profile and order history
1408
+
1409
+ ---
1410
+
1411
+ ## Important Rules
1412
+
1413
+ ### DO:
1414
+ - Install `omni-sync-sdk` and use it for ALL data
1415
+ - Import types from the SDK
1416
+ - Handle loading states and errors
1417
+ - Persist cart ID in localStorage
1418
+ - Persist customer token after login
1419
+
1420
+ ### DON'T:
1421
+ - Create mock/hardcoded product data
1422
+ - Use localStorage for products
1423
+ - Skip implementing required pages
1424
+ - Write `const products = [...]` - use the API!
1425
+
1426
+ ---
1427
+
182
1428
  ## License
183
1429
 
184
1430
  MIT