Commit Graph

71 Commits

Author SHA1 Message Date
Unchained
de4eb0852c feat: Add order auto-confirmation (best effort)
Added order confirmation after checkout completion.
Note: This requires MANAGE_ORDERS permission which currently
has the same bug as HANDLE_PAYMENTS. The try-catch ensures
checkout won't fail if confirmation fails. Orders will be
UNCONFIRMED until manually confirmed in dashboard.
2026-03-29 19:33:04 +02:00
Unchained
9c3d8b0d11 fix: Remove COD transaction creation to fix checkout errors
The transaction creation was failing due to HANDLE_PAYMENTS permission issues.
Removed the code to get checkout working again. Will implement via
order metadata or core-extensions webhook instead.
2026-03-29 19:17:06 +02:00
Unchained
e15e6470d2 fix: Add SALEOR_API_TOKEN auth for COD transaction creation
- Add SALEOR_API_TOKEN environment variable support
- Update Apollo client to include auth header
- Enable COD transaction creation after checkout
2026-03-29 18:22:16 +02:00
Unchained
5f9b7bac3a fix: set checkout languageCode to ensure emails are sent in correct language
- Add CHECKOUT_LANGUAGE_CODE_UPDATE mutation to update checkout language
- Call language code update before completing checkout
- Language code (SR, EN, DE, FR) is now set on checkout before order creation
- This ensures order confirmation emails are sent in the customer's language
- Update step numbering in checkout flow (now 6 steps total)
2026-03-29 14:42:52 +02:00
Unchained
fbe0761609 fix: remove manual transaction creation causing 400 error
- Remove CREATE_TRANSACTION_MUTATION call that's causing 400 error
- Saleor's checkoutComplete already creates order with NOT_CHARGED status for COD
- Order doesn't need manual transaction - staff handles fulfillment in dashboard
- Keep payment method selection and UI components intact
2026-03-29 14:31:42 +02:00
Unchained
10b18c6010 feat: add 30-day money back guarantee trust badge above complete order button
- Add green trust badge with checkmark icon above 'Complete Order' button
- Add translations for all locales (EN, SR, DE, FR)
- Badge includes: '30-Day Money-Back Guarantee' text
- Styled with green background and border to match trust/conversion theme
2026-03-29 14:27:05 +02:00
Unchained
ff481f18c3 feat: implement Cash on Delivery (COD) payment method
This commit adds comprehensive COD support using Saleor's native Transaction system:

**Architecture:**
- Uses Saleor's native Transaction objects (not metadata)
- Modular payment method configuration
- Extensible design for future payment types (cards, bank transfer, etc.)

**New Components:**
- PaymentMethodSelector: Reusable payment method selection UI
- PaymentMethodCard: Individual payment method card
- CODInstructions: COD-specific instructions and guidance
- PaymentSection: Checkout integration wrapper

**Core Features:**
- COD selected by default for Serbia (default-channel)
- Transaction created automatically on order completion
- Transaction visible in Saleor Dashboard
- Multi-language support (EN, SR, DE, FR)
- No additional fees
- Instructions shown to customer (prepare cash, inspect order, no fee)

**Files Added:**
- docs/COD-IMPLEMENTATION-PLAN.md
- src/lib/config/paymentMethods.ts
- src/lib/saleor/payments/types.ts
- src/lib/saleor/payments/cod.ts
- src/components/payment/PaymentMethodSelector.tsx
- src/components/payment/PaymentMethodCard.tsx
- src/components/payment/CODInstructions.tsx
- src/components/payment/index.ts
- src/app/[locale]/checkout/components/PaymentSection.tsx

**Files Modified:**
- src/app/[locale]/checkout/page.tsx (added payment section, transaction creation)
- src/i18n/messages/{en,sr,de,fr}.json (payment translations)

**Technical Details:**
- Transaction status: NOT_CHARGED
- Available actions: [CHARGE]
- PSP Reference format: COD-{orderNumber}-{timestamp}
- Staff collects cash and fulfills order via Dashboard

