From 7b94537670ca5e75051db5588279275a3b1dbbfe Mon Sep 17 00:00:00 2001 From: Unchained Date: Sat, 21 Mar 2026 12:36:21 +0200 Subject: [PATCH] feat(saleor): Phase 1 - GraphQL Client Setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Apollo Client for Saleor GraphQL API - Create GraphQL fragments (Product, Variant, Checkout) - Create GraphQL queries (Products, Checkout) - Create GraphQL mutations (Checkout operations) - Add TypeScript types for Saleor entities - Add product helper functions - Install @apollo/client and graphql dependencies Part of WordPress/WooCommerce β†’ Saleor migration --- ASSET_INVENTORY.md | 135 ++++ MIGRATION_GUIDE.md | 271 ++++++++ SALEOR_MIGRATION_PLAN.md | 528 +++++++++++++++ ecommerce-features-checklist.md | 460 +++++++++++++ infrastructure-overview.md | 302 +++++++++ mautic-abandoned-cart.md | 466 +++++++++++++ media-migration-guide.md | 449 +++++++++++++ package-lock.json | 153 ++++- package.json | 2 + saleor-features.md | 296 ++++++++ saleor-migration.md | 521 +++++++++++++++ scripts/EMAIL_REACTIVATION_CAMPAIGNS.md | 304 +++++++++ scripts/migrate_all_users_and_orders.py | 852 ++++++++++++++++++++++++ scripts/migrate_cod_orders.py | 576 ++++++++++++++++ scripts/migrate_complete.py | 785 ++++++++++++++++++++++ scripts/migrate_guest_orders.py | 736 ++++++++++++++++++++ scripts/migrate_guest_orders.sql | 201 ++++++ src/lib/saleor/client.ts | 49 ++ src/lib/saleor/fragments/Checkout.ts | 74 ++ src/lib/saleor/fragments/Product.ts | 81 +++ src/lib/saleor/fragments/Variant.ts | 84 +++ src/lib/saleor/index.ts | 35 + src/lib/saleor/mutations/Checkout.ts | 154 +++++ src/lib/saleor/products.ts | 105 +++ src/lib/saleor/queries/Checkout.ts | 21 + src/lib/saleor/queries/Products.ts | 51 ++ src/types/saleor.ts | 191 ++++++ 27 files changed, 7879 insertions(+), 3 deletions(-) create mode 100644 ASSET_INVENTORY.md create mode 100644 MIGRATION_GUIDE.md create mode 100644 SALEOR_MIGRATION_PLAN.md create mode 100644 ecommerce-features-checklist.md create mode 100644 infrastructure-overview.md create mode 100644 mautic-abandoned-cart.md create mode 100644 media-migration-guide.md create mode 100644 saleor-features.md create mode 100644 saleor-migration.md create mode 100644 scripts/EMAIL_REACTIVATION_CAMPAIGNS.md create mode 100644 scripts/migrate_all_users_and_orders.py create mode 100644 scripts/migrate_cod_orders.py create mode 100644 scripts/migrate_complete.py create mode 100644 scripts/migrate_guest_orders.py create mode 100644 scripts/migrate_guest_orders.sql create mode 100644 src/lib/saleor/client.ts create mode 100644 src/lib/saleor/fragments/Checkout.ts create mode 100644 src/lib/saleor/fragments/Product.ts create mode 100644 src/lib/saleor/fragments/Variant.ts create mode 100644 src/lib/saleor/index.ts create mode 100644 src/lib/saleor/mutations/Checkout.ts create mode 100644 src/lib/saleor/products.ts create mode 100644 src/lib/saleor/queries/Checkout.ts create mode 100644 src/lib/saleor/queries/Products.ts create mode 100644 src/types/saleor.ts diff --git a/ASSET_INVENTORY.md b/ASSET_INVENTORY.md new file mode 100644 index 0000000..534947c --- /dev/null +++ b/ASSET_INVENTORY.md @@ -0,0 +1,135 @@ +# Manoon Assets Migration Inventory +## Date: March 20, 2026 +## Source: WordPress (manoon-media bucket) +## Destination: Saleor (saleor bucket) + +--- + +## πŸ“ FOLDER STRUCTURE + +``` +saleor/ +β”œβ”€β”€ brand/ # Logos and brand assets +β”œβ”€β”€ content/ # Blog, articles, general content +β”œβ”€β”€ marketing/ # Before/after, testimonials, banners +β”œβ”€β”€ products/ # Product images (migrated first) +└── thumbnails/ # Auto-generated by Saleor +``` + +--- + +## 🎨 BRAND ASSETS (35 files) + +### Main Logo Files +| File | Size | Purpose | Recommended Use | +|------|------|---------|-----------------| +| `cropped-manoon-logo_256x.png` | 38KB | Main logo | Header, footer | +| `cropped-manoon-logo_256x-300x300.png` | 20KB | Square format | Social media, favicon | +| `cropped-manoon-logo_256x-416x416.png` | 30KB | Large square | High-res displays | + +### Partner/Press Logos +| File | Brand | Use Case | +|------|-------|----------| +| `bazaar-logo.png` | Bazaar Magazine | As seen in/press section | +| `cosmopolitan-logo.png` | Cosmopolitan | As seen in/press section | +| `lepotazdravilja-logo.png` | Lepota Zdravlja | As seen in/press section | + +**Full URL:** `https://minio-api.nodecrew.me/saleor/brand/{filename}` + +--- + +## πŸ“Έ BEFORE/AFTER IMAGES (65 files) + +### Hair Results +| File | Description | +|------|-------------| +| `hair-before-after-1_1.webp` | Hair elixir result #1 | +| `hair-before-after-2_1.webp` | Hair elixir result #2 | +| `hair-before-after-3_1.webp` | Hair elixir result #3 | +| `hair-before-after-4_1.webp` | Hair elixir result #4 | +| `hair-before-after-5_1.webp` | Hair elixir result #5 | + +### Skin Results +| File | Description | +|------|-------------| +| `manoon-before-after-1_cleanup-compressed_1280x.jpg` | Serum result | +| `manoon-before-after-2_cleanup_1-compressed_1280x.jpg` | Serum result 2 | +| `manoon-before-after-3_cleanup-compressed_1280x.jpg` | Serum result 3 | +| `marlene-before-after_cleanup-compressed_1280x.jpg` | Customer Marlene | +| `susanne-before-after_cleanup-compressed_1280x.jpg` | Customer Susanne | + +**Full URL:** `https://minio-api.nodecrew.me/saleor/marketing/{filename}` + +--- + +## πŸ’¬ TESTIMONIAL IMAGES (67 files) + +| File | Description | +|------|-------------| +| `Image-Testemonials-2.jpeg` | Testimonial featured image | +| `testimonial10_1280x.jpg` | Customer testimonial #10 | +| `testimonial11_1280x.jpg` | Customer testimonial #11 | +| `testimonial15_1280x.jpg` | Customer testimonial #15 | + +**Full URL:** `https://minio-api.nodecrew.me/saleor/marketing/{filename}` + +--- + +## πŸ›οΈ PRODUCT IMAGES (9 main + thumbnails) + +| Product | Main Image | Gallery Images | +|---------|-----------|----------------| +| Morning Glow | `morning-glow-main.jpg` | `morning-glow-gallery-1.jpg` | +| Hair Elixir | `hair-elixir-main.webp` | - | +| Anti-age Serum | `anti-age-serum-main.jpg` | `anti-age-serum-gallery-1.jpg`, `anti-age-serum-gallery-2.jpg` | +| Luksuzni Set | `luksuzni-set-main.jpg` | `luksuzni-set-gallery-1.jpg`, `luksuzni-set-gallery-2.jpg` | + +**Full URL:** `https://minio-api.nodecrew.me/saleor/products/{filename}` + +--- + +## πŸ“ CONTENT IMAGES (25 files) + +Various blog/article images, WhatsApp uploads, and other content assets. + +**Full URL:** `https://minio-api.nodecrew.me/saleor/content/{filename}` + +--- + +## πŸ”— QUICK REFERENCE URLS + +### CDN Base URL +``` +https://minio-api.nodecrew.me/saleor/ +``` + +### Direct Access Examples +``` +Logo: https://minio-api.nodecrew.me/saleor/brand/cropped-manoon-logo_256x.png +Product: https://minio-api.nodecrew.me/saleor/products/morning-glow-main.jpg +Marketing: https://minio-api.nodecrew.me/saleor/marketing/hair-before-after-1_1.webp +Content: https://minio-api.nodecrew.me/saleor/content/{filename} +``` + +--- + +## 🎯 NEXT STEPS FOR STOREFRONT + +1. **Hero Section**: Use logo from `/brand/` folder +2. **Product Pages**: Use images from `/products/` folder +3. **Results/Social Proof**: Use before/after from `/marketing/` folder +4. **Testimonials**: Use testimonial images from `/marketing/` folder +5. **Press/As Seen In**: Use partner logos from `/brand/` folder + +--- + +## πŸ“Š TOTAL ASSETS MIGRATED + +| Category | Count | Folder | +|----------|-------|--------| +| Brand/Logos | 35 | `/brand/` | +| Products | 9 | `/products/` | +| Before/After | 65 | `/marketing/` | +| Testimonials | 67 | `/marketing/` | +| Content | 25 | `/content/` | +| **TOTAL** | **201** | - | diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md new file mode 100644 index 0000000..8a34970 --- /dev/null +++ b/MIGRATION_GUIDE.md @@ -0,0 +1,271 @@ +# WooCommerce to Saleor Migration Guide + +## Migration Summary + +| Component | Status | Notes | +|-----------|--------|-------| +| **Products** | βœ… Complete | 4 products with variants, SKUs, pricing (RSD) | +| **Assets** | βœ… Complete | 204 files migrated to organized folders | +| **Inventory** | βœ… Complete | track_inventory=false, stock records added | +| **Translations** | βœ… Complete | English translations added | +| **Users** | ⏳ Ready | **4,886 total** (1,172 with orders + 2,714 prospects) | +| **Orders** | ⏳ Ready | 1,786 COD orders | + +--- + +## 1. Product Migration (DONE) + +Products migrated with: +- SKUs mapped directly +- Prices in RSD (Serbian Dinar) +- Published status +- Channel listings configured +- Inventory settings: `track_inventory=false` + +### Product SKU Mapping + +| WooCommerce SKU | Saleor SKU | Product | +|----------------|------------|---------| +| morning-glow | MORNING-GLOW-50ML | Morning Glow | +| hair-elixir | HAIR-ELIXIR-30ML | Hair Elixir | +| anti-age-serum | ANTI-AGE-SERUM-30ML | Anti-age Serum | +| luksuzni-set | LUK-SU-ZNI-SET | Luksuzni Set | + +--- + +## 2. Asset Migration (DONE) + +All 204 assets organized in MinIO `saleor` bucket: + +``` +saleor/ +β”œβ”€β”€ brand/ (36 files) - Logos, partner badges +β”œβ”€β”€ marketing/ (133 files) - Before/after, testimonials +β”œβ”€β”€ content/ (26 files) - Blog images +└── products/ (9 files) - Product photos +``` + +**CDN Base URL:** `https://minio-api.nodecrew.me/saleor/` + +--- + +## 3. Customer & Order Migration Strategy + +### Customer Analysis + +| Category | Count | Description | +|----------|-------|-------------| +| **Total WordPress Users** | 4,886 | All registered accounts | +| **With Orders** | 1,172 | Actually purchased something | +| **Without Orders** | 2,714 | Abandoned carts, newsletter signups | +| **Guest Orders** | 144 | No account, email only | +| **TOTAL REAL CUSTOMERS** | **1,274** | Unique emails from orders | + +### Why Migrate All 4,886 Users? + +The 2,714 users without orders are valuable for: +- **Abandoned cart recovery** - They started but didn't finish +- **Newsletter subscribers** - Already interested in brand +- **Reactivation campaigns** - Win back potential customers +- **Lookalike audiences** - For Meta/Google ads + +### Customer Segmentation + +During migration, users are automatically segmented: + +| Segment | Criteria | Count (Est.) | Strategy | +|---------|----------|--------------|----------| +| **VIP_CUSTOMER** | 3+ completed orders | ~200 | Loyalty program, early access | +| **ACTIVE_CUSTOMER** | 1-2 completed orders | ~972 | Cross-sell, subscription | +| **CART_ABANDONER** | Pending/processing orders | ~1,086 | Recovery sequence | +| **PROSPECT** | No orders | ~2,628 | Welcome series, education | + +--- + +## 4. Migration Scripts + +### Available Scripts + +| Script | Purpose | Use When | +|--------|---------|----------| +| `migrate_all_users_and_orders.py` | **Complete migration** (recommended) | You want all users + segmentation | +| `migrate_cod_orders.py` | Orders only (no user creation) | Quick order migration only | +| `migrate_guest_orders.py` | Alternative guest checkout | Legacy option | + +### Recommended: Complete Migration + +```bash +# Set environment variables +export WP_DB_HOST=doorwayftw +export WP_DB_USER=DUjqYuqsYvaGUFV4 +export WP_DB_PASSWORD=voP0UzecALE0WRNJQcTCf0STMcxIiX99 +export SALEOR_DB_HOST=doorwayftw +export SALEOR_DB_USER=saleor +export SALEOR_DB_PASSWORD= + +# Preview (dry run) +python scripts/migrate_all_users_and_orders.py --users --orders --dry-run + +# Migrate specific segment +python scripts/migrate_all_users_and_orders.py --users --segment VIP_CUSTOMER + +# Full migration +python scripts/migrate_all_users_and_orders.py --users --orders +``` + +### Migration by Segments (Phased Approach) + +**Phase 1: VIP & Active Customers** (Lowest risk) +```bash +python scripts/migrate_all_users_and_orders.py \ + --users --segment VIP_CUSTOMER --orders --limit-orders 100 +``` + +**Phase 2: Cart Abandoners** (Medium value) +```bash +python scripts/migrate_all_users_and_orders.py \ + --users --segment CART_ABANDONER --orders +``` + +**Phase 3: Prospects** (Reactivation focus) +```bash +python scripts/migrate_all_users_and_orders.py \ + --users --segment PROSPECT +``` + +--- + +## 5. Post-Migration: Email Reactivation Campaigns + +See `EMAIL_REACTIVATION_CAMPAIGNS.md` for complete strategy. + +### Quick Summary + +| Campaign | Target | Goal | +|----------|--------|------| +| **Cart Recovery** | 1,086 abandoners | 10-15% conversion | +| **Welcome Series** | 2,628 prospects | 5-8% first order | +| **Win-Back** | Inactive customers | 3-5% reactivation | +| **VIP Program** | 200 top customers | Loyalty + referrals | + +### Campaign Templates Included + +- Cart recovery (3 emails) +- Welcome series (4 emails) +- Win-back sequence (2 emails) +- VIP perks announcement + +### Technical Setup + +Segmentation data stored in user metadata: +```json +{ + "segment": "CART_ABANDONER", + "wp_user_id": 12345, + "order_count": 1, + "completed_orders": 0, + "total_spent": 0, + "registration_date": "2022-11-20T13:42:19" +} +``` + +Export for email platform: +```sql +-- Get all PROSPECTS for welcome campaign +SELECT email, first_name, metadata->>'registration_date' +FROM account_user +WHERE metadata->>'segment' = 'PROSPECT'; +``` + +--- + +## 6. COD Payment Handling + +Since Manoon uses Cash on Delivery: + +### Status Mapping + +| WC Status | Saleor Status | Payment | +|-----------|---------------|---------| +| `wc-pending` | `UNCONFIRMED` | Unpaid | +| `wc-processing` | `UNFULFILLED` | Unpaid | +| `wc-completed` | `FULFILLED` | βœ… Paid (COD collected) | +| `wc-cancelled` | `CANCELED` | Unpaid | + +### Payment Records + +For completed orders, a dummy payment record is created: +- Gateway: `mirumee.payments.dummy` +- Status: `FULLY_CHARGED` +- Amount: Order total + +This allows reporting and analytics to work correctly. + +--- + +## 7. Data Transformations + +| Field | WooCommerce | Saleor | +|-------|-------------|--------| +| **Prices** | Decimal (115.00) | Integer cents (11500) | +| **Tax Rate** | Calculated | Fixed 15% (Serbia VAT) | +| **Status** | wc-* strings | Saleor workflow states | +| **Origin** | Various | `BULK_CREATE` | +| **Passwords** | WP hashed | `!` (unusable, reset required) | + +--- + +## 8. Verification Checklist + +After migration: + +- [ ] User count matches: 4,886 +- [ ] Order count matches: 1,786 +- [ ] Segments correctly assigned +- [ ] LTV calculated for each customer +- [ ] Order totals are correct (cents) +- [ ] Completed orders have payment records +- [ ] Addresses formatted correctly +- [ ] SKUs link to correct products + +--- + +## 9. Rollback Plan + +If needed: + +```sql +-- Delete imported data +DELETE FROM order_order WHERE metadata->>'origin' = 'BULK_CREATE'; +DELETE FROM account_user WHERE id IN ( + SELECT saleor_user_id FROM wc_complete_user_mapping +); + +-- Drop mapping tables +DROP TABLE wc_complete_user_mapping; +DROP TABLE wc_order_mapping; +``` + +--- + +## 10. Next Steps + +1. βœ… Run migration preview: `--dry-run` +2. βœ… Verify counts match expectations +3. βœ… Run Phase 1 (VIP customers) +4. βœ… Set up email platform (Mautic/MailerLite/Mailchimp) +5. βœ… Import segments into email platform +6. βœ… Launch cart recovery campaign +7. βœ… Launch welcome series for prospects +8. βœ… Monitor conversion rates +9. βœ… Optimize campaigns based on data + +--- + +## Support + +For issues: +1. Check Saleor logs: `kubectl logs -n saleor deployment/saleor-api` +2. Run with `--dry-run` first +3. Check mapping tables for progress +4. Review `EMAIL_REACTIVATION_CAMPAIGNS.md` for marketing setup diff --git a/SALEOR_MIGRATION_PLAN.md b/SALEOR_MIGRATION_PLAN.md new file mode 100644 index 0000000..cf88853 --- /dev/null +++ b/SALEOR_MIGRATION_PLAN.md @@ -0,0 +1,528 @@ +# Manoon Headless: WordPress/WooCommerce β†’ Saleor Migration Plan + +## Current State Analysis + +### Tech Stack +- **Framework**: Next.js 16.1.6 + React 19.2.3 +- **Styling**: Tailwind CSS v4 +- **State**: Zustand (cart) +- **i18n**: next-intl (Serbian/English) +- **Animation**: Framer Motion +- **Backend**: WooCommerce REST API + +### Current Data Flow +``` +Next.js Storefront β†’ WooCommerce REST API β†’ WordPress Database +``` + +### Target Data Flow +``` +Next.js Storefront β†’ Saleor GraphQL API β†’ PostgreSQL Database +``` + +--- + +## Migration Strategy: Stacked PRs + +Using stacked PRs for dependent changes: + +``` +main (WooCommerce - stable) +β”‚ +β”œβ”€β”€ feature/001-saleor-graphql-client (base) +β”‚ └── Saleor GraphQL client, types, config +β”‚ +β”œβ”€β”€ feature/002-saleor-products (depends on 001) +β”‚ └── Product fetching, listing, detail pages +β”‚ +β”œβ”€β”€ feature/003-saleor-cart (depends on 002) +β”‚ └── Cart functionality with Saleor checkout +β”‚ +β”œβ”€β”€ feature/004-saleor-checkout (depends on 003) +β”‚ └── Checkout flow, payments (COD), order creation +β”‚ +└── feature/005-remove-woocommerce (depends on 004) + └── Remove WooCommerce code, env vars, deps +``` + +--- + +## Phase 1: GraphQL Client Setup (feature/001-saleor-graphql-client) + +### Tasks +- [ ] Install GraphQL dependencies (`@apollo/client`, `graphql`) +- [ ] Create Saleor GraphQL client configuration +- [ ] Set up type generation from Saleor schema +- [ ] Create environment variables for Saleor API +- [ ] Test connection to Saleor API + +### Files to Create +``` +src/lib/saleor/ +β”œβ”€β”€ client.ts # Apollo Client configuration +β”œβ”€β”€ fragments/ +β”‚ β”œβ”€β”€ Product.ts # Product fragment +β”‚ β”œβ”€β”€ Variant.ts # Variant fragment +β”‚ └── Checkout.ts # Checkout fragment +β”œβ”€β”€ mutations/ +β”‚ β”œβ”€β”€ Checkout.ts # Checkout mutations +β”‚ └── Cart.ts # Cart mutations +└── queries/ + β”œβ”€β”€ Products.ts # Product queries + └── Checkout.ts # Checkout queries + +src/types/saleor.ts # Generated TypeScript types +``` + +### Dependencies to Add +```bash +npm install @apollo/client graphql +npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations +``` + +--- + +## Phase 2: Product Migration (feature/002-saleor-products) + +### Tasks +- [ ] Create Saleor product types/interfaces +- [ ] Replace `getProducts()` with Saleor query +- [ ] Replace `getProductBySlug()` with Saleor query +- [ ] Update `ProductCard` component to use Saleor data +- [ ] Update `ProductDetail` component to use Saleor data +- [ ] Handle product variants +- [ ] Handle product translations (SR/EN) + +### GraphQL Queries Needed +```graphql +# Get all products +query GetProducts($channel: String!, $locale: LanguageCodeEnum!) { + products(channel: $channel, first: 100) { + edges { + node { + id + name + slug + description + translation(languageCode: $locale) { + name + slug + description + } + variants { + id + name + sku + pricing { + price { + gross { + amount + currency + } + } + } + } + media { + url + alt + } + } + } + } +} + +# Get product by slug +query GetProduct($slug: String!, $channel: String!, $locale: LanguageCodeEnum!) { + product(slug: $slug, channel: $channel) { + id + name + slug + description + translation(languageCode: $locale) { + name + slug + description + } + variants { + id + name + sku + pricing { + price { + gross { + amount + currency + } + } + } + } + media { + url + alt + } + } +} +``` + +### Files to Modify +``` +src/lib/woocommerce.ts β†’ src/lib/saleor/products.ts +src/components/product/ProductCard.tsx +src/components/product/ProductDetail.tsx +src/app/products/page.tsx +src/app/products/[slug]/page.tsx +``` + +--- + +## Phase 3: Cart Migration (feature/003-saleor-cart) + +### Tasks +- [ ] Replace Zustand cart store with Saleor checkout +- [ ] Create checkout on first cart addition +- [ ] Update cart lines (add, remove, update quantity) +- [ ] Fetch checkout by ID (from localStorage/cookie) +- [ ] Update CartDrawer component + +### Saleor Checkout Flow +``` +1. User adds item β†’ Create checkout (if not exists) +2. Add checkout line β†’ checkoutLinesAdd mutation +3. Update quantity β†’ checkoutLinesUpdate mutation +4. Remove item β†’ checkoutLinesDelete mutation +5. Store checkoutId in localStorage +``` + +### GraphQL Mutations Needed +```graphql +# Create checkout +mutation CheckoutCreate($input: CheckoutCreateInput!) { + checkoutCreate(input: $input) { + checkout { + id + token + lines { + id + quantity + variant { + id + name + product { + name + media { + url + } + } + } + } + } + errors { + field + message + } + } +} + +# Add lines +mutation CheckoutLinesAdd($checkoutId: ID!, $lines: [CheckoutLineInput!]!) { + checkoutLinesAdd(checkoutId: $checkoutId, lines: $lines) { + checkout { + id + lines { + id + quantity + } + } + } +} + +# Update lines +mutation CheckoutLinesUpdate($checkoutId: ID!, $lines: [CheckoutLineUpdateInput!]!) { + checkoutLinesUpdate(checkoutId: $checkoutId, lines: $lines) { + checkout { + id + lines { + id + quantity + } + } + } +} +``` + +### Files to Modify +``` +src/stores/cartStore.ts β†’ src/stores/saleorCheckoutStore.ts +src/components/cart/CartDrawer.tsx +``` + +--- + +## Phase 4: Checkout Flow (feature/004-saleor-checkout) + +### Tasks +- [ ] Create checkout page +- [ ] Implement shipping address form +- [ ] Implement billing address form +- [ ] Set shipping method (COD) +- [ ] Create order on completion +- [ ] Show order confirmation + +### Cash on Delivery (COD) Flow +``` +1. User completes checkout form +2. Set shipping/billing addresses +3. Select shipping method (fixed price) +4. Complete checkout β†’ creates order +5. Order status: UNFULFILLED +6. Payment status: NOT_CHARGED (COD) +``` + +### GraphQL Mutations +```graphql +# Set shipping address +mutation CheckoutShippingAddressUpdate($checkoutId: ID!, $shippingAddress: AddressInput!) { + checkoutShippingAddressUpdate(checkoutId: $checkoutId, shippingAddress: $shippingAddress) { + checkout { + id + shippingAddress { + firstName + lastName + streetAddress1 + city + postalCode + phone + } + } + } +} + +# Set billing address +mutation CheckoutBillingAddressUpdate($checkoutId: ID!, $billingAddress: AddressInput!) { + checkoutBillingAddressUpdate(checkoutId: $checkoutId, billingAddress: $billingAddress) { + checkout { + id + billingAddress { + firstName + lastName + streetAddress1 + city + postalCode + phone + } + } + } +} + +# Complete checkout (creates order) +mutation CheckoutComplete($checkoutId: ID!) { + checkoutComplete(checkoutId: $checkoutId) { + order { + id + number + status + total { + gross { + amount + currency + } + } + } + errors { + field + message + } + } +} +``` + +### Files to Create +``` +src/app/checkout/ +β”œβ”€β”€ page.tsx # Checkout page +β”œβ”€β”€ CheckoutForm.tsx # Address forms +β”œβ”€β”€ OrderSummary.tsx # Cart summary +└── CheckoutSuccess.tsx # Order confirmation +``` + +--- + +## Phase 5: Cleanup (feature/005-remove-woocommerce) + +### Tasks +- [ ] Remove WooCommerce dependencies +- [ ] Remove WooCommerce API file +- [ ] Clean up environment variables +- [ ] Update documentation +- [ ] Test complete flow + +### Files to Remove +``` +src/lib/woocommerce.ts +``` + +### Dependencies to Remove +```bash +npm uninstall @woocommerce/woocommerce-rest-api +``` + +### Environment Variables to Update +```bash +# Remove +NEXT_PUBLIC_WOOCOMMERCE_URL +NEXT_PUBLIC_WOOCOMMERCE_CONSUMER_KEY +NEXT_PUBLIC_WOOCOMMERCE_CONSUMER_SECRET + +# Add +NEXT_PUBLIC_SALEOR_API_URL +NEXT_PUBLIC_SALEOR_CHANNEL +``` + +--- + +## URL Structure + +### Current (WooCommerce) +``` +/products/ # Product listing +/products/:slug/ # Product detail (Serbian) +/en/products/:slug/ # Product detail (English) +``` + +### Target (Saleor) +``` +/products/ # Product listing +/products/:slug/ # Product detail (Serbian or English slug) +``` + +Saleor stores both Serbian and English slugs. The storefront will fetch by slug and detect language. + +--- + +## Component Mapping + +| Current Component | Saleor Equivalent | Changes | +|-------------------|-------------------|---------| +| `WooProduct` interface | `Product` fragment | Different field names | +| `getProducts()` | `GetProducts` query | GraphQL instead of REST | +| `getProductBySlug()` | `GetProduct` query | GraphQL instead of REST | +| `useCartStore` (Zustand) | `useCheckoutStore` | Saleor checkout-based | +| `formatPrice()` | `formatPrice()` | Handle Money type | + +--- + +## Data Mapping + +### Product +| WooCommerce | Saleor | Notes | +|-------------|--------|-------| +| `id` | `id` | Woo uses int, Saleor uses UUID | +| `name` | `name` | Same | +| `slug` | `slug` | Same | +| `price` | `variants[0].pricing.price.gross.amount` | Nested in variant | +| `regular_price` | `variants[0].pricing.price.gross.amount` | Saleor has discounts | +| `images[0].src` | `media[0].url` | Different structure | +| `stock_status` | `variants[0].quantityAvailable` | Check > 0 | +| `description` | `description` | JSON editor format | +| `sku` | `variants[0].sku` | In variant | + +### Cart/Checkout +| WooCommerce | Saleor | Notes | +|-------------|--------|-------| +| Cart items in localStorage | Checkout ID in localStorage | Saleor stores server-side | +| `add_to_cart` | `checkoutLinesAdd` | Mutation | +| `update_quantity` | `checkoutLinesUpdate` | Mutation | +| `remove_from_cart` | `checkoutLinesDelete` | Mutation | +| Cart total (calculated) | `checkout.totalPrice` | Server-calculated | + +--- + +## Testing Checklist + +### Phase 1: GraphQL Client +- [ ] Apollo Client connects to Saleor API +- [ ] Type generation works +- [ ] Environment variables configured + +### Phase 2: Products +- [ ] Product listing page shows products +- [ ] Product detail page works with Serbian slug +- [ ] Product detail page works with English slug +- [ ] Language switcher works +- [ ] Product images load +- [ ] Prices display correctly (RSD) + +### Phase 3: Cart +- [ ] Add to cart works +- [ ] Update quantity works +- [ ] Remove from cart works +- [ ] Cart persists across page reloads +- [ ] CartDrawer shows correct items + +### Phase 4: Checkout +- [ ] Checkout page loads +- [ ] Shipping address form works +- [ ] Billing address form works +- [ ] Order creation works +- [ ] Order confirmation shows +- [ ] COD payment method available + +### Phase 5: Cleanup +- [ ] No WooCommerce dependencies +- [ ] All tests pass +- [ ] Build succeeds +- [ ] No console errors + +--- + +## Rollback Plan + +If issues arise, revert to WooCommerce: +```bash +git checkout master +npm install # Restore WooCommerce deps +``` + +--- + +## Post-Migration Tasks + +- [ ] Update deployment docs +- [ ] Train team on Saleor dashboard +- [ ] Set up monitoring +- [ ] Configure CDN for images +- [ ] Test on staging +- [ ] Deploy to production +- [ ] Monitor for errors +- [ ] Collect user feedback + +--- + +## Resources + +- **Saleor API URL**: `https://api.manoonoils.com/graphql/` +- **Saleor Dashboard**: `https://dashboard.manoonoils.com/` +- **Current Storefront**: `https://dev.manoonoils.com/` +- **MinIO Assets**: `https://minio-api.nodecrew.me/saleor/` + +--- + +## Migration Commands + +```bash +# Start migration +git checkout -b feature/001-saleor-graphql-client + +# After each phase +git add . +git commit -m "feat(saleor): Phase X - Description" +git push -u origin feature/001-saleor-graphql-client + +# Create PR on GitHub +gh pr create --title "[1/5] Saleor GraphQL Client Setup" --base main + +# Merge and continue +git checkout main +git pull origin main +git checkout -b feature/002-saleor-products +``` diff --git a/ecommerce-features-checklist.md b/ecommerce-features-checklist.md new file mode 100644 index 0000000..7c22173 --- /dev/null +++ b/ecommerce-features-checklist.md @@ -0,0 +1,460 @@ +# Advanced E-Commerce Features Checklist + +## Saleor Built-in vs Missing Features + +### βœ… Built-in (Ready to Use) + +| Feature | Saleor Support | Notes | +|---------|---------------|-------| +| **Products & Variants** | βœ… Native | Simple & variable products | +| **Categories** | βœ… Native | Hierarchical with nesting | +| **Collections** | βœ… Native | Manual & automated collections | +| **Inventory** | βœ… Native | Multi-warehouse support | +| **Multi-language** | βœ… Native | Full translation support | +| **Multi-currency** | βœ… Native | Per-channel pricing | +| **Promotions** | βœ… Native | % off, fixed amount, vouchers | +| **Gift Cards** | βœ… Native | Digital gift cards | +| **Taxes** | βœ… Native | Per-country tax rates | +| **Shipping** | βœ… Native | Zones & methods | +| **Customer Accounts** | βœ… Native | Full account management | +| **Order Management** | βœ… Native | Status tracking, fulfillments | +| **Staff Permissions** | βœ… Native | Role-based access | +| **Pages & Menus** | βœ… Native | CMS features | +| **Checkout** | βœ… Native | Customizable flow | +| **Payments** | βœ… Native | Stripe, Adyen, etc. | + +--- + +## ❌ Missing Features (Need to Build/Add) + +### 1. Product Reviews ⭐ HIGH PRIORITY + +**Status:** NOT in Saleor (on roadmap but not planned) + +**Solutions:** +| Solution | Cost | Effort | Best For | +|----------|------|--------|----------| +| **Judge.me** | Free-$15/mo | 2 hours | Budget option, works well | +| **Trustpilot** | $200+/mo | 2 hours | SEO, brand trust | +| **Yotpo** | $300+/mo | 4 hours | Enterprise, UGC | +| **Build Custom** | Free | 2-4 weeks | Full control | + +**Custom Build SQL:** +```sql +CREATE TABLE product_review ( + id SERIAL PRIMARY KEY, + product_id INTEGER REFERENCES product_product(id), + user_id INTEGER REFERENCES account_user(id), + rating INTEGER CHECK (rating >= 1 AND rating <= 5), + title VARCHAR(255), + comment TEXT, + is_verified_purchase BOOLEAN DEFAULT false, + is_approved BOOLEAN DEFAULT false, + helpful_count INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW() +); +``` + +--- + +### 2. Upsells & Cross-sells ⭐ HIGH PRIORITY + +**Status:** NOT in Saleor (confirmed missing) + +**What You Need:** + +```sql +-- Related products / upsells table +CREATE TABLE product_related ( + id SERIAL PRIMARY KEY, + product_id INTEGER REFERENCES product_product(id), + related_product_id INTEGER REFERENCES product_product(id), + type VARCHAR(50), -- 'upsell', 'cross_sell', 'related' + sort_order INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW(), + UNIQUE(product_id, related_product_id, type) +); +``` + +**Types of Upsells:** + +| Type | Example | Location | +|------|---------|----------| +| **Upsell** | 500ml β†’ 1L (upgrade) | Product page | +| **Cross-sell** | Olive oil + vinegar (complementary) | Cart page | +| **Related** | Same category products | Product page | +| **Bundle** | Oil + vinegar + herbs package | Product page | +| **Frequently Bought Together** | AI-based recommendations | Cart page | + +**Implementation Options:** + +#### Option A: Manual (Product-Level) +```sql +-- Admin manually assigns related products +INSERT INTO product_related (product_id, related_product_id, type, sort_order) +VALUES +(1, 5, 'upsell', 1), -- Product 1 shows Product 5 as upsell +(1, 6, 'cross_sell', 1), -- Product 1 shows Product 6 as cross-sell +(1, 7, 'related', 1); -- Product 1 shows Product 7 as related +``` + +**Admin UI Needed:** +- Product edit page with "Related Products" section +- Search & select products +- Drag to reorder +- Choose type (upsell/cross-sell/related) + +#### Option B: Automated (Category-Based) +```typescript +// Automatically show products from same category +const getRelatedProducts = async (productId: string, categoryId: string) => { + return await saleorClient.query({ + query: gql` + query GetRelatedProducts($categoryId: ID!, $excludeId: ID!) { + products( + first: 4, + filter: {categories: [$categoryId]}, + channel: "default-channel" + ) { + edges { + node { + id + name + slug + thumbnail { url } + variants { + channelListings { + price { amount currency } + } + } + } + } + } + } + `, + variables: { categoryId, excludeId: productId } + }); +}; +``` + +#### Option C: AI/ML Recommendations (Advanced) +Services: +- **Recombee** - $99/mo+ +- **Amazon Personalize** - Pay per use +- **Algolia Recommend** - $29/mo+ +- **Build custom** - Requires order history analysis + +**Effort:** High (4-8 weeks) + +--- + +### 3. Product Bundles ⭐ MEDIUM PRIORITY + +**Status:** NOT in Saleor (requested, on roadmap) + +**Example:** +- Olive Oil 500ml + Vinegar 250ml = Bundle price $15 (save $3) + +**Custom Implementation:** + +```sql +-- Bundle definition +CREATE TABLE product_bundle ( + id SERIAL PRIMARY KEY, + name VARCHAR(250), + slug VARCHAR(255) UNIQUE, + description JSONB, + product_type_id INTEGER REFERENCES product_producttype(id), + bundle_price_amount NUMERIC(20,3), + currency VARCHAR(3), + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT NOW() +); + +-- Bundle items +CREATE TABLE product_bundle_item ( + id SERIAL PRIMARY KEY, + bundle_id INTEGER REFERENCES product_bundle(id), + product_variant_id INTEGER REFERENCES product_productvariant(id), + quantity INTEGER DEFAULT 1, + sort_order INTEGER DEFAULT 0 +); +``` + +**Storefront Display:** +```typescript +// Show bundle on product page + +``` + +--- + +### 4. Abandoned Cart Recovery ⭐ HIGH PRIORITY + +**Status:** NOT in Saleor + +**Solutions:** +1. **Mautic** (FREE - you have it!) - See `mautic-abandoned-cart.md` +2. **Klaviyo** - $20-50/mo +3. **N8N automation** - FREE + +--- + +### 5. Email Marketing ⭐ MEDIUM PRIORITY + +**Status:** NOT in Saleor + +**Solutions:** +1. **Mautic** (FREE - you have it!) +2. **Klaviyo** - Best for e-commerce +3. **Mailchimp** - Good free tier + +**Email Types Needed:** +- Welcome email +- Order confirmation +- Shipping notification +- Post-purchase follow-up +- Win-back campaign +- Birthday discount + +--- + +### 6. Loyalty/Rewards Program ⭐ MEDIUM PRIORITY + +**Status:** NOT in Saleor + +**Custom Build:** +```sql +-- Loyalty points +CREATE TABLE loyalty_account ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES account_user(id), + points_balance INTEGER DEFAULT 0, + lifetime_points INTEGER DEFAULT 0, + tier VARCHAR(50) DEFAULT 'bronze', -- bronze, silver, gold + created_at TIMESTAMP DEFAULT NOW() +); + +-- Points transactions +CREATE TABLE loyalty_transaction ( + id SERIAL PRIMARY KEY, + account_id INTEGER REFERENCES loyalty_account(id), + points INTEGER, -- positive for earn, negative for redeem + type VARCHAR(50), -- 'purchase', 'referral', 'redemption', 'bonus' + description TEXT, + order_id INTEGER REFERENCES order_order(id), + created_at TIMESTAMP DEFAULT NOW() +); +``` + +--- + +### 7. Subscription/Recurring Products ⭐ LOW PRIORITY + +**Status:** NOT in Saleor + +**Solutions:** +- **Stripe Billing** - Best integration +- **Recharge** - $300/mo+ (Shopify-focused) +- **Chargebee** - $249/mo+ +- **Build custom** with Stripe + +--- + +### 8. Wishlist/Favorites ⭐ MEDIUM PRIORITY + +**Status:** NOT in Saleor + +**Simple Implementation:** +```sql +CREATE TABLE wishlist_item ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES account_user(id), + product_variant_id INTEGER REFERENCES product_productvariant(id), + added_at TIMESTAMP DEFAULT NOW(), + UNIQUE(user_id, product_variant_id) +); +``` + +--- + +### 9. Recently Viewed Products ⭐ LOW PRIORITY + +**Implementation:** +```typescript +// Store in localStorage or Redis +const trackProductView = (productId: string) => { + const recentlyViewed = JSON.parse(localStorage.getItem('recentlyViewed') || '[]'); + recentlyViewed.unshift(productId); + localStorage.setItem('recentlyViewed', JSON.stringify(recentlyViewed.slice(0, 10))); +}; +``` + +--- + +### 10. Product Comparison ⭐ LOW PRIORITY + +**Implementation:** +```typescript +// Allow users to compare 2-3 products side-by-side +const ProductComparison = ({ products }) => { + return ( + + + + + {products.map(p => )} + + + + + + {products.map(p => )} + + + + {products.map(p => )} + + +
Feature{p.name}
Price${p.price}
Volume{p.volume}
+ ); +}; +``` + +--- + +### 11. Quick View (Modal) ⭐ LOW PRIORITY + +**Implementation:** +```typescript +// Product card with quick view button + + +

