- Remove dev.manoonoils.com from storefront ingress to prevent cross-domain tracking issues
- Use environment variable for OpenPanel API URL in route handler
- Fixes session state conflicts from multiple domains
- Updated monitor.py to support both OAuth and Service Account
- Created setup-oauth-local.py for easy local authorization
- Created cronjob-oauth.yaml for OAuth-based deployment
- Updated README with both authentication options
- OAuth is now the recommended method (no key file needed)
- Python monitoring script for daily GSC reports
- Kubernetes CronJob for automated execution
- Tracks search analytics, crawl errors, and sitemap status
- Includes full setup documentation
- Update ingress to serve all domains (dev.manoonoils.com, manoonoils.com, www.manoonoils.com)
- Update NEXT_PUBLIC_SITE_URL to https://manoonoils.com in deployment env vars
- Prepare for 24h testing period before removing dev domain
- Add template replacement logic for product keywords
- Replace {{productName}} with actual product.name
- Keywords now show correct product name instead of template variable
- Add keywords, canonical, OpenGraph to About page
- Refactor Contact page to server component with generateMetadata
- Create ContactPageClient for form functionality
- All pages now have complete SEO coverage
- Document actual rendered HTML structure
- Show extracted JSON-LD schemas
- Include complete verification test results
- Prove all 7/7 SEO checks pass with real data
- Remove next/script dependency causing SSR issues
- Use regular script tag for server-side rendering
- Add real SEO verification test that checks rendered output
- All 7/7 SEO checks now passing
- Add comprehensive SEO implementation guide
- Add automated SEO testing script
- Document all schema types and integrations
- Include verification methods and expected impact
- Fix shipping cost not included in checkout total
- Add useShippingMethodSelector hook for proper abstraction
- Remove blocking initCheckout from Header for better performance
- Checkout now initializes lazily when cart opens or item added
Complete analytics overhaul with redundant tracking:
CLIENT-SIDE (useAnalytics hook):
- Tracks user behavior in real-time
- Product views, add to cart, checkout steps
- Revenue tracking via op.revenue()
- Captures user session data
SERVER-SIDE (API route + server functions):
- POST /api/analytics/track-order endpoint
- trackOrderCompletedServer() function
- Reliable tracking that can't be blocked
- Works even if browser closes
DUAL TRACKING for order completion:
1. Client tracks immediately (session data)
2. API call to server endpoint (reliable)
3. Both sources recorded with 'source' property
Files:
- src/lib/analytics.ts - Client-side with dual tracking
- src/lib/analytics-server.ts - Server-side tracking
- src/app/api/analytics/track-order/route.ts - API endpoint
Benefits:
- ✅ 100% revenue capture (server-side backup)
- ✅ Real-time client tracking
- ✅ Ad blocker resistant
- ✅ Browser-close resistant
- ✅ Full funnel visibility
The checkout was being cleared before tracking, causing getTotal()
to return 0. Fixed by reordering operations:
1. Track order completion (while checkout data exists)
2. Then clear the checkout
Added console log to verify total is captured correctly.
Add detailed console logs to debug why revenue tracking isn't working:
- Log when trackOrderCompleted is called
- Log revenue amount and currency
- Log success/failure of revenue tracking
This will help identify if the issue is with the op.revenue() call
or if it's failing silently.
The order confirmation requires MANAGE_ORDERS permission which
the storefront API token doesn't have. Removing the auto-confirmation
attempt to prevent console errors. Orders will remain UNCONFIRMED
until manually confirmed in Saleor Dashboard.
Remove complex tracking implementation that was causing errors:
- Remove op.increment/decrement calls (causing Duplicate event errors)
- Remove complex type definitions
- Remove unused tracking methods
- Keep only essential tracking with proper error handling
This reverts to a simpler, working analytics implementation.
Update free shipping minimum from 3,000 RSD to 10,000 RSD across:
- TickerBar component
- English translations (en.json)
- Serbian translations (sr.json)
- French translations (fr.json)
- German translations (de.json)
Features:
- Add Cash on Delivery (COD) payment method
- Modular payment configuration system
- PaymentMethodSelector and PaymentMethodCard components
- 30-day money back guarantee badge
- Checkout language fix for multilingual emails
- Cart reset after order completion
- Service layer architecture
Note: Orders are UNCONFIRMED until manually confirmed in dashboard.
Auto-confirmation has permission issues in Saleor.
Features:
- Add Cash on Delivery (COD) payment method
- Modular payment configuration system
- Reusable PaymentMethodSelector and PaymentMethodCard components
- 30-day money back guarantee badge
- Checkout language fix for multilingual emails
- Cart reset after order completion
- Service layer architecture for checkout operations
Technical:
- Abstracted email system in saleor-core-extensions
- Payment method detection from order data
- Configuration-driven translations (EN, SR, DE, FR)
Note: Order auto-confirmation has permission issues in Saleor,
orders will be UNCONFIRMED until manually confirmed.
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.
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.
- 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)
- 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
- 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
- Add black border-2 and shadow when selected
- Change icon background to black with white icon when selected
- Add 'Selected' badge with checkmark icon
- Make text bolder and more visible when selected
- Add hover shadow effects
- Add 'selected' translation key for all locales
- Overall more lively and prominent selected state
- Change useTranslations from Payment.COD to Payment.cod
- Fixes MISSING_MESSAGE error in CODInstructions component
- Consistent with translation file structure
- Change translation keys from COD to cod in all locale files
- Fixes MISSING_MESSAGE error in PaymentMethodCard
- Aligns translation keys with payment method IDs
- Update PaymentMethodCard to use next-intl translations
- Remove hardcoded English names from config
- Add comingSoon translation key for unavailable methods
- Payment names now match checkout language (SR/EN/DE/FR)
Ensure language code is set in checkout store before creating checkout.
This ensures orders created from any page will have correct language.
- Add setLanguageCode to NewHero before addLine
- Add setLanguageCode to ProductDetail before addLine
- Uses current locale from useLocale or props
- Add languageCode to checkout store state
- Add setLanguageCode action to store
- Pass languageCode when creating checkout
- Header component sets language code from useLocale hook
- Enables multi-language order confirmation 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).
- Organized into 7 implementation phases with dependencies
- Includes priority matrix (P0/P1/P2)
- Revenue and SEO impact ratings
- Success metrics for tracking
- Resource requirements and timeline estimates
- Dependency graph showing implementation order
- Organized into 7 implementation phases with dependencies
- Includes priority matrix (P0/P1/P2)
- Revenue and SEO impact ratings
- Success metrics for tracking
- Resource requirements and timeline estimates
- Dependency graph showing implementation order
- Fixed OrderNotificationService tests by removing React element prop assertions
- Updated admin email tests to match actual function signatures
- Fixed AnalyticsService test hoisting issue with vi.hoisted()
- Exported AnalyticsService class for test instantiation
- Converted require() to dynamic import() in singleton test
- All 49 tests now passing
- Coverage: 88% statements, 90% functions, 89% lines, 67% branches
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).
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
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.
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.
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.
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.
Add NEXT_PUBLIC_OPENPANEL_CLIENT_ID, OPENPANEL_CLIENT_SECRET, and
OPENPANEL_API_URL to the storefront runtime container for server-side
tracking to work properly.
- 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
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.
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.
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.
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.
- 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
- 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)
- 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
- 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 [email protected] and [email protected]
- 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
- 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
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
- 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