- B1: Next.js 15 + Payload CMS 3.0 + Postgres 16, ESLint, Prettier, Husky, Vitest - B2: 9 collections, 6 globals, 12 Page Builder blocks, access control, slugify/revalidate hooks - B3: ezy.com.ua payments, Binotel HMAC webhook, leads API, Telegram bot, Resend email, rate limiting - B4: Tariffs collection with ezy API sync (cron + manual), dynamic pricing source-of-truth - B5: 13 test files covering unit libs and all API routes - B6: Dockerfile multi-stage, docker-compose.prod.yml, nginx.conf SSL, GitHub Actions CI/CD, health endpoint - B7: docs/admin-guide-ua.md (marketer guide), docs/deploy.md (VPS instructions), README quickstart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
78 lines
2.4 KiB
TypeScript
78 lines
2.4 KiB
TypeScript
import type { CollectionConfig, FieldAccess } from 'payload'
|
||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||
import { isAdmin } from '@/access/isAdmin'
|
||
|
||
const adminFieldAccess: FieldAccess = ({ req: { user } }) => user?.role === 'admin'
|
||
|
||
export const Tariffs: CollectionConfig = {
|
||
slug: 'tariffs',
|
||
admin: {
|
||
useAsTitle: 'last_synced_name',
|
||
defaultColumns: [
|
||
'last_synced_name',
|
||
'display_name',
|
||
'category_tag',
|
||
'last_synced_price',
|
||
'visible',
|
||
],
|
||
},
|
||
access: {
|
||
read: () => true,
|
||
create: isAdmin,
|
||
update: isAdmin,
|
||
delete: isAdmin,
|
||
},
|
||
fields: [
|
||
{
|
||
name: 'ezy_id',
|
||
type: 'number',
|
||
required: true,
|
||
unique: true,
|
||
index: true,
|
||
admin: {
|
||
readOnly: true,
|
||
description: 'Встановлюється автоматично при синхронізації з ezy API',
|
||
},
|
||
access: { update: adminFieldAccess },
|
||
},
|
||
{ name: 'display_name', type: 'text', label: 'Назва для сайту (перевизначення)' },
|
||
{ name: 'description', type: 'richText', editor: lexicalEditor({}) },
|
||
{ name: 'image', type: 'upload', relationTo: 'media' },
|
||
{ name: 'icon', type: 'text', label: 'Emoji або назва іконки' },
|
||
{
|
||
name: 'category_tag',
|
||
type: 'select',
|
||
required: true,
|
||
options: [
|
||
{ label: 'Dyno', value: 'dyno' },
|
||
{ label: 'Dyvolis', value: 'dyvolis' },
|
||
{ label: 'Maze', value: 'maze' },
|
||
{ label: 'Combo', value: 'combo' },
|
||
{ label: 'Family', value: 'family' },
|
||
],
|
||
},
|
||
{ name: 'sort', type: 'number', defaultValue: 0, admin: { position: 'sidebar' } },
|
||
{ name: 'visible', type: 'checkbox', defaultValue: true, admin: { position: 'sidebar' } },
|
||
{
|
||
name: 'last_synced_name',
|
||
type: 'text',
|
||
label: 'Назва з ezy API',
|
||
admin: { readOnly: true, description: 'Оновлюється автоматично при синхронізації' },
|
||
access: { update: adminFieldAccess },
|
||
},
|
||
{
|
||
name: 'last_synced_price',
|
||
type: 'number',
|
||
label: 'Ціна з ezy API (грн)',
|
||
admin: { readOnly: true },
|
||
access: { update: adminFieldAccess },
|
||
},
|
||
{
|
||
name: 'last_synced_at',
|
||
type: 'date',
|
||
label: 'Дата синхронізації',
|
||
admin: { readOnly: true, date: { pickerAppearance: 'dayAndTime' } },
|
||
access: { update: adminFieldAccess },
|
||
},
|
||
],
|
||
}
|