Closes: Cash on Delivery payment implementation
2026-03-29 06:02:51 +02:00
Unchained
322c4c805b fix: add back email update step in checkout 2026-03-28 18:09:57 +02:00
Unchained
bcf74e1fd1 feat: implement one-page checkout with dynamic shipping 2026-03-28 18:03:12 +02:00
Unchained
a419337d99 feat: store userLanguage in checkout metadata for multi-language emails
Adds userLanguage and userLocale to checkout metadata during order completion.
This allows N8N workflows to detect the customer's selected language and
send order confirmation emails in the correct language (sr, en, de, fr).
2026-03-28 07:27:09 +02:00
Unchained
09294fd752 refactor: Remove email functionality - migrated to core-extensions app
Removed:
- Webhook handlers (src/app/api/webhooks/saleor/)
- Email templates (src/emails/)
- OrderNotificationService (src/lib/services/)

Emails now handled by saleor-core-extensions service
Manifest: https://core-extensions.manoonoils.com/api/manifest
2026-03-26 08:50:58 +02:00
Unchained
2e6668ff0d fix(webhook): prevent duplicate revenue tracking
Move analytics tracking inside ORDER_CONFIRMED conditional block
so revenue is only tracked once when order is confirmed, not twice
(once for ORDER_CREATED and once for ORDER_CONFIRMED).
2026-03-25 20:35:39 +02:00
Unchained
ab7dfbe48b refactor(webhook): modularize email and analytics code
Create service-oriented architecture for better maintainability:

- AnalyticsService: Centralized analytics tracking with OpenPanel
  - trackOrderReceived(), trackRevenue(), track()
  - Error handling that doesn't break main flow
  - Singleton pattern for single instance

- OrderNotificationService: Encapsulates all order email logic
  - sendOrderConfirmation() - customer + admin
  - sendOrderShipped() - with tracking info
  - sendOrderCancelled() - with reason
  - sendOrderPaid() - payment confirmation
  - Translation logic moved from webhook to service
  - Email formatting utilities encapsulated

- Webhook route refactored:
  - Reduced from 605 lines to ~250 lines
  - No business logic - only HTTP handling
  - Delegates to services for emails and analytics
  - Cleaner separation of concerns

- New utils file: formatPrice() shared between services

