Changed Rybbit script loading from server-side rewrite to client-side direct
connection. This prevents Next.js from proxying the request, which was causing
all visitor IPs to show as the Hetzner server IP (138.201.11.251).
Before:
- Browser → Next.js → Rybbit (server-side proxy, loses client IP)
After:
- Browser → Rybbit (direct connection, real IP preserved)
Changes:
- layout.tsx: Use direct Rybbit URL for script src
- next.config.ts: Remove /api/script.js rewrite
- Add permanent redirects for /products/manoon to /products
- Strip malformed /contact suffix from product URLs
- Create custom branded 404 page with product navigation
- Add NotFound translations for en and sr locales
- Create new API route /api/rybbit/track to proxy Rybbit tracking requests
- Extract real client IP from Cloudflare headers (cf-connecting-ip)
- Forward X-Forwarded-For and X-Real-IP headers to analytics backends
- Update OpenPanel proxy to also forward client IP
- Update next.config.ts rewrite to use internal API route
This fixes geo-location issues where all traffic appeared to come from
Cloudflare edge locations instead of actual visitor countries.
- Create new API route /api/rybbit/track to proxy Rybbit tracking requests
- Extract real client IP from Cloudflare headers (cf-connecting-ip)
- Forward X-Forwarded-For and X-Real-IP headers to analytics backends
- Update OpenPanel proxy to also forward client IP
- Update next.config.ts rewrite to use internal API route
This fixes geo-location issues where all traffic appeared to come from
Cloudflare edge locations instead of actual visitor countries.
- Add revalidate=3600 to homepage and products page (1hr ISR)
- Add middleware to set cache headers for HTML pages
- Bypass cache for checkout and cart pages
- Add Next.js rewrites to proxy /api/script.js and /api/track through self-hosted Rybbit
- This bypasses ad blockers that would block rybbit.nodecrew.me directly
- Add NEXT_PUBLIC_RYBBIT_HOST and NEXT_PUBLIC_RYBBIT_SITE_ID env vars to K8s deployment
- Add RybbitService for tracking e-commerce events
- Update useAnalytics hook to track with both OpenPanel and Rybbit
- Add Rybbit script to layout for page view tracking
- Track all applicable store events: product views, cart, checkout, orders, search, etc.
- 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
- 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
- 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.
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.
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
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).
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.
- 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