omni-sync-sdk 0.4.1 → 0.6.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.
package/README.md CHANGED
@@ -31,14 +31,73 @@ const omni = new OmniSyncClient({
31
31
  // Fetch products
32
32
  const { data: products } = await omni.getProducts();
33
33
 
34
- // Create a cart
35
- const cart = await omni.createCart();
34
+ // ===== GUEST CHECKOUT (Recommended for most sites) =====
35
+ // Cart stored locally - NO API calls until checkout!
36
36
 
37
- // Add item to cart
38
- await omni.addToCart(cart.id, {
37
+ // Add to local cart (stored in localStorage)
38
+ omni.addToLocalCart({
39
39
  productId: products[0].id,
40
40
  quantity: 1,
41
+ name: products[0].name,
42
+ price: String(products[0].basePrice),
43
+ });
44
+
45
+ // Set customer info
46
+ omni.setLocalCartCustomer({ email: 'customer@example.com' });
47
+ omni.setLocalCartShippingAddress({
48
+ firstName: 'John',
49
+ lastName: 'Doe',
50
+ line1: '123 Main St',
51
+ city: 'New York',
52
+ postalCode: '10001',
53
+ country: 'US',
41
54
  });
55
+
56
+ // Submit order (single API call!)
57
+ const order = await omni.submitGuestOrder();
58
+ console.log('Order created:', order.orderId);
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Two Ways to Handle Cart
64
+
65
+ ### Option 1: Local Cart (Guest Users) - RECOMMENDED
66
+
67
+ For guest users, the cart is stored in **localStorage** - exactly like Amazon, Shopify, and other major platforms do. This means:
68
+ - ✅ No API calls when browsing/adding to cart
69
+ - ✅ Cart persists across page refreshes
70
+ - ✅ Single API call at checkout
71
+ - ✅ No server load for window shoppers
72
+
73
+ ```typescript
74
+ // Add product to local cart
75
+ omni.addToLocalCart({ productId: 'prod_123', quantity: 2 });
76
+
77
+ // View cart
78
+ const cart = omni.getLocalCart();
79
+ console.log('Items:', cart.items.length);
80
+
81
+ // Update quantity
82
+ omni.updateLocalCartItem('prod_123', 5);
83
+
84
+ // Remove item
85
+ omni.removeFromLocalCart('prod_123');
86
+
87
+ // At checkout - submit everything in ONE API call
88
+ const order = await omni.submitGuestOrder();
89
+ ```
90
+
91
+ ### Option 2: Server Cart (Registered Users)
92
+
93
+ For logged-in customers, use server-side cart:
94
+ - ✅ Cart syncs across devices
95
+ - ✅ Abandoned cart recovery
96
+ - ✅ Customer history tracking
97
+
98
+ ```typescript
99
+ const cart = await omni.createCart();
100
+ await omni.addToCart(cart.id, { productId: 'prod_123', quantity: 2 });
42
101
  ```
43
102
 
44
103
  ---
@@ -56,18 +115,28 @@ export const omni = new OmniSyncClient({
56
115
  connectionId: 'vc_YOUR_CONNECTION_ID', // Your Connection ID from OmniSync
57
116
  });
58
117
 
59
- // ----- Cart Helpers -----
118
+ // ----- Guest Cart Helpers (localStorage) -----
119
+
120
+ export function getCartItemCount(): number {
121
+ return omni.getLocalCartItemCount();
122
+ }
123
+
124
+ export function getCart() {
125
+ return omni.getLocalCart();
126
+ }
127
+
128
+ // ----- For Registered Users (server cart) -----
60
129
 
61
- export function getCartId(): string | null {
130
+ export function getServerCartId(): string | null {
62
131
  if (typeof window === 'undefined') return null;
63
132
  return localStorage.getItem('cartId');
64
133
  }
65
134
 
66
- export function setCartId(id: string): void {
135
+ export function setServerCartId(id: string): void {
67
136
  localStorage.setItem('cartId', id);
68
137
  }
69
138
 
70
- export function clearCartId(): void {
139
+ export function clearServerCartId(): void {
71
140
  localStorage.removeItem('cartId');
72
141
  }
73
142
 
@@ -180,13 +249,234 @@ interface InventoryInfo {
180
249
 
181
250
  ---
182
251
 
183
- ### Cart
252
+ ### Local Cart (Guest Users) - RECOMMENDED
253
+
254
+ The local cart stores everything in **localStorage** until checkout. This is the recommended approach for most storefronts.
255
+
256
+ #### Add to Local Cart
257
+
258
+ ```typescript
259
+ // Add item with product info (for display)
260
+ omni.addToLocalCart({
261
+ productId: 'prod_123',
262
+ variantId: 'var_456', // Optional: for products with variants
263
+ quantity: 2,
264
+ name: 'Cool T-Shirt', // Optional: for cart display
265
+ price: '29.99', // Optional: for cart display
266
+ image: 'https://...', // Optional: for cart display
267
+ });
268
+ ```
269
+
270
+ #### Get Local Cart
271
+
272
+ ```typescript
273
+ const cart = omni.getLocalCart();
274
+
275
+ console.log(cart.items); // Array of cart items
276
+ console.log(cart.customer); // Customer info (if set)
277
+ console.log(cart.shippingAddress); // Shipping address (if set)
278
+ console.log(cart.couponCode); // Applied coupon (if any)
279
+ ```
280
+
281
+ #### Update Item Quantity
282
+
283
+ ```typescript
284
+ // Set quantity to 5
285
+ omni.updateLocalCartItem('prod_123', 5);
286
+
287
+ // For variant products
288
+ omni.updateLocalCartItem('prod_123', 3, 'var_456');
289
+
290
+ // Set to 0 to remove
291
+ omni.updateLocalCartItem('prod_123', 0);
292
+ ```
293
+
294
+ #### Remove Item
295
+
296
+ ```typescript
297
+ omni.removeFromLocalCart('prod_123');
298
+ omni.removeFromLocalCart('prod_123', 'var_456'); // With variant
299
+ ```
300
+
301
+ #### Clear Cart
302
+
303
+ ```typescript
304
+ omni.clearLocalCart();
305
+ ```
306
+
307
+ #### Set Customer Info
308
+
309
+ ```typescript
310
+ omni.setLocalCartCustomer({
311
+ email: 'customer@example.com', // Required
312
+ firstName: 'John', // Optional
313
+ lastName: 'Doe', // Optional
314
+ phone: '+1234567890', // Optional
315
+ });
316
+ ```
317
+
318
+ #### Set Shipping Address
319
+
320
+ ```typescript
321
+ omni.setLocalCartShippingAddress({
322
+ firstName: 'John',
323
+ lastName: 'Doe',
324
+ line1: '123 Main St',
325
+ line2: 'Apt 4B', // Optional
326
+ city: 'New York',
327
+ region: 'NY', // Optional: State/Province
328
+ postalCode: '10001',
329
+ country: 'US',
330
+ phone: '+1234567890', // Optional
331
+ });
332
+ ```
333
+
334
+ #### Set Billing Address (Optional)
335
+
336
+ ```typescript
337
+ omni.setLocalCartBillingAddress({
338
+ firstName: 'John',
339
+ lastName: 'Doe',
340
+ line1: '456 Business Ave',
341
+ city: 'New York',
342
+ postalCode: '10002',
343
+ country: 'US',
344
+ });
345
+ ```
346
+
347
+ #### Apply Coupon
348
+
349
+ ```typescript
350
+ omni.setLocalCartCoupon('SAVE20');
351
+
352
+ // Remove coupon
353
+ omni.setLocalCartCoupon(undefined);
354
+ ```
355
+
356
+ #### Get Cart Item Count
357
+
358
+ ```typescript
359
+ const count = omni.getLocalCartItemCount();
360
+ console.log(`${count} items in cart`);
361
+ ```
362
+
363
+ #### Local Cart Type Definition
364
+
365
+ ```typescript
366
+ interface LocalCart {
367
+ items: LocalCartItem[];
368
+ couponCode?: string;
369
+ customer?: {
370
+ email: string;
371
+ firstName?: string;
372
+ lastName?: string;
373
+ phone?: string;
374
+ };
375
+ shippingAddress?: {
376
+ firstName: string;
377
+ lastName: string;
378
+ line1: string;
379
+ line2?: string;
380
+ city: string;
381
+ region?: string;
382
+ postalCode: string;
383
+ country: string;
384
+ phone?: string;
385
+ };
386
+ billingAddress?: { /* same as shipping */ };
387
+ notes?: string;
388
+ updatedAt: string;
389
+ }
390
+
391
+ interface LocalCartItem {
392
+ productId: string;
393
+ variantId?: string;
394
+ quantity: number;
395
+ name?: string;
396
+ sku?: string;
397
+ price?: string;
398
+ image?: string;
399
+ addedAt: string;
400
+ }
401
+ ```
402
+
403
+ ---
404
+
405
+ ### Guest Checkout (Submit Order)
406
+
407
+ Submit the local cart as an order with a **single API call**:
408
+
409
+ ```typescript
410
+ // Make sure cart has items, customer email, and shipping address
411
+ const order = await omni.submitGuestOrder();
412
+
413
+ console.log(order.orderId); // 'order_abc123...'
414
+ console.log(order.orderNumber); // 'ORD-12345'
415
+ console.log(order.status); // 'pending'
416
+ console.log(order.total); // 59.98
417
+ console.log(order.message); // 'Order created successfully'
418
+
419
+ // Cart is automatically cleared after successful order
420
+ ```
421
+
422
+ #### Keep Cart After Order
423
+
424
+ ```typescript
425
+ // If you want to keep the cart data (e.g., for order review page)
426
+ const order = await omni.submitGuestOrder({ clearCartOnSuccess: false });
427
+ ```
428
+
429
+ #### Create Order with Custom Data
430
+
431
+ If you manage cart state yourself instead of using local cart:
432
+
433
+ ```typescript
434
+ const order = await omni.createGuestOrder({
435
+ items: [
436
+ { productId: 'prod_123', quantity: 2 },
437
+ { productId: 'prod_456', variantId: 'var_789', quantity: 1 },
438
+ ],
439
+ customer: {
440
+ email: 'customer@example.com',
441
+ firstName: 'John',
442
+ lastName: 'Doe',
443
+ },
444
+ shippingAddress: {
445
+ firstName: 'John',
446
+ lastName: 'Doe',
447
+ line1: '123 Main St',
448
+ city: 'New York',
449
+ postalCode: '10001',
450
+ country: 'US',
451
+ },
452
+ couponCode: 'SAVE20', // Optional
453
+ notes: 'Please gift wrap', // Optional
454
+ });
455
+ ```
456
+
457
+ #### Guest Order Response Type
458
+
459
+ ```typescript
460
+ interface GuestOrderResponse {
461
+ orderId: string;
462
+ orderNumber: string;
463
+ status: string;
464
+ total: number;
465
+ message: string;
466
+ }
467
+ ```
468
+
469
+ ---
470
+
471
+ ### Server Cart (Registered Users)
472
+
473
+ For logged-in customers who want cart sync across devices.
184
474
 
185
475
  #### Create Cart
186
476
 
187
477
  ```typescript
188
478
  const cart = await omni.createCart();
189
- setCartId(cart.id); // Save to localStorage
479
+ setServerCartId(cart.id); // Save to localStorage
190
480
  ```
191
481
 
192
482
  #### Get Cart
@@ -615,12 +905,12 @@ export default function ProductsPage() {
615
905
  }
616
906
  ```
617
907
 
618
- ### Product Detail with Add to Cart
908
+ ### Product Detail with Add to Cart (Local Cart)
619
909
 
620
910
  ```typescript
621
911
  'use client';
622
912
  import { useEffect, useState } from 'react';
623
- import { omni, getCartId, setCartId } from '@/lib/omni-sync';
913
+ import { omni } from '@/lib/omni-sync';
624
914
  import type { Product } from 'omni-sync-sdk';
625
915
 
626
916
  export default function ProductPage({ params }: { params: { id: string } }) {
@@ -628,7 +918,6 @@ export default function ProductPage({ params }: { params: { id: string } }) {
628
918
  const [selectedVariant, setSelectedVariant] = useState<string | null>(null);
629
919
  const [quantity, setQuantity] = useState(1);
630
920
  const [loading, setLoading] = useState(true);
631
- const [adding, setAdding] = useState(false);
632
921
 
633
922
  useEffect(() => {
634
923
  async function load() {
@@ -645,30 +934,25 @@ export default function ProductPage({ params }: { params: { id: string } }) {
645
934
  load();
646
935
  }, [params.id]);
647
936
 
648
- const handleAddToCart = async () => {
937
+ const handleAddToCart = () => {
649
938
  if (!product) return;
650
- setAdding(true);
651
- try {
652
- let cartId = getCartId();
653
-
654
- if (!cartId) {
655
- const cart = await omni.createCart();
656
- cartId = cart.id;
657
- setCartId(cartId);
658
- }
659
939
 
660
- await omni.addToCart(cartId, {
661
- productId: product.id,
662
- variantId: selectedVariant || undefined,
663
- quantity,
664
- });
665
-
666
- alert('Added to cart!');
667
- } catch (err) {
668
- alert('Failed to add to cart');
669
- } finally {
670
- setAdding(false);
671
- }
940
+ // Get variant if selected
941
+ const variant = selectedVariant
942
+ ? product.variants?.find(v => v.id === selectedVariant)
943
+ : null;
944
+
945
+ // Add to local cart (NO API call!)
946
+ omni.addToLocalCart({
947
+ productId: product.id,
948
+ variantId: selectedVariant || undefined,
949
+ quantity,
950
+ name: variant?.name || product.name,
951
+ price: String(variant?.price || product.salePrice || product.basePrice),
952
+ image: product.images?.[0]?.url,
953
+ });
954
+
955
+ alert('Added to cart!');
672
956
  };
673
957
 
674
958
  if (loading) return <div>Loading...</div>;
@@ -749,63 +1033,33 @@ export default function ProductPage({ params }: { params: { id: string } }) {
749
1033
  }
750
1034
  ```
751
1035
 
752
- ### Cart Page
1036
+ ### Cart Page (Local Cart)
753
1037
 
754
1038
  ```typescript
755
1039
  'use client';
756
- import { useEffect, useState } from 'react';
757
- import { omni, getCartId } from '@/lib/omni-sync';
758
- import type { Cart } from 'omni-sync-sdk';
1040
+ import { useState } from 'react';
1041
+ import { omni } from '@/lib/omni-sync';
1042
+ import type { LocalCart } from 'omni-sync-sdk';
759
1043
 
760
1044
  export default function CartPage() {
761
- const [cart, setCart] = useState<Cart | null>(null);
762
- const [loading, setLoading] = useState(true);
763
- const [updating, setUpdating] = useState<string | null>(null);
1045
+ const [cart, setCart] = useState<LocalCart>(omni.getLocalCart());
764
1046
 
765
- const loadCart = async () => {
766
- const cartId = getCartId();
767
- if (!cartId) {
768
- setLoading(false);
769
- return;
770
- }
771
- try {
772
- const c = await omni.getCart(cartId);
773
- setCart(c);
774
- } finally {
775
- setLoading(false);
776
- }
1047
+ const updateQuantity = (productId: string, quantity: number, variantId?: string) => {
1048
+ const updated = omni.updateLocalCartItem(productId, quantity, variantId);
1049
+ setCart(updated);
777
1050
  };
778
1051
 
779
- useEffect(() => { loadCart(); }, []);
780
-
781
- const updateQuantity = async (itemId: string, quantity: number) => {
782
- if (!cart) return;
783
- setUpdating(itemId);
784
- try {
785
- if (quantity <= 0) {
786
- await omni.removeCartItem(cart.id, itemId);
787
- } else {
788
- await omni.updateCartItem(cart.id, itemId, { quantity });
789
- }
790
- await loadCart();
791
- } finally {
792
- setUpdating(null);
793
- }
1052
+ const removeItem = (productId: string, variantId?: string) => {
1053
+ const updated = omni.removeFromLocalCart(productId, variantId);
1054
+ setCart(updated);
794
1055
  };
795
1056
 
796
- const removeItem = async (itemId: string) => {
797
- if (!cart) return;
798
- setUpdating(itemId);
799
- try {
800
- await omni.removeCartItem(cart.id, itemId);
801
- await loadCart();
802
- } finally {
803
- setUpdating(null);
804
- }
805
- };
1057
+ // Calculate subtotal from local cart
1058
+ const subtotal = cart.items.reduce((sum, item) => {
1059
+ return sum + (parseFloat(item.price || '0') * item.quantity);
1060
+ }, 0);
806
1061
 
807
- if (loading) return <div>Loading cart...</div>;
808
- if (!cart || cart.items.length === 0) {
1062
+ if (cart.items.length === 0) {
809
1063
  return (
810
1064
  <div className="text-center py-12">
811
1065
  <h1 className="text-2xl font-bold">Your cart is empty</h1>
@@ -819,42 +1073,38 @@ export default function CartPage() {
819
1073
  <h1 className="text-2xl font-bold mb-6">Shopping Cart</h1>
820
1074
 
821
1075
  {cart.items.map((item) => (
822
- <div key={item.id} className="flex items-center gap-4 py-4 border-b">
1076
+ <div key={`${item.productId}-${item.variantId || ''}`} className="flex items-center gap-4 py-4 border-b">
823
1077
  <img
824
- src={item.product.images?.[0]?.url || '/placeholder.jpg'}
825
- alt={item.product.name}
1078
+ src={item.image || '/placeholder.jpg'}
1079
+ alt={item.name || 'Product'}
826
1080
  className="w-20 h-20 object-cover"
827
1081
  />
828
1082
  <div className="flex-1">
829
- <h3 className="font-medium">{item.product.name}</h3>
830
- {item.variant && <p className="text-sm text-gray-500">{item.variant.name}</p>}
831
- <p className="font-bold">${item.unitPrice}</p>
1083
+ <h3 className="font-medium">{item.name || 'Product'}</h3>
1084
+ <p className="font-bold">${item.price}</p>
832
1085
  </div>
833
1086
  <div className="flex items-center gap-2">
834
1087
  <button
835
- onClick={() => updateQuantity(item.id, item.quantity - 1)}
836
- disabled={updating === item.id}
1088
+ onClick={() => updateQuantity(item.productId, item.quantity - 1, item.variantId)}
837
1089
  className="w-8 h-8 border rounded"
838
1090
  >-</button>
839
1091
  <span className="w-8 text-center">{item.quantity}</span>
840
1092
  <button
841
- onClick={() => updateQuantity(item.id, item.quantity + 1)}
842
- disabled={updating === item.id}
1093
+ onClick={() => updateQuantity(item.productId, item.quantity + 1, item.variantId)}
843
1094
  className="w-8 h-8 border rounded"
844
1095
  >+</button>
845
1096
  </div>
846
1097
  <button
847
- onClick={() => removeItem(item.id)}
848
- disabled={updating === item.id}
1098
+ onClick={() => removeItem(item.productId, item.variantId)}
849
1099
  className="text-red-600"
850
1100
  >Remove</button>
851
1101
  </div>
852
1102
  ))}
853
1103
 
854
1104
  <div className="mt-6 text-right">
855
- <p className="text-xl">Subtotal: <strong>${cart.subtotal}</strong></p>
856
- {cart.discountAmount && Number(cart.discountAmount) > 0 && (
857
- <p className="text-green-600">Discount: -${cart.discountAmount}</p>
1105
+ <p className="text-xl">Subtotal: <strong>${subtotal.toFixed(2)}</strong></p>
1106
+ {cart.couponCode && (
1107
+ <p className="text-green-600">Coupon applied: {cart.couponCode}</p>
858
1108
  )}
859
1109
  <a
860
1110
  href="/checkout"
@@ -868,12 +1118,182 @@ export default function CartPage() {
868
1118
  }
869
1119
  ```
870
1120
 
871
- ### Multi-Step Checkout
1121
+ ### Guest Checkout (Single API Call)
1122
+
1123
+ This is the recommended checkout for guest users. All cart data is in localStorage, and we submit it in one API call.
1124
+
1125
+ ```typescript
1126
+ 'use client';
1127
+ import { useState, useEffect } from 'react';
1128
+ import { omni } from '@/lib/omni-sync';
1129
+ import type { LocalCart, GuestOrderResponse } from 'omni-sync-sdk';
1130
+
1131
+ type Step = 'info' | 'review' | 'complete';
1132
+
1133
+ export default function CheckoutPage() {
1134
+ const [cart, setCart] = useState<LocalCart>(omni.getLocalCart());
1135
+ const [step, setStep] = useState<Step>('info');
1136
+ const [order, setOrder] = useState<GuestOrderResponse | null>(null);
1137
+ const [submitting, setSubmitting] = useState(false);
1138
+ const [error, setError] = useState('');
1139
+
1140
+ // Form state
1141
+ const [email, setEmail] = useState(cart.customer?.email || '');
1142
+ const [firstName, setFirstName] = useState(cart.customer?.firstName || '');
1143
+ const [lastName, setLastName] = useState(cart.customer?.lastName || '');
1144
+ const [address, setAddress] = useState(cart.shippingAddress?.line1 || '');
1145
+ const [city, setCity] = useState(cart.shippingAddress?.city || '');
1146
+ const [postalCode, setPostalCode] = useState(cart.shippingAddress?.postalCode || '');
1147
+ const [country, setCountry] = useState(cart.shippingAddress?.country || 'US');
1148
+
1149
+ // Calculate subtotal
1150
+ const subtotal = cart.items.reduce((sum, item) => {
1151
+ return sum + (parseFloat(item.price || '0') * item.quantity);
1152
+ }, 0);
1153
+
1154
+ // Redirect if cart is empty
1155
+ useEffect(() => {
1156
+ if (cart.items.length === 0 && step !== 'complete') {
1157
+ window.location.href = '/cart';
1158
+ }
1159
+ }, [cart.items.length, step]);
1160
+
1161
+ const handleInfoSubmit = (e: React.FormEvent) => {
1162
+ e.preventDefault();
1163
+
1164
+ // Save to local cart
1165
+ omni.setLocalCartCustomer({ email, firstName, lastName });
1166
+ omni.setLocalCartShippingAddress({
1167
+ firstName,
1168
+ lastName,
1169
+ line1: address,
1170
+ city,
1171
+ postalCode,
1172
+ country,
1173
+ });
1174
+
1175
+ setStep('review');
1176
+ };
1177
+
1178
+ const handlePlaceOrder = async () => {
1179
+ setSubmitting(true);
1180
+ setError('');
1181
+
1182
+ try {
1183
+ // Single API call to create order!
1184
+ const result = await omni.submitGuestOrder();
1185
+ setOrder(result);
1186
+ setStep('complete');
1187
+ } catch (err) {
1188
+ setError(err instanceof Error ? err.message : 'Failed to place order');
1189
+ } finally {
1190
+ setSubmitting(false);
1191
+ }
1192
+ };
1193
+
1194
+ if (step === 'complete' && order) {
1195
+ return (
1196
+ <div className="text-center py-12">
1197
+ <h1 className="text-3xl font-bold text-green-600">Order Complete!</h1>
1198
+ <p className="mt-4">Order Number: <strong>{order.orderNumber}</strong></p>
1199
+ <p className="mt-2">Total: <strong>${order.total.toFixed(2)}</strong></p>
1200
+ <p className="mt-4 text-gray-600">A confirmation email will be sent to {email}</p>
1201
+ <a href="/" className="mt-6 inline-block text-blue-600">Continue Shopping</a>
1202
+ </div>
1203
+ );
1204
+ }
1205
+
1206
+ return (
1207
+ <div className="max-w-2xl mx-auto">
1208
+ <h1 className="text-2xl font-bold mb-6">Checkout</h1>
1209
+
1210
+ {error && (
1211
+ <div className="bg-red-100 text-red-600 p-3 rounded mb-4">{error}</div>
1212
+ )}
1213
+
1214
+ {step === 'info' && (
1215
+ <form onSubmit={handleInfoSubmit} className="space-y-4">
1216
+ <h2 className="text-lg font-bold">Contact Information</h2>
1217
+ <input
1218
+ type="email"
1219
+ placeholder="Email"
1220
+ value={email}
1221
+ onChange={e => setEmail(e.target.value)}
1222
+ required
1223
+ className="w-full border p-2 rounded"
1224
+ />
1225
+
1226
+ <h2 className="text-lg font-bold mt-6">Shipping Address</h2>
1227
+ <div className="grid grid-cols-2 gap-4">
1228
+ <input placeholder="First Name" value={firstName} onChange={e => setFirstName(e.target.value)} required className="border p-2 rounded" />
1229
+ <input placeholder="Last Name" value={lastName} onChange={e => setLastName(e.target.value)} required className="border p-2 rounded" />
1230
+ </div>
1231
+ <input placeholder="Address" value={address} onChange={e => setAddress(e.target.value)} required className="w-full border p-2 rounded" />
1232
+ <div className="grid grid-cols-2 gap-4">
1233
+ <input placeholder="City" value={city} onChange={e => setCity(e.target.value)} required className="border p-2 rounded" />
1234
+ <input placeholder="Postal Code" value={postalCode} onChange={e => setPostalCode(e.target.value)} required className="border p-2 rounded" />
1235
+ </div>
1236
+ <select value={country} onChange={e => setCountry(e.target.value)} className="w-full border p-2 rounded">
1237
+ <option value="US">United States</option>
1238
+ <option value="IL">Israel</option>
1239
+ <option value="GB">United Kingdom</option>
1240
+ </select>
1241
+
1242
+ <button type="submit" className="w-full bg-black text-white py-3 rounded">
1243
+ Review Order
1244
+ </button>
1245
+ </form>
1246
+ )}
1247
+
1248
+ {step === 'review' && (
1249
+ <div className="space-y-6">
1250
+ {/* Order Summary */}
1251
+ <div className="border p-4 rounded">
1252
+ <h3 className="font-bold mb-4">Order Summary</h3>
1253
+ {cart.items.map((item) => (
1254
+ <div key={`${item.productId}-${item.variantId || ''}`} className="flex justify-between py-2">
1255
+ <span>{item.name} x {item.quantity}</span>
1256
+ <span>${(parseFloat(item.price || '0') * item.quantity).toFixed(2)}</span>
1257
+ </div>
1258
+ ))}
1259
+ <hr className="my-2" />
1260
+ <div className="flex justify-between font-bold">
1261
+ <span>Total</span>
1262
+ <span>${subtotal.toFixed(2)}</span>
1263
+ </div>
1264
+ </div>
1265
+
1266
+ {/* Shipping Info */}
1267
+ <div className="border p-4 rounded">
1268
+ <h3 className="font-bold mb-2">Shipping To</h3>
1269
+ <p>{firstName} {lastName}</p>
1270
+ <p>{address}</p>
1271
+ <p>{city}, {postalCode}, {country}</p>
1272
+ <button onClick={() => setStep('info')} className="text-blue-600 text-sm mt-2">Edit</button>
1273
+ </div>
1274
+
1275
+ <button
1276
+ onClick={handlePlaceOrder}
1277
+ disabled={submitting}
1278
+ className="w-full bg-green-600 text-white py-3 rounded text-lg"
1279
+ >
1280
+ {submitting ? 'Processing...' : 'Place Order'}
1281
+ </button>
1282
+ </div>
1283
+ )}
1284
+ </div>
1285
+ );
1286
+ }
1287
+ ```
1288
+
1289
+ ### Multi-Step Checkout (Server Cart - For Registered Users)
1290
+
1291
+ For logged-in users with server-side cart:
872
1292
 
873
1293
  ```typescript
874
1294
  'use client';
875
1295
  import { useEffect, useState } from 'react';
876
- import { omni, getCartId, clearCartId } from '@/lib/omni-sync';
1296
+ import { omni, getServerCartId } from '@/lib/omni-sync';
877
1297
  import type { Checkout, ShippingRate } from 'omni-sync-sdk';
878
1298
 
879
1299
  type Step = 'customer' | 'shipping' | 'payment' | 'complete';
@@ -896,7 +1316,7 @@ export default function CheckoutPage() {
896
1316
 
897
1317
  useEffect(() => {
898
1318
  async function initCheckout() {
899
- const cartId = getCartId();
1319
+ const cartId = getServerCartId();
900
1320
  if (!cartId) {
901
1321
  window.location.href = '/cart';
902
1322
  return;
@@ -948,7 +1368,6 @@ export default function CheckoutPage() {
948
1368
  setSubmitting(true);
949
1369
  try {
950
1370
  const { orderId } = await omni.completeCheckout(checkout.id);
951
- clearCartId();
952
1371
  setStep('complete');
953
1372
  } catch (err) {
954
1373
  alert('Failed to complete order');
@@ -1202,12 +1621,12 @@ export default function AccountPage() {
1202
1621
  }
1203
1622
  ```
1204
1623
 
1205
- ### Header Component with Cart Count
1624
+ ### Header Component with Cart Count (Local Cart)
1206
1625
 
1207
1626
  ```typescript
1208
1627
  'use client';
1209
- import { useEffect, useState } from 'react';
1210
- import { omni, getCartId, isLoggedIn } from '@/lib/omni-sync';
1628
+ import { useState, useEffect } from 'react';
1629
+ import { omni, isLoggedIn } from '@/lib/omni-sync';
1211
1630
 
1212
1631
  export function Header() {
1213
1632
  const [cartCount, setCartCount] = useState(0);
@@ -1215,17 +1634,8 @@ export function Header() {
1215
1634
 
1216
1635
  useEffect(() => {
1217
1636
  setLoggedIn(isLoggedIn());
1218
-
1219
- async function loadCart() {
1220
- const cartId = getCartId();
1221
- if (cartId) {
1222
- try {
1223
- const cart = await omni.getCart(cartId);
1224
- setCartCount(cart.itemCount);
1225
- } catch {}
1226
- }
1227
- }
1228
- loadCart();
1637
+ // Get cart count from local storage (NO API call!)
1638
+ setCartCount(omni.getLocalCartItemCount());
1229
1639
  }, []);
1230
1640
 
1231
1641
  return (
@@ -1340,7 +1750,13 @@ import type {
1340
1750
  ProductQueryParams,
1341
1751
  PaginatedResponse,
1342
1752
 
1343
- // Cart
1753
+ // Local Cart (Guest Users)
1754
+ LocalCart,
1755
+ LocalCartItem,
1756
+ CreateGuestOrderDto,
1757
+ GuestOrderResponse,
1758
+
1759
+ // Server Cart (Registered Users)
1344
1760
  Cart,
1345
1761
  CartItem,
1346
1762
  AddToCartDto,