This prevents future bugs by:
1. Centralizing email logic in one place
2. Making code testable (services can be unit tested)
3. Easier to add new webhook handlers
4. Translation logic not mixed with HTTP code
5. Analytics failures don't break order processing
2026-03-25 20:22:55 +02:00
Unchained
646d057970 Merge branch 'feature/saleor-emails' into dev 2026-03-25 19:58:17 +02:00
Unchained
a0fa0f5401 fix(analytics): add OpenPanel revenue tracking to webhooks
Add OpenPanel import and initialization that was missing from webhook route.
Add order_received and revenue tracking when orders are confirmed.
Revenue tracking uses op.revenue() method with amount, currency,
order_id, and order_number properties.
2026-03-25 19:57:43 +02:00
Unchained
aa7a0ed3c8 fix(webhook): remove incorrect /100 division from formatPrice
Saleor stores amounts as actual currency values (e.g., 5479 RSD),
not as cents (e.g., 547900). The formatPrice function was incorrectly
dividing by 100, causing prices like 5479 RSD to display as 55 RSD.
2026-03-25 19:53:08 +02:00
Unchained
15a65758d7 fix(webhook): remove incorrect /100 division from formatPrice
Saleor stores amounts as actual currency values (e.g., 5479 RSD),
not as cents (e.g., 547900). The formatPrice function was incorrectly
dividing by 100, causing prices like 5479 RSD to display as 55 RSD.
2026-03-25 19:51:27 +02:00
Unchained
b5f8ddbaaa fix(analytics): add OpenPanel client secret and server-side tracking
Some checks failed
Build and Deploy / build (push) Has been cancelled
- Add OPENPANEL_CLIENT_SECRET for server-side tracking
- Add OPENPANEL_API_URL environment variable
- Add server-side OpenPanel tracking for orders
- Track order_received and revenue events on webhook,description:Commit OpenPanel fixes
2026-03-25 19:15:48 +02:00
Unchained
6dbaf99b29 fix(analytics): use direct OpenPanel URL instead of proxy
Use https://op.nodecrew.me/api and https://op.nodecrew.me/op1.js
directly since OpenPanel is self-hosted.
2026-03-25 19:09:10 +02:00
Unchained
cdd3f9c77e fix(analytics): add proxy route and correct script URL for OpenPanel
- Add /api/op/[...path] proxy route to forward events to self-hosted OpenPanel
- Add scriptUrl=/api/op/op1.js to OpenPanelComponent
- Proxy prevents ad blockers from blocking tracking requests
2026-03-25 19:07:52 +02:00
Unchained
17367024c2 feat(analytics): add OpenPanel tracking to storefront
Add comprehensive OpenPanel analytics tracking:
- Install @openpanel/nextjs SDK
- Add OpenPanelComponent to root layout for automatic page views
- Create useAnalytics hook for tracking custom events
- Track checkout funnel: started, shipping step, order completed
- Track product views and add-to-cart events
- Identify users on order completion
- Add NEXT_PUBLIC_OPENPANEL_CLIENT_ID to environment
2026-03-25 18:48:13 +02:00
Unchained
bf628f873f fix(webhook): prevent duplicate customer emails by only sending on ORDER_CONFIRMED
Some checks failed
Build and Deploy / build (push) Has been cancelled
Both ORDER_CREATED and ORDER_CONFIRMED were sending customer emails,
causing duplicates. Now only ORDER_CONFIRMED sends customer emails,
while both events still notify admins.
2026-03-25 16:17:35 +02:00
Unchained
eb311568db fix(webhook): get currency from channel.currency_code instead of top-level
Some checks failed
Build and Deploy / build (push) Has been cancelled
Saleor webhook payload stores currency in channel.currency_code,
not as a top-level currency field. Updated interfaces and conversion
function to use the correct location.
2026-03-25 15:32:49 +02:00
Unchained
c9aaacc452 fix(webhook): handle Saleor legacy webhook payload format with snake_case fields
Some checks failed
Build and Deploy / build (push) Has been cancelled
Saleor sends webhook payloads as arrays with snake_case fields:
- user_email instead of userEmail
- billing_address instead of billingAddress
- total_gross_amount instead of total.gross.amount
- etc.

Added convertPayloadToOrder() function to transform snake_case payload
to camelCase format expected by our email templates.
2026-03-25 15:22:40 +02:00
Unchained
e08e919e83 fix(webhook): handle Saleor subscription payload format (array)
Some checks failed
Build and Deploy / build (push) Has been cancelled
Saleor sends webhook payloads as arrays in subscription format.
This fix extracts the order from the array or object format.
2026-03-25 15:10:45 +02:00
Unchained
923f805d47 fix(webhook): normalize event names to uppercase for case-insensitive matching
Some checks failed
Build and Deploy / build (push) Has been cancelled
Saleor sends event names in lowercase (order_created) but our code
expects uppercase (ORDER_CREATED). This fix normalizes the event
name before checking supported events.
2026-03-25 14:59:32 +02:00
Unchained
5576946829 feat(emails): implement transactional email system with Resend
Some checks failed
Build and Deploy / build (push) Has been cancelled
- Add Resend email integration with @react-email/render
- Create email templates: OrderConfirmation, OrderShipped, OrderCancelled, OrderPaid
- Implement webhook handler for ORDER_CREATED and other events
- Add multi-language support for customer emails
- Admin emails in English with order details
- Update checkout page with auto-scroll on order completion
- Configure DASHBOARD_URL environment variable
2026-03-25 14:13:34 +02:00
Unchained
ef83538d0b fix: add required email and country fields to checkout
- Add email field (required) for order confirmation
- Add phone field in contact info section
- Add country dropdown with regional options
- Add validation for email format and required fields
- Add checkoutEmailUpdate mutation call before completing
- Use selected country instead of hardcoded RS
- Add translations for new fields (EN, SR, DE, FR)
2026-03-25 10:50:42 +02:00
Unchained
4fcd4b3ba8 refactor: abstract site URL across email templates
- Add NEXT_PUBLIC_SITE_URL to .env.local
- Update email templates to accept siteUrl prop
- Update webhook handler to pass siteUrl from env var
- Update create-webhooks.graphql with placeholder URL
2026-03-25 10:33:03 +02:00
Unchained
b8b3a57e6f feat: Add Saleor webhook handler with Resend email integration
- Add Resend SDK for transactional emails
- Create React Email templates for order events:
  - OrderConfirmation
  - OrderShipped
  - OrderCancelled
  - OrderPaid
