538 lines
15 KiB
Markdown
538 lines
15 KiB
Markdown
# ManoonOils Storefront - AGENTS.md
|
||
|
||
## Project Overview
|
||
|
||
**ManoonOils** is a premium natural cosmetics e-commerce storefront built as a headless Next.js application. It serves the Serbian market primarily (with German, French, and English localization) selling natural oils and serums for hair and skin care.
|
||
|
||
**Current Status:** Active development - migrating from WordPress/WooCommerce to Saleor headless commerce.
|
||
|
||
**Production URL:** https://manoonoils.com (currently WordPress, transitioning to Next.js)
|
||
**Staging/Dev URL:** https://dev.manoonoils.com (Next.js storefront)
|
||
**Saleor API:** https://api.manoonoils.com
|
||
**Saleor Dashboard:** https://dashboard.manoonoils.com
|
||
|
||
---
|
||
|
||
## Tech Stack
|
||
|
||
| Layer | Technology | Version |
|
||
|-------|-----------|---------|
|
||
| **Framework** | Next.js (App Router) | 16.1.6 |
|
||
| **Language** | TypeScript | 5.x |
|
||
| **React** | React | 19.2.3 |
|
||
| **Styling** | Tailwind CSS | 4.x |
|
||
| **State Management** | Zustand | 5.x |
|
||
| **i18n** | next-intl | 4.8.3 |
|
||
| **API Client** | Apollo Client | 4.1.6 |
|
||
| **Backend** | Saleor (GraphQL) | Cloud-hosted |
|
||
| **Deployment** | Docker + K3s (Kubernetes) | - |
|
||
| **Object Storage** | MinIO | - |
|
||
| **Analytics** | OpenPanel + Rybbit | - |
|
||
| **Email** | Resend | - |
|
||
|
||
---
|
||
|
||
## Development Environment
|
||
|
||
### Setup Commands
|
||
```bash
|
||
# Install dependencies
|
||
npm install
|
||
|
||
# Start development server
|
||
npm run dev
|
||
# Server runs on http://localhost:3000
|
||
|
||
# Build for production
|
||
npm run build
|
||
|
||
# Run tests
|
||
npm test # Interactive mode
|
||
npm run test:run # Single run
|
||
npm run test:coverage # With coverage
|
||
|
||
# Run e2e tests
|
||
npm run test:e2e # Headless
|
||
npm run test:e2e:ui # With UI
|
||
|
||
# Linting
|
||
npm run lint
|
||
```
|
||
|
||
### Environment Variables
|
||
Create `.env.local` with:
|
||
```env
|
||
NEXT_PUBLIC_SALEOR_API_URL=https://api.manoonoils.com/graphql/
|
||
NEXT_PUBLIC_SITE_URL=https://manoonoils.com
|
||
NEXT_PUBLIC_OPENPANEL_CLIENT_ID=your-client-id
|
||
NEXT_PUBLIC_RYBBIT_HOST=https://rybbit.nodecrew.me
|
||
NEXT_PUBLIC_RYBBIT_SITE_ID=your-site-id
|
||
```
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
### Directory Structure
|
||
```
|
||
src/
|
||
├── app/ # Next.js App Router
|
||
│ ├── [locale]/ # Localized routes (sr, en, de, fr)
|
||
│ │ ├── page.tsx # Homepage
|
||
│ │ ├── products/
|
||
│ │ │ ├── page.tsx # Product listing
|
||
│ │ │ └── [slug]/ # Product detail
|
||
│ │ ├── solutions/ # Programmatic SEO pages
|
||
│ │ │ ├── page.tsx # Solutions hub
|
||
│ │ │ ├── [slug]/ # Oil-for-concern pages
|
||
│ │ │ ├── by-oil/ # Browse by oil
|
||
│ │ │ └── by-concern/ # Browse by concern
|
||
│ │ ├── about/
|
||
│ │ ├── contact/
|
||
│ │ └── checkout/
|
||
│ ├── api/ # API routes
|
||
│ ├── layout.tsx # Root layout
|
||
│ ├── sitemap.ts # Dynamic sitemap
|
||
│ └── robots.ts # Robots.txt
|
||
├── components/
|
||
│ ├── cart/ # Cart drawer
|
||
│ ├── home/ # Homepage sections
|
||
│ ├── layout/ # Header, Footer, MobileMenu
|
||
│ ├── payment/ # Payment components
|
||
│ ├── product/ # Product cards, details
|
||
│ ├── programmatic-seo/ # SEO page templates
|
||
│ ├── providers/ # Context providers
|
||
│ ├── seo/ # Schema markup components
|
||
│ ├── solutions/ # Breadcrumb, etc.
|
||
│ └── ui/ # Reusable UI (Drawer, Marquee)
|
||
├── lib/
|
||
│ ├── analytics/ # Analytics tracking
|
||
│ ├── config/ # Configuration (shipping, payments)
|
||
│ ├── i18n/ # Locale config, metadata
|
||
│ ├── programmatic-seo/ # SEO data loading, types
|
||
│ ├── saleor/ # GraphQL queries, mutations
|
||
│ ├── seo/ # Keywords, schema
|
||
│ └── services/ # Business logic services
|
||
├── stores/ # Zustand stores
|
||
├── types/ # TypeScript types
|
||
└── i18n/ # Translation messages
|
||
```
|
||
|
||
---
|
||
|
||
## Supported Locales
|
||
|
||
| Locale | Code | Saleor Mapping | Flag |
|
||
|--------|------|----------------|------|
|
||
| Serbian | `sr` | SR | 🇷🇸 |
|
||
| English | `en` | EN | 🇬🇧 |
|
||
| German | `de` | EN | 🇩🇪 |
|
||
| French | `fr` | EN | 🇫🇷 |
|
||
|
||
**Default Locale:** Serbian (`sr`)
|
||
|
||
**URL Structure:**
|
||
- Serbian: `https://manoonoils.com/` (root) or `/sr/`
|
||
- English: `https://manoonoils.com/en/`
|
||
- German: `https://manoonoils.com/de/`
|
||
- French: `https://manoonoils.com/fr/`
|
||
|
||
---
|
||
|
||
## Existing Features
|
||
|
||
### 1. Homepage (`/`)
|
||
- Hero video section
|
||
- Problem/solution narrative
|
||
- Product showcase
|
||
- How it works
|
||
- Before/after gallery
|
||
- Testimonials
|
||
- Newsletter signup
|
||
- Trust badges
|
||
- Exit intent popup (email capture)
|
||
- Ticker bar with promotions
|
||
|
||
### 2. Product Listing (`/products`)
|
||
- Grid layout with product cards
|
||
- Filtering and sorting
|
||
- Localization support
|
||
- SEO-optimized with keywords
|
||
|
||
### 3. Product Detail (`/products/[slug]`)
|
||
- Product images (from Saleor/MinIO)
|
||
- Pricing and variants
|
||
- Add to cart functionality
|
||
- Product benefits display
|
||
- Bundle selector (2x, 3x packs)
|
||
- Related products
|
||
- Product reviews
|
||
- SEO schema markup (ProductSchema)
|
||
|
||
### 4. Cart & Checkout
|
||
- **Cart Drawer:** Slide-out cart with quantity controls
|
||
- **Checkout Page:**
|
||
- Shipping address form
|
||
- Shipping method selector
|
||
- Payment method (Cash on Delivery - COD)
|
||
- Order summary
|
||
- Analytics tracking
|
||
- **Free Shipping:** Orders over 10,000 RSD
|
||
|
||
### 5. Programmatic SEO (`/solutions`)
|
||
- **Solutions Hub:** Entry point to all solution pages
|
||
- **Oil-for-Concern Pages:** 40 pages (10 pairs × 4 locales)
|
||
- Example: `/sr/solutions/arganovo-ulje-za-bore`
|
||
- Example: `/en/solutions/argan-oil-for-wrinkles`
|
||
- **Browse by Oil:** `/solutions/by-oil`
|
||
- **Browse by Concern:** `/solutions/by-concern`
|
||
- **Content:** JSON-driven with localized slugs, titles, descriptions, FAQs
|
||
- **Schema:** FAQSchema for rich snippets
|
||
|
||
### 6. Static Pages
|
||
- **About:** Brand story, mission
|
||
- **Contact:** Contact form
|
||
|
||
### 7. Internationalization
|
||
- Full i18n with next-intl
|
||
- 4 language translations
|
||
- SEO metadata per locale
|
||
- Hreflang tags for all pages
|
||
- Locale-aware routing
|
||
|
||
### 8. SEO
|
||
- Dynamic sitemap.xml (48+ URLs)
|
||
- Canonical URLs with locale prefix
|
||
- OpenGraph tags
|
||
- Twitter Card tags
|
||
- Product schema markup
|
||
- Organization schema
|
||
- FAQ schema (programmatic pages)
|
||
- Breadcrumb schema
|
||
- Keywords strategy per page type
|
||
|
||
### 9. Analytics & Tracking
|
||
- **OpenPanel:** User behavior analytics
|
||
- **Rybbit:** Session replay and tracking
|
||
- Client-side script injection
|
||
- Proxy configuration for privacy
|
||
- **Mautic:** Email marketing tracking
|
||
- **Custom Analytics:**
|
||
- Add to cart events
|
||
- Checkout funnel
|
||
- Cart views
|
||
- Remove from cart
|
||
|
||
### 10. Email
|
||
- **Resend** integration for transactional emails
|
||
- Email capture popup on exit intent
|
||
|
||
---
|
||
|
||
## Data Architecture
|
||
|
||
### Programmatic SEO Content
|
||
```
|
||
data/
|
||
├── taxonomy/
|
||
│ ├── oils.json # 5 oils with metadata
|
||
│ └── concerns.json # 9 skin concerns
|
||
└── content/
|
||
└── oil-for-concern/ # 10 content files
|
||
├── argan-oil-wrinkles.json
|
||
├── argan-oil-dry-skin.json
|
||
├── argan-oil-under-eye-bags.json
|
||
├── jojoba-oil-acne.json
|
||
├── jojoba-oil-oily-skin.json
|
||
├── rosehip-oil-wrinkles.json
|
||
├── rosehip-oil-dark-spots.json
|
||
├── rosehip-oil-acne-scars.json
|
||
├── sea-buckthorn-oil-hyperpigmentation.json
|
||
└── sweet-almond-oil-sensitive-skin.json
|
||
```
|
||
|
||
### Content JSON Structure
|
||
Each file contains localized content:
|
||
- `pageTitle` (per locale)
|
||
- `metaTitle` / `metaDescription` (per locale)
|
||
- `oilName` / `concernName` (per locale)
|
||
- `whyThisWorks` (per locale)
|
||
- `keyBenefits`, `howToApply` (per locale arrays)
|
||
- `faqs` (localized Q&A)
|
||
- `localizedSlugs` (sr, en, de, fr)
|
||
|
||
---
|
||
|
||
## API Routes
|
||
|
||
| Route | Purpose |
|
||
|-------|---------|
|
||
| `/api/analytics/track-order` | Order completion tracking |
|
||
| `/api/email-capture` | Email subscription endpoint |
|
||
| `/api/geoip` | GeoIP detection for localization |
|
||
| `/api/rybbit/track` | Rybbit analytics proxy |
|
||
|
||
---
|
||
|
||
## E-commerce Backend (Saleor)
|
||
|
||
### Current Integration
|
||
- **Products:** Fetched via GraphQL
|
||
- **Variants:** Size/options support
|
||
- **Checkout:** Saleor checkout API
|
||
- **Payments:** Cash on Delivery (COD) only
|
||
- **Orders:** Tracked in Saleor
|
||
|
||
### Shipping Configuration
|
||
```typescript
|
||
// src/lib/config/shipping.ts
|
||
FREE_SHIPPING_THRESHOLD_RSD = 10000; // 10,000 RSD
|
||
DEFAULT_SHIPPING_COST_RSD = 500; // 500 RSD
|
||
```
|
||
|
||
### Payment Methods
|
||
- Cash on Delivery (COD) - Available
|
||
- Credit Card - Coming soon
|
||
- Bank Transfer - Coming later
|
||
|
||
---
|
||
|
||
## Infrastructure
|
||
|
||
### Deployment Stack
|
||
- **Container:** Docker (node:20-slim)
|
||
- **Orchestration:** K3s (Kubernetes)
|
||
- **Ingress:** Traefik
|
||
- **Object Storage:** MinIO
|
||
- **Databases:**
|
||
- PostgreSQL (Saleor)
|
||
- MariaDB (WordPress - legacy)
|
||
- Redis (cache + task queue)
|
||
|
||
### Domains & Routing
|
||
```
|
||
manoonoils.com → WordPress (production, being phased out)
|
||
dev.manoonoils.com → Next.js storefront (staging)
|
||
api.manoonoils.com → Saleor GraphQL API
|
||
dashboard.manoonoils.com → Saleor Admin Dashboard
|
||
minio-api.nodecrew.me → MinIO Object Storage
|
||
```
|
||
|
||
### Image Domains (Next.js)
|
||
- `manoonoils.com`
|
||
- `minio-api.nodecrew.me`
|
||
- `api.manoonoils.com`
|
||
- `**.saleor.cloud`
|
||
- `images.unsplash.com`
|
||
|
||
### Build Output
|
||
- `output: 'standalone'` (Next.js standalone mode)
|
||
- Static files served from `.next/static/`
|
||
- Port: 3000
|
||
|
||
---
|
||
|
||
## Planned Features (Roadmap)
|
||
|
||
### Phase 1: Essential (Current Focus)
|
||
- [x] Saleor core products integration
|
||
- [x] Programmatic SEO (40 pages)
|
||
- [x] Multi-language support (4 locales)
|
||
- [x] Cart and checkout flow
|
||
- [x] Analytics tracking
|
||
- [ ] Product reviews (Judge.me or custom)
|
||
- [ ] Upsells & cross-sells
|
||
- [ ] AJAX add to cart (no page reload)
|
||
- [ ] Mautic abandoned cart recovery
|
||
|
||
### Phase 2: Growth (1-3 months)
|
||
- [ ] Email marketing campaigns (Mautic)
|
||
- [ ] Wishlist/favorites
|
||
- [ ] Product bundles
|
||
- [ ] Recently viewed products
|
||
- [ ] Back in stock notifications
|
||
|
||
### Phase 3: Advanced (6+ months)
|
||
- [ ] Loyalty/rewards program
|
||
- [ ] AI/ML product recommendations
|
||
- [ ] Subscription/recurring products
|
||
- [ ] Product comparison
|
||
- [ ] Quick view modal
|
||
- [ ] Dynamic pricing/volume discounts
|
||
|
||
---
|
||
|
||
## Code Conventions
|
||
|
||
### File Naming
|
||
- Components: `PascalCase.tsx`
|
||
- Utilities: `camelCase.ts`
|
||
- API routes: `route.ts`
|
||
- Pages: `page.tsx`
|
||
- Layouts: `layout.tsx`
|
||
|
||
### Styling
|
||
- Tailwind CSS utility classes
|
||
- Custom colors: `[#1A1A1A]`, `[#FAF9F7]`, `[#666666]`, etc.
|
||
- Responsive breakpoints: `sm:`, `md:`, `lg:`
|
||
|
||
### State Management
|
||
- **Zustand** for global state (cart, checkout)
|
||
- **React state** for local component state
|
||
|
||
### API Calls
|
||
- **Apollo Client** for GraphQL (Saleor)
|
||
- **REST** for custom API routes
|
||
|
||
### Error Handling
|
||
- Error boundaries for React components
|
||
- Try/catch for async operations
|
||
- Console logging for build-time errors
|
||
|
||
---
|
||
|
||
## Testing
|
||
|
||
### Unit Tests
|
||
- **Framework:** Vitest
|
||
- **Location:** `src/__tests__/unit/`
|
||
- Run: `npm run test:run`
|
||
|
||
### Integration Tests
|
||
- **Location:** `src/__tests__/integration/`
|
||
- API route testing with MSW
|
||
|
||
### E2E Tests
|
||
- **Framework:** Playwright
|
||
- **Location:** Root `e2e/` directory
|
||
- Run: `npm run test:e2e`
|
||
|
||
---
|
||
|
||
## SEO Checklist
|
||
|
||
### Implemented
|
||
- [x] Dynamic sitemap.xml (auto-generated)
|
||
- [x] Robots.txt
|
||
- [x] Canonical URLs
|
||
- [x] Hreflang tags (all 4 locales)
|
||
- [x] OpenGraph meta tags
|
||
- [x] Twitter Card meta tags
|
||
- [x] Product schema (JSON-LD)
|
||
- [x] Organization schema
|
||
- [x] FAQ schema (programmatic pages)
|
||
- [x] Breadcrumb schema
|
||
- [x] Keywords per page type
|
||
- [x] Localized metadata
|
||
|
||
### Pending
|
||
- [ ] Blog/Content marketing pages
|
||
- [ ] Review schema (when reviews added)
|
||
- [ ] Article schema (for blog posts)
|
||
|
||
---
|
||
|
||
## Analytics Events
|
||
|
||
### Tracked Events
|
||
| Event | Trigger | Data |
|
||
|-------|---------|------|
|
||
| `add_to_cart` | Click "Add to Cart" | Product ID, name, price, quantity |
|
||
| `remove_from_cart` | Click remove button | Product ID, name, quantity |
|
||
| `view_cart` | Open cart drawer | Total, item count, currency |
|
||
| `begin_checkout` | Start checkout | Cart contents |
|
||
| `purchase` | Complete order | Order ID, total, items |
|
||
| `page_view` | Page load | URL, referrer |
|
||
|
||
---
|
||
|
||
## Important Notes
|
||
|
||
### Cache Strategy
|
||
- Static pages: `public, max-age=3600, stale-while-revalidate=86400`
|
||
- Checkout/cart pages: No cache
|
||
- API routes: No cache
|
||
|
||
### Image Optimization
|
||
- Next.js Image component with automatic optimization
|
||
- Avif and WebP formats supported
|
||
- Responsive sizing with deviceSizes and imageSizes
|
||
|
||
### Security
|
||
- Environment variables for sensitive config
|
||
- No secrets in client-side code
|
||
- CORS configured for API routes
|
||
|
||
### Performance
|
||
- `output: 'standalone'` for Docker optimization
|
||
- Image optimization via Next.js
|
||
- Code splitting by route
|
||
- Lazy loading for below-fold content
|
||
|
||
---
|
||
|
||
## Common Tasks
|
||
|
||
### Adding a New Programmatic SEO Page
|
||
1. Add content JSON to `data/content/oil-for-concern/`
|
||
2. Run `node scripts/validate-taxonomy.js`
|
||
3. Run `node scripts/generate-urls.js`
|
||
4. Build and verify: `npm run build`
|
||
|
||
### Adding a New Locale
|
||
1. Add locale to `SUPPORTED_LOCALES` in `src/lib/i18n/locales.ts`
|
||
2. Add translations to `src/i18n/messages/[locale].json`
|
||
3. Add SEO keywords in `src/lib/seo/keywords/locales/[locale].ts`
|
||
4. Add page metadata in `src/lib/i18n/pageMetadata.ts`
|
||
5. Update sitemap logic if needed
|
||
|
||
### Adding a New Product
|
||
1. Add product in Saleor Dashboard
|
||
2. Upload images to MinIO
|
||
3. Set pricing and variants
|
||
4. Build will auto-generate product pages
|
||
|
||
### Updating Shipping Threshold
|
||
1. Edit `src/lib/config/shipping.ts`
|
||
2. Change `FREE_SHIPPING_THRESHOLD_RSD`
|
||
3. Rebuild and redeploy
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
### Build Issues
|
||
- Check `NEXT_PUBLIC_SALEOR_API_URL` is set
|
||
- Verify MinIO is accessible
|
||
- Run `npm run lint` before building
|
||
|
||
### Locale Issues
|
||
- Check locale cookie (`NEXT_LOCALE`)
|
||
- Verify messages JSON files exist
|
||
- Check `SUPPORTED_LOCALES` array
|
||
|
||
### Cart Issues
|
||
- Verify Saleor checkout API is accessible
|
||
- Check `saleorCheckoutStore.ts` for errors
|
||
- Clear browser localStorage if needed
|
||
|
||
### Image Issues
|
||
- Check MinIO bucket permissions
|
||
- Verify image domains in `next.config.ts`
|
||
- Check image URLs are HTTPS
|
||
|
||
---
|
||
|
||
## Contact & Resources
|
||
|
||
- **Repository:** Private Git repository
|
||
- **Saleor Dashboard:** https://dashboard.manoonoils.com
|
||
- **MinIO Console:** https://minio-api.nodecrew.me
|
||
- **Analytics:** OpenPanel + Rybbit
|
||
- **Email Service:** Resend
|
||
|
||
---
|
||
|
||
*Last updated: April 2026*
|
||
*Maintained by: ManoonOils Development Team*
|