- src/app/api/leads/route.ts — POST /api/leads with Zod validation, UTM + GA Client ID from cookies, rate limiting (5 req/min/IP), async Twenty CRM sync (fire-and-forget) - src/app/api/binotel/webhook/route.ts — HMAC-SHA256 verification, matches call to most recent lead by phone, saves binotelCallId - src/lib/twenty.ts — GraphQL mutation to create Person in Twenty CRM - src/lib/binotel.ts — HMAC verify, phone normalizer, payload parser - src/lib/gtm.ts — pushGTMEvent() client helper Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
403 lines
18 KiB
Markdown
403 lines
18 KiB
Markdown
# Shumiland Web — Implementation Plan
|
||
|
||
## Project Overview
|
||
|
||
Corporate website for Shumiland family theme park (shumiland.com.ua).
|
||
Stack: Next.js 15.4.11 + Payload CMS 3.x + PostgreSQL + Tailwind CSS 4 + shadcn/ui.
|
||
Language: Ukrainian only. No i18n.
|
||
|
||
---
|
||
|
||
## Phase Status
|
||
|
||
| Phase | Name | Status |
|
||
|-------|------|--------|
|
||
| 1 | Foundation (Next.js + Payload + DB) | ✅ Done |
|
||
| 2 | Content Model (Collections + Globals + Blocks) | ✅ Done |
|
||
| 3 | Middleware (UTM + Google Client ID) | ✅ Done |
|
||
| 4 | Public Pages + BlockRenderer | ✅ Done |
|
||
| 5 | Lead System + Binotel + Twenty CRM | ⏳ Next |
|
||
| 6 | Ticket Payment (ezy.com.ua → Monobank) | ⬜ Pending |
|
||
| 7 | Analytics (GTM + GA4 + Umami) + Employee Portal | ⬜ Pending |
|
||
| 8 | Polish + Seed + Docker finalization | ⬜ Pending |
|
||
|
||
---
|
||
|
||
## Phase 1 — Foundation ✅
|
||
|
||
**Goal:** Working Next.js 15 + Payload CMS 3.x + PostgreSQL, auth, admin panel.
|
||
|
||
### Completed:
|
||
- [x] Next.js 15.4.11 + Payload CMS 3.38.x init (pinned to avoid 15.5.x incompatibility)
|
||
- [x] `docker-compose.yml` — PostgreSQL (5432), Twenty CRM (3003), Umami (3001), Metabase (3002)
|
||
- [x] `docker/postgres/init.sql` — creates databases: shumiland, umami, twenty, metabase
|
||
- [x] `.env.example` — all vars: DATABASE_URL, PAYLOAD_SECRET, EZY_*, SMTP_*, GTM, GA4, Binotel, Umami, Twenty
|
||
- [x] Tailwind CSS 4 with `@theme` block + brand colors + fonts
|
||
- [x] `src/app/globals.css` — brand color CSS vars, Montserrat Alternates + Rubik
|
||
- [x] `src/app/(frontend)/layout.tsx` — imports fonts, sets `<html lang="uk">`
|
||
- [x] `src/access/index.ts` — `isAdmin`, `isSuperAdmin`, `isEditor`, `isAdminOrPublished`, `isAuthenticated`
|
||
- [x] `src/collections/Users.ts` — auth:true, roles (SUPER_ADMIN/ADMIN/EDITOR), saveToJWT
|
||
- [x] `src/collections/Media.ts` — upload, 4 image sizes, mimeTypes incl. video + pdf
|
||
- [x] `src/payload.config.ts` — buildConfig with postgresAdapter, lexicalEditor
|
||
- [x] `src/app/(payload)/admin/[[...segments]]/page.tsx` — admin routing
|
||
- [x] `src/app/(payload)/admin/importMap.js` — empty importMap
|
||
- [x] `docker-compose.yml` + `Dockerfile` (multi-stage, node:20-alpine)
|
||
- [x] shadcn/ui components: Button (orange/green/blue/purple/yellow variants), Card, Input, Label, Textarea, Badge, Separator
|
||
- [x] `"type": "module"` in package.json (required for Payload CLI ESM)
|
||
- [x] `.npmrc` with `approve-builds=true`
|
||
|
||
**Brand colors:**
|
||
```
|
||
brand-green: #223D17
|
||
brand-green-mid: #446231
|
||
brand-orange: #F28E44
|
||
brand-yellow: #FCDD67
|
||
brand-blue: #3E4095
|
||
brand-purple: #7152A1
|
||
brand-black: #231F20
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 2 — Content Model ✅
|
||
|
||
**Goal:** All content types defined. Admin can create and edit everything.
|
||
|
||
### Completed:
|
||
- [x] `src/collections/Labels.ts` — name (unique), color (7 options), description, isActive. Group: "Маркетинг"
|
||
- [x] `src/collections/Pages.ts` — title, slug, isTemplate, blocks[] (all 11), SEO group, drafts
|
||
- [x] `src/collections/LandingPages.ts` — + eventDate, stickyCta, conversionGoal, drafts
|
||
- [x] `src/collections/Blog.ts` — title, slug, excerpt, coverImage, category, tags, publishedAt, body (richText), SEO
|
||
- [x] `src/collections/Events.ts` — + eventDate, eventEndDate, isFeatured, relatedLanding relation
|
||
- [x] `src/collections/Leads.ts` — name, phone, email, **label** (relation), tag, status, message, guestCount, eventDate, UTM fields (utmSource/Medium/Campaign/Content/Term), **googleClientId**, twentyId, binotelCallId, notes
|
||
- [x] `src/collections/Orders.ts` — orderNumber, status, customer fields, items[], totalAmount, tickets relation, ezyPaymentUrl, UTM fields, googleClientId
|
||
- [x] `src/collections/Tickets.ts` — ticketCode (SL-YYYYMMDD-XXXXXX), order relation, categoryName, price, qrCodeUrl, isUsed, usedAt, usedByEmployee
|
||
- [x] `src/globals/SiteSettings.ts` — tabs: General, Соцмережі, Аналітика (GTM/GA4/Umami), Binotel, SEO, Системне (maintenance mode)
|
||
- [x] `src/globals/TicketsConfig.ts` — categories[]: categoryId, name, price, location, ageGroup, isFree, freeCondition, isActive, order
|
||
- [x] `src/globals/Navigation.ts` — headerMenu (with dropdown support), headerCta, footerColumns, footerBottomLinks
|
||
- [x] 11 Page Builder blocks in `src/blocks/`:
|
||
- `HeroBlock` — backgroundType (video/image/gradient), cta + secondaryCta, gtmEvent, minHeight
|
||
- `TextBlock` — richText, align, maxWidth, backgroundColor
|
||
- `FeaturesBlock` — columns (2/3/4), items[icon/image/title/desc], backgroundColor
|
||
- `LocationCardBlock` — locations[name, slug, desc, image, badge, price, ctaLabel], showBuyButton
|
||
- `PricingBlock` — showTicketSelector toggle → TicketsConfig or customTable, location filter, showFreeCategories, note
|
||
- `GalleryBlock` — layout (grid/masonry/carousel), columns, images[], enableLightbox
|
||
- `FormBlock` — formType (birthday/group/callback/generic), **label** (relation to Labels), gtmEvent, successMessage, layout, sideImage
|
||
- `CTABlock` — title, desc, backgroundType (gradient/image/solid), buttons[], align
|
||
- `CountdownBlock` — targetDate, expiredMessage, show flags, backgroundColor, cta
|
||
- `BlogPreviewBlock` — source (blog/events/both), count, layout (cards/list/featured), moreLinkUrl
|
||
- `MapBlock` — embedUrl, mapHeight, transportInfo[], workingHours[], directionsUrl
|
||
- [x] `src/payload-types.ts` — generated (1874 lines), all interfaces verified
|
||
|
||
---
|
||
|
||
## Phase 3 — Middleware + UTM + Google Client ID ⏳ NEXT
|
||
|
||
**Goal:** Middleware captures UTM params and Google Client ID from every request, stores in httpOnly cookies.
|
||
|
||
### Files to create:
|
||
- [ ] `src/middleware.ts`
|
||
- [ ] `src/lib/utm.ts`
|
||
|
||
### `src/middleware.ts` spec:
|
||
```typescript
|
||
// Reads from URL: utm_source, utm_medium, utm_campaign, utm_content, utm_term
|
||
// Sets httpOnly cookies (30-day expiry) — only if present in query
|
||
// Does NOT overwrite existing cookies (first-touch attribution)
|
||
// Passes request through unchanged
|
||
// Matcher: excludes /api/*, /admin/*, /_next/*, /fonts/*, *.{ico,svg,png,jpg,webp}
|
||
```
|
||
|
||
### `src/lib/utm.ts` spec:
|
||
```typescript
|
||
// getUtmFromCookies(cookies: ReadonlyRequestCookies): UtmData
|
||
// Returns: { utmSource, utmMedium, utmCampaign, utmContent, utmTerm }
|
||
|
||
// getGoogleClientId(cookies: ReadonlyRequestCookies): string | null
|
||
// Reads `_ga` cookie, parses format: GA1.X.XXXXXXXXXX.XXXXXXXXXX → "XXXXXXXXXX.XXXXXXXXXX"
|
||
```
|
||
|
||
### Maintenance mode:
|
||
```typescript
|
||
// In middleware: read SiteSettings global from Payload
|
||
// If maintenanceMode === true AND request is not from authenticated admin
|
||
// → redirect to /maintenance
|
||
// Create src/app/(frontend)/maintenance/page.tsx
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 4 — Public Pages + BlockRenderer ⬜
|
||
|
||
**Goal:** All routes render CMS content via blocks.
|
||
|
||
### Files to create:
|
||
|
||
#### Core infrastructure:
|
||
- [ ] `src/components/blocks/BlockRenderer.tsx` — maps blockType → React component
|
||
- [ ] `src/components/blocks/HeroBlockComponent.tsx`
|
||
- [ ] `src/components/blocks/TextBlockComponent.tsx`
|
||
- [ ] `src/components/blocks/FeaturesBlockComponent.tsx`
|
||
- [ ] `src/components/blocks/LocationCardBlockComponent.tsx`
|
||
- [ ] `src/components/blocks/PricingBlockComponent.tsx`
|
||
- [ ] `src/components/blocks/GalleryBlockComponent.tsx`
|
||
- [ ] `src/components/blocks/FormBlockComponent.tsx`
|
||
- [ ] `src/components/blocks/CTABlockComponent.tsx`
|
||
- [ ] `src/components/blocks/CountdownBlockComponent.tsx`
|
||
- [ ] `src/components/blocks/BlogPreviewBlockComponent.tsx`
|
||
- [ ] `src/components/blocks/MapBlockComponent.tsx`
|
||
|
||
#### Layout:
|
||
- [ ] `src/components/layout/Header.tsx` — logo, Navigation global, headerCta button, mobile hamburger
|
||
- [ ] `src/components/layout/Footer.tsx` — columns from Navigation, socialLinks from SiteSettings
|
||
- [ ] `src/components/layout/MobileNav.tsx` — Framer Motion slide-out
|
||
- [ ] `src/components/layout/BinotelWidget.tsx` — script from SiteSettings.binotelWidgetHash
|
||
- [ ] `src/components/layout/GTMScript.tsx` — GTM snippet from SiteSettings.gtmId
|
||
- [ ] `src/components/layout/GA4Script.tsx` — gtag.js from SiteSettings.ga4MeasurementId
|
||
- [ ] `src/components/layout/UmamiScript.tsx` — from SiteSettings.umamiScriptUrl
|
||
- [ ] Update `src/app/(frontend)/layout.tsx` — import and render all above
|
||
|
||
#### Pages:
|
||
- [ ] `src/app/(frontend)/page.tsx` — home (slug: 'home')
|
||
- [ ] `src/app/(frontend)/dinopark/page.tsx`
|
||
- [ ] `src/app/(frontend)/dyvo-lis/page.tsx`
|
||
- [ ] `src/app/(frontend)/labiryn/page.tsx`
|
||
- [ ] `src/app/(frontend)/blog/page.tsx` — list with pagination + category filter
|
||
- [ ] `src/app/(frontend)/blog/[slug]/page.tsx` — Article structured data
|
||
- [ ] `src/app/(frontend)/events/page.tsx`
|
||
- [ ] `src/app/(frontend)/events/[slug]/page.tsx` — Event structured data
|
||
- [ ] `src/app/(frontend)/landing/[slug]/page.tsx` — sticky CTA bar
|
||
- [ ] `src/app/(frontend)/dni-narodzhennia/page.tsx`
|
||
- [ ] `src/app/(frontend)/grupovi-vidviduvannia/page.tsx`
|
||
- [ ] `src/app/(frontend)/[slug]/page.tsx` — catch-all CMS (MUST BE LAST)
|
||
- [ ] `src/app/(frontend)/not-found.tsx` — 404 with Шумік character
|
||
|
||
#### SEO:
|
||
- [ ] `src/app/sitemap.ts` — auto-generate from Pages + Blog + Events
|
||
- [ ] `src/app/robots.ts` — from SiteSettings.robotsTxt
|
||
- [ ] `generateMetadata()` in every page — from CMS SEO group + SiteSettings defaults
|
||
- [ ] Structured data: Organization, LocalBusiness, Article, Event schemas
|
||
|
||
---
|
||
|
||
## Phase 5 — Lead System + Binotel + Twenty CRM ⬜
|
||
|
||
**Goal:** Forms capture leads with labels, UTM, Google Client ID. Sync to Twenty CRM. Binotel tracking.
|
||
|
||
### Files to create:
|
||
- [ ] `src/components/forms/LeadForm.tsx` — 4 variants (birthday/group/callback/generic), RHF + Zod
|
||
- [ ] `src/components/forms/CallbackForm.tsx` — minimal (name + phone)
|
||
- [ ] `src/app/api/leads/route.ts` — POST: validate → create Lead → async Twenty sync → return success
|
||
- [ ] `src/lib/twenty.ts` — `syncPersonToTwenty(lead)` → POST to Twenty API, save twentyId
|
||
- [ ] `src/lib/binotel.ts` — `verifyWebhook()`, `matchCallToLead(phone)`
|
||
- [ ] `src/app/api/binotel/webhook/route.ts` — receive call data, match to Lead by phone
|
||
- [ ] `src/lib/gtm.ts` — `pushEvent(name, data)` → `window.dataLayer.push`
|
||
- [ ] `src/lib/ga4.ts` — `trackEvent(name, params)` → `window.gtag('event', ...)`
|
||
|
||
### Lead form API contract:
|
||
```typescript
|
||
POST /api/leads
|
||
Body: {
|
||
name: string
|
||
phone?: string
|
||
email?: string
|
||
message?: string
|
||
guestCount?: number
|
||
eventDate?: string
|
||
organization?: string
|
||
tag: 'birthday' | 'group' | 'callback' | 'generic'
|
||
labelId?: string // from FormBlock CMS config
|
||
}
|
||
// Middleware adds UTM + googleClientId from cookies automatically
|
||
```
|
||
|
||
### GTM events to fire:
|
||
- `lead_submit_birthday` — birthday form success
|
||
- `lead_submit_group` — group form success
|
||
- `lead_submit_callback` — callback form success
|
||
- `lead_submit_generic` — generic form success
|
||
|
||
---
|
||
|
||
## Phase 6 — Ticket Payment ⬜
|
||
|
||
**Goal:** Ticket selection → ezy.com.ua → Monobank payment → QR tickets via email.
|
||
|
||
### ezy.com.ua API:
|
||
```
|
||
GET https://www.ezy.com.ua/ipay/default/get-partner-tariff?activity={EZY_ACTIVITY_KEY}
|
||
POST https://www.ezy.com.ua/ipay/pay/partner-pay
|
||
Content-Type: multipart/form-data
|
||
Fields: partner_key, email, order (JSON)
|
||
Response: { url: "https://pay.monobank.ua/..." }
|
||
```
|
||
|
||
### Files to create:
|
||
- [ ] `src/lib/ezy.ts` — `getTariffs()` (5min cache), `createPayment({ email, order })`
|
||
- [ ] `src/app/(frontend)/payments/page.tsx` — 3-step checkout:
|
||
- Step 1: Ticket selector (categories from TicketsConfig, +/- counters, total)
|
||
- Step 2: Buyer data (name required, email required, phone)
|
||
- Step 3: Order review + pay button
|
||
- [ ] `src/app/api/tickets/create/route.ts` — POST: Order(PENDING) → ezy.createPayment() → { paymentUrl }
|
||
- [ ] `src/app/api/tickets/webhook/route.ts` — verify → Order.status=PAID → create Tickets → QR → email
|
||
- [ ] `src/app/api/tickets/verify/[code]/route.ts` — GET: check ticketCode → { valid, categoryName } + mark isUsed
|
||
- [ ] `src/lib/qr.ts` — QR = `https://shumiland.com.ua/api/tickets/verify/{ticketCode}`
|
||
- [ ] `src/lib/pdf.ts` — @react-pdf/renderer: logo, QR, ticket details, park address
|
||
- [ ] `src/lib/email.ts` — Nodemailer HTML email + PDF attachment
|
||
- [ ] `src/app/(frontend)/thank-you/page.tsx` — QR codes (qrcode.react), download PDF, Шумік celebrates
|
||
- [ ] GA4 `purchase` event on thank-you page
|
||
|
||
### Ticket code format: `SL-YYYYMMDD-XXXXXX` (e.g. `SL-20260402-A3K7F2`)
|
||
|
||
### Known tariff IDs from existing site:
|
||
- 3120, 3180, 3240 — Dinopark (adult/child/senior)
|
||
- 3300, 3360 — DyvoLis (adult/child)
|
||
- 3420 — Labiryn
|
||
|
||
---
|
||
|
||
## Phase 7 — Analytics + Employee Portal ⬜
|
||
|
||
**Goal:** Analytics working, admin dashboard, employee portal.
|
||
|
||
### Files to create:
|
||
- [ ] Wire GTM events to all components (hero_cta_click, ticket_cta_click, blog_view, etc.)
|
||
- [ ] `src/app/(frontend)/portal/page.tsx` — auth check, role-based card grid
|
||
- [ ] Optional: custom Payload admin view `/admin/analytics` with Recharts graphs
|
||
|
||
### GTM event map:
|
||
| Event | Trigger |
|
||
|-------|---------|
|
||
| `hero_cta_click` | HeroBlock CTA button |
|
||
| `ticket_cta_click` | PricingBlock / any ticket buy button |
|
||
| `purchase` | thank-you page load (totalAmount, items[]) |
|
||
| `lead_submit_{tag}` | LeadForm success |
|
||
| `blog_view` | Blog detail page load |
|
||
| `event_landing_view` | Landing page load |
|
||
| `landing_sticky_cta_click` | Sticky CTA on LandingPage |
|
||
|
||
### Employee Portal cards by role:
|
||
- **All roles**: Розклад, Контакти, Внутрішні ресурси
|
||
- **EDITOR+**: CMS Admin (/admin)
|
||
- **ADMIN+**: Metabase (bi.shumiland.com.ua), Umami (analytics.shumiland.com.ua), Twenty CRM (crm.shumiland.com.ua)
|
||
|
||
---
|
||
|
||
## Phase 8 — Polish + Seed + Docker ⬜
|
||
|
||
**Goal:** Production-ready, all edge cases covered.
|
||
|
||
### Files to create/update:
|
||
- [ ] `src/app/(frontend)/not-found.tsx` — 404 with Шумік (green fluffy character)
|
||
- [ ] `src/app/(frontend)/maintenance/page.tsx` — maintenance mode page
|
||
- [ ] `src/app/(frontend)/loading.tsx` — skeleton loaders
|
||
- [ ] Framer Motion animations: fade-in hero, stagger features, slide-in nav, hover gallery
|
||
- [ ] `src/seed.ts` — seed script:
|
||
- Default admin user (from .env)
|
||
- Homepage with 5 blocks (Hero, LocationCard, Features, BlogPreview, CTA)
|
||
- SiteSettings: Шуміленд, +38 (067) 123-45-67, address
|
||
- Navigation: header (Локації, Купити квиток, Блог, Контакти) + footer
|
||
- TicketsConfig: 7 categories (Dinopark ×3, DyvoLis ×2, Labiryn ×1, Combo ×1)
|
||
- Labels: birthday, group, callback, instagram, google-ads
|
||
- Sample blog post + event
|
||
- [ ] Performance: Payload select/depth optimization, Next.js Image, ISR revalidation
|
||
- homepage: 60s, blog: 300s, static pages: 3600s
|
||
- [ ] Security: rate limiting `/api/leads` and `/api/tickets/create`, webhook HMAC verification, CSP headers
|
||
- [ ] `Dockerfile` — multi-stage build, `output: standalone`
|
||
- [ ] Lighthouse audit: target 90+ on Performance, Accessibility, SEO
|
||
|
||
---
|
||
|
||
## Key Technical Decisions
|
||
|
||
| Decision | Choice | Reason |
|
||
|----------|--------|--------|
|
||
| CMS | Payload CMS 3.x (not TinaCMS) | Self-hosted, embedded in Next.js, non-tech admin UI, page builder |
|
||
| Next.js version | 15.4.11 pinned | 15.5.x incompatible with Payload peer deps |
|
||
| Database | PostgreSQL + Drizzle ORM | Payload's native ORM, no Prisma needed |
|
||
| Package type | `"type": "module"` | Required for Payload CLI ESM module resolution (tsx doesn't resolve @/ aliases) |
|
||
| Font | Montserrat Alternates + Rubik | Current site uses Alternates, not regular Montserrat |
|
||
| Payment | ezy.com.ua → Monobank | Actual provider extracted from existing site HTML |
|
||
| Form labels | Admin-configurable (Labels collection) | Track lead sources per campaign without code deploys |
|
||
| UTM tracking | First-touch, httpOnly cookies, 30 days | Standard attribution model |
|
||
| Google Client ID | From `_ga` cookie → saved in Lead/Order | Links CRM leads to GA4 sessions |
|
||
| Call tracking | Binotel widget + API webhooks | Widget hash: fgfz5owkoc9rxip2brp2 |
|
||
| Analytics | GTM (GTM-KJTSVLBC) + GA4 + Umami dual | GTM for tag management, Umami for privacy-friendly backup |
|
||
| Auth | Payload built-in (3 roles) | No external auth needed |
|
||
| i18n | None | Ukrainian only by decision |
|
||
|
||
---
|
||
|
||
## File Tree (current state)
|
||
|
||
```
|
||
src/
|
||
├── access/index.ts ✅
|
||
├── app/
|
||
│ ├── (frontend)/
|
||
│ │ ├── layout.tsx ✅ (fonts, lang="uk")
|
||
│ │ └── page.tsx ✅ (placeholder)
|
||
│ ├── (payload)/
|
||
│ │ └── admin/
|
||
│ │ ├── [[...segments]]/page.tsx ✅
|
||
│ │ └── importMap.js ✅
|
||
│ ├── globals.css ✅ (brand colors + fonts)
|
||
│ └── layout.tsx ✅
|
||
├── blocks/ ✅ ALL 11 DONE
|
||
│ ├── BlogPreviewBlock.ts, CTABlock.ts, CountdownBlock.ts
|
||
│ ├── FeaturesBlock.ts, FormBlock.ts, GalleryBlock.ts
|
||
│ ├── HeroBlock.ts, LocationCardBlock.ts, MapBlock.ts
|
||
│ ├── PricingBlock.ts, TextBlock.ts
|
||
├── collections/ ✅ ALL DONE
|
||
│ ├── Blog.ts, Events.ts, Labels.ts, LandingPages.ts
|
||
│ ├── Leads.ts, Media.ts, Orders.ts, Pages.ts
|
||
│ ├── Tickets.ts, Users.ts
|
||
├── components/ui/ ✅ shadcn components
|
||
│ ├── badge.tsx, button.tsx, card.tsx
|
||
│ ├── input.tsx, label.tsx, separator.tsx, textarea.tsx
|
||
├── globals/ ✅ ALL DONE
|
||
│ ├── Navigation.ts, SiteSettings.ts, TicketsConfig.ts
|
||
├── lib/utils.ts ✅
|
||
├── payload.config.ts ✅ all registered
|
||
└── payload-types.ts ✅ generated (1874 lines)
|
||
```
|
||
|
||
---
|
||
|
||
## Environment Variables Required
|
||
|
||
```bash
|
||
# Database
|
||
DATABASE_URL=postgresql://shumiland:shumiland@localhost:5432/shumiland
|
||
|
||
# Payload CMS
|
||
PAYLOAD_SECRET=your-secret-here
|
||
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
||
|
||
# ezy.com.ua (ticket payments)
|
||
EZY_ACTIVITY_KEY=your-activity-key
|
||
EZY_PARTNER_KEY=your-partner-key
|
||
|
||
# SMTP (email tickets)
|
||
SMTP_HOST=smtp.gmail.com
|
||
SMTP_PORT=587
|
||
SMTP_USER=tickets@shumiland.com.ua
|
||
SMTP_PASS=your-password
|
||
SMTP_FROM=Шуміленд <tickets@shumiland.com.ua>
|
||
|
||
# Analytics
|
||
NEXT_PUBLIC_GTM_ID=GTM-KJTSVLBC
|
||
NEXT_PUBLIC_GA4_ID=G-XXXXXXXXXX
|
||
|
||
# Binotel
|
||
NEXT_PUBLIC_BINOTEL_HASH=fgfz5owkoc9rxip2brp2
|
||
BINOTEL_API_KEY=your-key
|
||
|
||
# Umami
|
||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-uuid
|
||
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.shumiland.com.ua/script.js
|
||
|
||
# Twenty CRM
|
||
TWENTY_API_URL=http://localhost:3003
|
||
TWENTY_API_KEY=your-key
|
||
```
|