- Multi-language support (SR, EN, DE, FR)
- Customer emails in their language
- Admin emails in English to me@hytham.me and tamara@hytham.me
- Webhook handler at /api/webhooks/saleor
- Supports: ORDER_CONFIRMED, ORDER_FULLY_PAID, ORDER_CANCELLED, ORDER_FULFILLED
- Add GraphQL mutation to create webhooks in Saleor
- Add Resend API key to .env.local
2026-03-25 10:10:57 +02:00
Unchained
3d8a77dafa refactor: centralize bundle filtering with filterOutBundles helper
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-24 20:18:06 +02:00
Unchained
bfce7dcca0 fix: filter bundles from homepage, sitemap, and static params
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-24 20:14:42 +02:00
Unchained
8f780c3585 fix: bundle UI improvements - remove QTY selector, filter bundles from similar products
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-24 18:50:39 +02:00
Unchained
9a61564e3c feat: add bundle feature with 2x/3x set options
Some checks failed
Build and Deploy / build (push) Has been cancelled
- Created BundleSelector component for selecting bundle options
- Updated ProductDetail to show bundle options
- Added bundle translations for all 4 locales
- Added GraphQL query for bundle products
- Updated TypeScript types for attributes
- Saleor backend: created bundle products for all base products
2026-03-24 16:00:07 +02:00
Unchained
569a3e65fe fix: correct metadata access and locale variable names 2026-03-24 14:16:49 +02:00
Unchained
df95e729fc CSS polish: increase newsletter input height for better UX 2026-03-24 13:46:38 +02:00
Unchained
b18ab349b6 fix: revert newsletter form, keep only taller input 2026-03-24 13:43:02 +02:00
Unchained
855215badd fix: update newsletter form layout for taller input 2026-03-24 13:40:08 +02:00
Unchained
f40e661bf3 fix: make newsletter input taller without changing button height 2026-03-24 13:38:59 +02:00
Unchained
080a9e4e21 fix: increase homepage newsletter input height to h-16 2026-03-24 13:36:07 +02:00
Unchained
922978bf80 feat: add ManoonOils logo as app icon and favicon 2026-03-24 12:45:18 +02:00
Unchained
930a9a7614 refactor: eliminate hardcoded locale comparisons for antifragility
Created centralized helpers:
- src/lib/i18n/pageMetadata.ts: All page metadata (titles, descriptions, alt text)
- src/lib/i18n/productText.ts: Product-specific translated text (shortDescription, benefits)
- src/lib/i18n/metadata.ts: Helper functions for locale handling

Updated all pages to use centralized metadata:
- Homepage: Uses getPageMetadata for title, description, productionAlt
- Products page: Uses getPageMetadata
- Product detail: Uses getPageMetadata + getTranslatedShortDescription/getTranslatedBenefits
- About page: Uses getPageMetadata

ProductDetail component now uses:
- getTranslatedShortDescription() instead of locale comparison
- getTranslatedBenefits() instead of locale comparison

All user-facing text now goes through translation files or centralized helpers.
Adding a new language now requires only:
1. Add to SUPPORTED_LOCALES in locales.ts
2. Add LOCALE_CONFIG entry
3. Add entries to pageMetadata.ts and productText.ts
4. Add translation keys to message files
2026-03-24 12:39:38 +02:00
Unchained
3d895f4d7a refactor: improve locale modularity
- Added src/lib/i18n/metadata.ts with helper functions
- Updated [locale]/layout.tsx to use DEFAULT_LOCALE constant
- routing.ts already uses centralized SUPPORTED_LOCALES

