- 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
529 lines
12 KiB
Markdown
529 lines
12 KiB
Markdown
# 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
|
|
```
|