feat(saleor): Phase 1 - GraphQL Client Setup

- 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
This commit is contained in:
Unchained
2026-03-21 12:36:21 +02:00
parent db1914d69b
commit 7b94537670
27 changed files with 7879 additions and 3 deletions

528
SALEOR_MIGRATION_PLAN.md Normal file
View File

@@ -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
```