{product.name}

+ +
+ +// Modal fetches product details + +``` + +--- + +### 12. AJAX Add to Cart (No Page Reload) ⭐ MEDIUM PRIORITY + +**Implementation:** +```typescript +const AddToCartButton = ({ variantId }) => { + const [adding, setAdding] = useState(false); + + const handleAdd = async () => { + setAdding(true); + await saleorClient.mutate({ + mutation: ADD_TO_CART, + variables: { variantId, quantity: 1 } + }); + setAdding(false); + showToast('Added to cart!'); + updateCartCount(); // Update header cart icon + }; + + return ; +}; +``` + +--- + +### 13. Dynamic Pricing / Volume Discounts ⭐ LOW PRIORITY + +**Example:** +- Buy 1: $12 +- Buy 2: $11 each (save $2) +- Buy 3+: $10 each (save $6) + +**Saleor Native:** Not supported + +**Custom:** +```typescript +const getVolumePrice = (basePrice: number, quantity: number) => { + if (quantity >= 3) return basePrice * 0.83; // 17% off + if (quantity >= 2) return basePrice * 0.92; // 8% off + return basePrice; +}; +``` + +--- + +### 14. Back in Stock Notifications ⭐ MEDIUM PRIORITY + +**Implementation:** +```sql +CREATE TABLE stock_notification_request ( + id SERIAL PRIMARY KEY, + email VARCHAR(255), + product_variant_id INTEGER REFERENCES product_productvariant(id), + is_notified BOOLEAN DEFAULT false, + created_at TIMESTAMP DEFAULT NOW() +); + +-- When stock is updated, check and send emails +``` + +--- + +## Recommended Priority Order + +### Phase 1: Essential (Launch) +- [x] Saleor core products +- [ ] **Reviews** (Judge.me or custom) +- [ ] **Upsells/Cross-sells** (manual assignment) +- [ ] **AJAX cart** +- [ ] **Mautic abandoned cart** + +### Phase 2: Growth (1-3 months post-launch) +- [ ] **Email marketing** (Mautic or Klaviyo) +- [ ] **Wishlist** +- [ ] **Bundles** +- [ ] **Recently viewed** + +### Phase 3: Advanced (6+ months) +- [ ] **Loyalty program** +- [ ] **AI recommendations** +- [ ] **Subscriptions** +- [ ] **Product comparison** + +--- + +## Cost Summary + +| Feature | DIY Build | Third-Party | Recommended | +|---------|-----------|-------------|-------------| +| Reviews | 2-4 weeks | Judge.me FREE | **Judge.me** | +| Upsells | 1-2 weeks | N/A | **Custom** | +| Bundles | 2-3 weeks | N/A | **Custom** | +| Abandoned Cart | 2-3 days | Klaviyo $20/mo | **Mautic FREE** | +| Email Marketing | 1 week | Klaviyo $20/mo | **Mautic FREE** | +| Loyalty | 2-3 weeks | Smile.io $199/mo | **Custom later** | +| Subscriptions | 4-6 weeks | Recharge $300/mo | **Stripe later** | + +--- + +## Total Estimated Dev Time + +**Phase 1:** 4-6 weeks +**Phase 2:** 3-4 weeks +**Phase 3:** 6-8 weeks + +**Total:** 3-4 months for full-featured store diff --git a/infrastructure-overview.md b/infrastructure-overview.md new file mode 100644 index 0000000..9e9565d --- /dev/null +++ b/infrastructure-overview.md @@ -0,0 +1,302 @@ +# Infrastructure Overview: WordPress β†’ Saleor Migration + +## System Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ K3s Cluster β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ manoonoils namespace β”‚ β”‚ saleor namespace β”‚ β”‚ +β”‚ β”‚ (WordPress/WooCommerce)β”‚ β”‚ (Headless Commerce) β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ WordPress β”‚ β”‚ β”‚ β”‚ Saleor API (Django) β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ - WooCommerce β”‚ β”‚ β”‚ β”‚ - GraphQL endpoint β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ - ADVMO Plugin β”‚ β”‚ β”‚ β”‚ - Product management β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ Redis β”‚ β”‚ β”‚ β”‚ Redis β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ (Object Cache) β”‚ β”‚ β”‚ β”‚ (Celery + Cache) β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ MariaDB β”‚ β”‚ β”‚ β”‚ PostgreSQL β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ (WP database) β”‚ β”‚ β”‚ β”‚ (Products, Orders, Users) β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Shared Services β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ MinIO Object Storage β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ manoon-media β”‚ β”‚ saleor β”‚ β”‚ other β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ (WP images) β”‚ β”‚ (Saleor β”‚ β”‚ buckets... β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ images) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ Traefik Ingress β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ manoonoils.com β†’ WordPress β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ dev.manoonoils.com β†’ Next.js Storefront β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ api.manoonoils.com β†’ Saleor API β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ dashboard.manoonoils.com β†’ Saleor Dashboard β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ minio-api.nodecrew.me β†’ MinIO API β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Redis Usage + +### WordPress Redis (manoonoils namespace) + +```yaml +# WordPress uses Redis for: +# - Object caching (reduces DB queries) +# - Session storage +# - Transients cache + +Service: redis.manoonoils.svc.cluster.local:6379 +Purpose: WP Object Cache only +Data: Temporary cache (can be cleared) +Persistence: Not critical +``` + +**wp-config.php:** +```php +define( 'WP_REDIS_HOST', 'redis' ); +define( 'WP_REDIS_PORT', 6379 ); +``` + +### Saleor Redis (saleor namespace) + +```yaml +# Saleor uses Redis for: +# - Celery task queue (background jobs) +# - Django cache framework +# - WebSocket channel layer (if using subscriptions) + +Service: saleor-redis.saleor.svc.cluster.local:6379 +Purpose: Task queue + Cache +Data: Task queue messages, cached data +Persistence: Not critical (tasks re-created if lost) +``` + +**Saleor environment:** +```bash +CELERY_BROKER_URL=redis://saleor-redis.saleor:6379/0 +REDIS_URL=redis://saleor-redis.saleor:6379/0 +``` + +### Key Point: Separate Redis Instances + +| Component | Redis Instance | Purpose | +|-----------|----------------|---------| +| **WordPress** | `redis.manoonoils` | Object cache only | +| **Saleor** | `saleor-redis.saleor` | Celery + cache | +| **No sharing** | - | Each has its own | + +## Media Storage Architecture + +### MinIO Configuration + +**Single MinIO instance serves both systems:** + +``` +MinIO Server (minio.manoonoils:9000) +β”œβ”€β”€ manoon-media/ ← WordPress uploads (existing) +β”‚ β”œβ”€β”€ wp-content/ +β”‚ β”‚ └── uploads/ +β”‚ β”‚ β”œβ”€β”€ 2024/ +β”‚ β”‚ β”‚ └── 01/ +β”‚ β”‚ β”‚ └── product-image.jpg +β”‚ β”‚ └── 2023/ +β”‚ └── assets/ +β”‚ └── logo.png +β”‚ +└── saleor/ ← Saleor media (new) + β”œβ”€β”€ products/ ← Product images + β”œβ”€β”€ assets/ ← Logos, favicons + └── exports/ ← Data exports +``` + +### WordPress Media Flow + +``` +1. User uploads image in WordPress + ↓ +2. ADVMO plugin intercepts upload + ↓ +3. Image saved to MinIO: manoon-media/wp-content/uploads/2024/01/image.jpg + ↓ +4. WordPress stores URL: https://minio-api.nodecrew.me/manoon-media/wp-content/uploads/2024/01/image.jpg + ↓ +5. Image served from MinIO +``` + +### Saleor Media Flow + +``` +1. User uploads image in Saleor Dashboard + ↓ +2. Saleor API saves to MinIO: saleor/products/image.jpg + ↓ +3. Saleor generates thumbnails + ↓ +4. Image served via API: https://api.manoonoils.com/media/products/image.jpg + ↓ +5. Or direct from MinIO: https://minio-api.nodecrew.me/saleor/products/image.jpg +``` + +## Image Migration Process + +### Option 1: Copy Images (Recommended) + +```bash +# 1. Access MinIO +kubectl exec -it deployment/minio -n manoonoils -- /bin/sh + +# 2. Set up alias +mc alias set local http://localhost:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD + +# 3. Create saleor bucket if not exists +mc mb local/saleor + +# 4. Copy all WordPress images to Saleor bucket +mc cp --recursive local/manoon-media/wp-content/uploads/ local/saleor/products/ + +# 5. Copy assets +mc cp local/manoon-media/assets/logo.png local/saleor/assets/ +``` + +**Result:** +- Original images stay in `manoon-media` (WordPress keeps working) +- Copies in `saleor` (for Saleor use) +- No downtime during migration + +### Option 2: Move Images (After Full Migration) + +```bash +# Only after WordPress is fully retired: + +# 1. Move instead of copy +mc mv local/manoon-media/wp-content/uploads/ local/saleor/products/ + +# 2. Update any remaining references +# 3. Delete manoon-media bucket when confirmed safe +``` + +## Logo & Asset Strategy + +### During Migration Period + +**Keep logos in both places:** + +``` +MinIO: +β”œβ”€β”€ manoon-media/assets/logo.png ← Used by WordPress +└── saleor/assets/logo.png ← Used by Saleor +``` + +**Next.js storefront:** +```typescript +// Use absolute URL to MinIO +const LOGO_URL = 'https://minio-api.nodecrew.me/saleor/assets/logo.png'; + +// Or use Next.js public folder +import logo from '@/public/logo.png'; +``` + +### Post-Migration + +**Option A: Keep in MinIO** +- Serve from `saleor/assets/` +- Update via MinIO console or API +- CDN-friendly + +**Option B: Move to Next.js** +``` +storefront/public/ +β”œβ”€β”€ logo.png +β”œβ”€β”€ favicon.ico +└── assets/ + └── hero-banner.jpg +``` + +**Access:** `https://dev.manoonoils.com/logo.png` + +## Data Flow During Migration + +### Phase 1: Parallel Running + +``` +Customer visits dev.manoonoils.com (Saleor storefront) + ↓ +Products fetched from Saleor API + ↓ +Product images loaded from: + - NEW products: saleor bucket + - OLD products: manoon-media bucket (mapped URL) + ↓ +Checkout via Saleor checkout API +``` + +### Phase 2: Full Cutover + +``` +Customer visits dev.manoonoils.com + ↓ +All products in Saleor + ↓ +All images in saleor bucket + ↓ +WordPress fully retired +``` + +## Backup Strategy + +### What to Back Up + +| Component | Backup Method | Frequency | Location | +|-----------|--------------|-----------|----------| +| **WordPress DB** | Kopia | Daily | StorageBox | +| **WordPress files** | Kopia | Daily | StorageBox | +| **MinIO buckets** | Kopia | Daily | StorageBox | +| **Saleor DB** | Kopia | Daily | StorageBox | +| **Saleor PVC** | Kopia | Daily | StorageBox | + +### MinIO Backup Commands + +```bash +# Backup specific bucket +kopia snapshot create /mnt/storagebox/kopia-backups + +# Or use MinIO client for bucket backup +mc mirror local/saleor /backup/saleor-$(date +%Y%m%d) +``` + +## Summary + +| Component | WordPress | Saleor | Relationship | +|-----------|-----------|--------|--------------| +| **Redis** | Separate instance | Separate instance | No sharing | +| **Database** | MariaDB | PostgreSQL | Separate | +| **Media** | manoon-media bucket | saleor bucket | Same MinIO | +| **Cache** | WP Object Cache | Django Cache | Separate | +| **Task Queue** | None (WP-Cron) | Celery + Redis | Saleor only | + +**Key Takeaways:** +1. βœ… Saleor has its own Redis (no conflict with WordPress) +2. βœ… Both use same MinIO (easy image copying) +3. βœ… Copy images from `manoon-media` to `saleor` bucket +4. βœ… Keep logos in both places during transition +5. βœ… WordPress can stay running while Saleor is tested diff --git a/mautic-abandoned-cart.md b/mautic-abandoned-cart.md new file mode 100644 index 0000000..5daade6 --- /dev/null +++ b/mautic-abandoned-cart.md @@ -0,0 +1,466 @@ +# Mautic Abandoned Cart Recovery Setup + +## Overview +Use your existing Mautic instance for abandoned cart recovery instead of paying for Klaviyo. + +**Mautic URL:** https://mautic.nodecrew.me + +## How It Works + +``` +1. User adds item to cart + ↓ +2. Storefront sends event to Mautic (via API or tracking pixel) + ↓ +3. Mautic creates/updates contact with cart data + ↓ +4. Campaign waits 1 hour + ↓ +5. If no purchase β†’ Send abandoned cart email + ↓ +6. User clicks email β†’ Cart restored β†’ Convert! +``` + +## Step 1: Set Up Mautic Tracking + +### Option A: Mautic Tracking Pixel (JavaScript) + +Add to your Next.js storefront: + +```typescript +// lib/mautic.ts +export function trackAddToCart(product: any, quantity: number) { + if (typeof window !== 'undefined' && (window as any).mt) { + (window as any).mt('send', 'pageview', { + page_title: `Added to Cart: ${product.name}`, + page_url: window.location.href, + product_name: product.name, + product_sku: product.variants[0]?.sku, + product_price: product.variants[0]?.channelListings[0]?.price?.amount, + quantity: quantity, + event: 'add_to_cart' + }); + } +} + +export function trackCheckoutStarted(checkout: any) { + if (typeof window !== 'undefined' && (window as any).mt) { + (window as any).mt('send', 'pageview', { + page_title: 'Checkout Started', + page_url: window.location.href, + checkout_value: checkout.totalPrice?.amount, + checkout_id: checkout.id, + event: 'checkout_started' + }); + } +} + +export function trackOrderCompleted(order: any) { + if (typeof window !== 'undefined' && (window as any).mt) { + (window as any).mt('send', 'pageview', { + page_title: 'Order Completed', + page_url: window.location.href, + order_total: order.total.gross.amount, + order_id: order.id, + event: 'purchase_completed' + }); + } +} +``` + +```typescript +// pages/_app.tsx or layout.tsx +import Script from 'next/script'; + +export default function RootLayout({ children }) { + return ( + + + {/* Mautic Tracking */} +