Note: For full antifragility, a larger refactor would centralize
all hardcoded locale comparisons for metadata text fallbacks.
Currently adding a new language requires:
1. SUPPORTED_LOCALES in locales.ts
2. LOCALE_CONFIG entry
3. Translation keys in all message files
2026-03-24 12:30:05 +02:00
Unchained
74ab98ad2f fix: multiple components using wrong locale for ProductCard links
- homepage page.tsx was passing productLocale (SR/EN) instead of locale (sr/en) to ProductCard
- ProductShowcase default locale was "SR" instead of "sr"
- ProductBenefits default locale was "SR" instead of "sr"

These caused URLs like /en/SR/products/... when clicking products
2026-03-24 11:38:14 +02:00
Unchained
ead03bc04f fix: product detail page passing wrong locale to ProductDetail and Footer 2026-03-24 11:34:52 +02:00
Unchained
a5cd048a6e refactor: centralize locale constants to prevent breaking changes
Created src/lib/i18n/locales.ts as single source of truth for:
- SUPPORTED_LOCALES array
- LOCALE_COOKIE name
- DEFAULT_LOCALE
- LOCALE_CONFIG (labels, flags, Saleor locale mapping)
- Helper functions (isValidLocale, getSaleorLocale, getLocaleFromPath)

Updated all files to use centralized constants:
- middleware.ts
- Header.tsx
- ProductCard.tsx
- sitemap.ts
- root layout and locale layout
- routing.ts

Benefits:
- Adding new locale only requires updating ONE file (locales.ts)
- No more hardcoded locale lists scattered across codebase
- Cookie name defined in one place
- Type-safe locale validation
2026-03-24 11:27:55 +02:00
Unchained
a4e7a07adb feat: add hreflang tags and international sitemap for SEO
- Added hreflang alternates to root layout for all locales (sr, en, de, fr)
- Added hreflang alternates to [locale] layout for all locales
- Updated sitemap to include all locale variants for every page
- Google will now properly index all language versions
2026-03-24 11:22:22 +02:00
Unchained
b70d46ff95 fix: checkout page not passing locale prop to Header/Footer 2026-03-24 08:15:45 +02:00
Unchained
a84647db6c feat: add language switcher with cookie persistence and browser detection
Changes:
- Root page.tsx now detects browser language (Accept-Language header)
  and cookie preference to redirect to correct locale (/sr or /en)
- Middleware handles old Serbian URLs (/products, /about, etc.) with
  301 redirects to /sr/* while respecting locale cookie
- Header component now includes language switcher dropdown with
  flag icons (Serbian and English)
- Language selection sets NEXT_LOCALE cookie and persists preference

Behavior:
- User visits / → redirects to /sr or /en based on cookie/browser
- User selects English from dropdown → cookie set, redirects to /en/*
- User visits /products with en cookie → /en/products (301)
- User visits /products with sr/no cookie → /sr/products (301)
2026-03-24 08:09:27 +02:00
Unchained
92b6c830e1 feat: implement locale-aware routing with [locale] dynamic segments
Some checks failed
Build and Deploy / build (push) Has been cancelled
WARNING: This change breaks existing SEO URLs for Serbian locale.

Changes:
- Migrated from separate locale folders (src/app/en/, src/app/de/, etc.)
  to [locale] dynamic segments (src/app/[locale]/)
- Serbian is now at /sr/ instead of / (root)
- English at /en/, German at /de/, French at /fr/
- All components updated to generate locale-aware links
- Root / now redirects to /sr (307 temporary redirect)

SEO Impact:
- Previously indexed Serbian URLs (/, /products, /about, /contact)
  will now return 404 or redirect to /sr/* URLs
- This is a breaking change for SEO - Serbian pages should ideally
  remain at root (/) with only non-default locales getting prefix
- Consider implementing 301 redirects from old URLs to maintain
  search engine rankings

Technical Notes:
- next-intl v4 with [locale] structure requires ALL locales to
  have the prefix (cannot have default locale at root)
- Alternative approach would be separate folder structure per locale
2026-03-23 20:59:33 +02:00