omni-sync-sdk 0.4.1 → 0.6.1

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