import { list } from "@keystone-6/core"; import { allowAll } from "@keystone-6/core/access"; import { text, password, timestamp, image, relationship, select, integer, checkbox } from "@keystone-6/core/fields"; import { document } from '@keystone-6/fields-document'; import { type Lists } from ".keystone/types"; export const lists: Lists = { // π€ ΠΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ User: list({ access: allowAll, fields: { name: text({ validation: { isRequired: true } }), email: text({ validation: { isRequired: true }, isIndexed: "unique" }), password: password({ validation: { isRequired: true } }), createdAt: timestamp({ defaultValue: { kind: "now" } }), }, }), System: list({ access: allowAll, ui: { label: 'System settings', description: 'ΠΠ»ΠΎΠ±Π°Π»ΡΠ½ΡΠ΅ Π½Π°ΡΡΡΠΎΠΉΠΊΠΈ ΡΠ°ΠΉΡΠ°', listView: { initialColumns: ['siteTitle', 'updatedAt'] }, }, fields: { siteTitle: text({ validation: { isRequired: true }, ui: { description: 'Title ΡΠ°ΠΉΡΠ° (metatitle / ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΠ΅ Π² ΡΠ°ΠΏΠΊΠ΅ ΠΈ Ρ.ΠΏ.)' }, }), linedUrl: text({ validation: { isRequired: true }, ui: { description: 'LinedIn url' }, }), headerLogo: image({ storage: 'local_images', ui: { description: 'ΠΠΎΠ³ΠΎΡΠΈΠΏ Π΄Π»Ρ Ρ Π΅Π΄Π΅ΡΠ° (ΡΠ²Π΅ΡΠ»ΡΠΉ/ΡΠ΅ΠΌΠ½ΡΠΉ Π²Π°ΡΠΈΠ°Π½Ρ ΠΏΠΎ ΠΌΠ°ΠΊΠ΅ΡΡ)' }, }), footerLogo: image({ storage: 'local_images', ui: { description: 'ΠΠΎΠ³ΠΎΡΠΈΠΏ Π΄Π»Ρ ΡΡΡΠ΅ΡΠ°' }, }), footerDisclaimer: document({ ui: { description: 'ΠΠΈΡΠΊΠ»Π΅ΠΉΠΌΠ΅Ρ Π² ΡΡΡΠ΅ΡΠ΅ (rich-text)' }, formatting: { headingLevels: [1, 2, 3, 4], inlineMarks: { bold: true, italic: true, underline: true, strikethrough: true, code: true, superscript: true, subscript: true, }, listTypes: { ordered: true, unordered: true }, alignment: { center: true, end: true }, blockTypes: { blockquote: true, code: true }, softBreaks: true, }, links: true, dividers: true, }), updatedAt: timestamp({ defaultValue: { kind: 'now' }, ui: { itemView: { fieldMode: 'read' } }, }), }, hooks: { async beforeOperation({ operation, context, listKey }) { // Π Π°Π·ΡΠ΅ΡΠ°Π΅ΠΌ ΡΠΎΠ»ΡΠΊΠΎ ΠΠΠΠ£ Π·Π°ΠΏΠΈΡΡ if (operation === 'create') { const count = await context.db[listKey].count({}); if (count > 0) throw new Error('ΠΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄Π½Ρ Π·Π°ΠΏΠΈΡΡ System'); } // ΠΠ°ΠΏΡΠ΅Ρ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ (ΡΡΠΎΠ±Ρ ΡΠ»ΡΡΠ°ΠΉΠ½ΠΎ Π½Π΅ ΠΏΠΎΡΠ΅ΡΡΡΡ Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΡΠ΅ Π½Π°ΡΡΡΠΎΠΉΠΊΠΈ) if (operation === 'delete') { throw new Error('ΠΠ΅Π»ΡΠ·Ρ ΡΠ΄Π°Π»ΠΈΡΡ System; ΠΈΠ·ΠΌΠ΅Π½ΠΈΡΠ΅ ΡΡΡΠ΅ΡΡΠ²ΡΡΡΡΡ Π·Π°ΠΏΠΈΡΡ.'); } }, async resolveInput({ operation, resolvedData }) { if (operation === 'update') { resolvedData.updatedAt = new Date().toISOString(); } return resolvedData; }, }, }), NavItem: list({ access: allowAll, ui: { label: 'Navigation', labelField: 'label', listView: { initialColumns: ['label', 'href', 'order', 'parent'] }, }, fields: { label: text({ validation: { isRequired: true } }), href: text({ validation: { isRequired: true }, ui: { description: 'ΠΠ°ΠΏΡΠΈΠΌΠ΅Ρ: /about, #contact, https://example.com' }, }), order: integer({ defaultValue: 0 }), isExternal: checkbox({ defaultValue: false, ui: { description: 'ΠΡΠΊΡΡΠ²Π°ΡΡ Π² Π½ΠΎΠ²ΠΎΠΉ Π²ΠΊΠ»Π°Π΄ΠΊΠ΅ (target=_blank)' }, }), enabled: checkbox({ defaultValue: true }), // Π΄ΡΠ΅Π²ΠΎΠ²ΠΈΠ΄Π½Π°Ρ ΡΡΡΡΠΊΡΡΡΠ° (ΠΏΠΎΠ΄ΠΌΠ΅Π½Ρ) parent: relationship({ ref: 'NavItem.children' }), children: relationship({ ref: 'NavItem.parent', many: true, ui: { displayMode: 'cards', cardFields: ['label', 'href', 'order', 'isExternal', 'enabled'], inlineCreate: { fields: ['label', 'href', 'order', 'isExternal', 'enabled'] }, inlineEdit: { fields: ['label', 'href', 'order', 'isExternal', 'enabled'] }, }, }), }, }), // π© Hero (ΠΊΠ°ΠΊ Π±ΡΠ»ΠΎ) HeroSection: list({ access: allowAll, ui: { label: "Hero Section", listView: { initialColumns: ["title"] } }, fields: { title: text({ validation: { isRequired: true }, ui: { description: "ΠΠ»Π°Π²Π½ΡΠΉ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΡΠ΅ΠΊΡΠΈΠΈ" } }), description: text({ ui: { displayMode: "textarea", description: "ΠΠΎΠ΄Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ / ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅" } }), backgroundImage: image({ storage: "local_images", ui: { description: "Π€ΠΎΠ½ΠΎΠ²Π°Ρ ΠΊΠ°ΡΡΠΈΠ½ΠΊΠ° (1360x640)" } }), buttonText: text({ ui: { description: "Π’Π΅ΠΊΡΡ ΠΊΠ½ΠΎΠΏΠΊΠΈ" } }), buttonLink: text({ ui: { description: "Π‘ΡΡΠ»ΠΊΠ° ΠΊΠ½ΠΎΠΏΠΊΠΈ (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ: /contact)" } }), }, hooks: { async beforeOperation({ operation, listKey, context }) { if (operation === "create") { const existing = await context.db[listKey].findMany({ take: 1 }); if (existing.length > 0) throw new Error("ΠΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄ΠΈΠ½ HeroSection"); } }, }, }), // π¨ Π£Π½ΠΈΠ²Π΅ΡΡΠ°Π»ΡΠ½Π°Ρ ΠΏΠ»ΠΈΡΠΊΠ° ΡΠ΅ΠΊΡΠΈΠΈ (ΠΈ Β«ΡΡΠ°ΡΠΈΡΡΠΈΠΊΠ°Β», ΠΈ Β«ΡΠΈΠ½Π°Π½ΡΡΒ») FinanceRow: list({ access: allowAll, ui: { label: "Finance Row", listView: { initialColumns: ["order", "value", "label", "trend"] }, }, fields: { value: text({ validation: { isRequired: true } }), // Π½Π°ΠΏΡ. "$2,05 bln" ΠΈΠ»ΠΈ "5 mln" label: text({ validation: { isRequired: true } }), // ΠΏΠΎΠ΄ΠΏΠΈΡΡ ΠΏΠΎΠ΄ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ΠΌ trend: select({ type: "enum", options: [ { label: "ΠΠ΅Ρ ΡΡΡΠ΅Π»ΠΊΠΈ", value: "none" }, { label: "ΠΠ²Π΅ΡΡ ", value: "up" }, { label: "ΠΠ½ΠΈΠ·", value: "down" }, ], defaultValue: "none", ui: { displayMode: "segmented-control" }, }), order: integer({ defaultValue: 0 }), // Π΄Π»Ρ ΡΠΎΡΡΠΈΡΠΎΠ²ΠΊΠΈ ΠΈ Β«Π±ΠΈΡΡ ΠΏΠΎ 3Β» section: relationship({ ref: "ScreenSecondSection.rows" }), }, }), // π¦ ΠΡΠΎΡΠ°Ρ ΡΠ΅ΠΊΡΠΈΡ (About/Overview) β singleton ScreenSecondSection: list({ access: allowAll, ui: { label: "Screen Second", listView: { initialColumns: ["title"] } }, fields: { title: text({ validation: { isRequired: true }, ui: { description: "ΠΠ°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΡΠ΅ΠΊΡΠΈΠΈ (About Freedom Holding Corp)" }, }), description: text({ ui: { displayMode: "textarea", description: "ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ ΠΏΠΎΠ΄ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΎΠΌ" }, }), logo: image({ storage: "local_images", ui: { description: "ΠΠΎΠ³ΠΎΡΠΈΠΏ ΡΠΏΡΠ°Π²Π°" } }), rows: relationship({ ref: "FinanceRow.section", many: true, ui: { displayMode: "cards", cardFields: ["value", "label", "trend", "order"], inlineCreate: { fields: ["value", "label", "trend", "order"] }, inlineEdit: { fields: ["value", "label", "trend", "order"] }, linkToItem: true, }, }), footnote: text({ ui: { description: "Π‘Π½ΠΎΡΠΊΠ° (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ: * for the year ended ...)" } }), }, hooks: { async beforeOperation({ operation, listKey, context }) { if (operation === "create") { const existing = await context.db[listKey].findMany({ take: 1 }); if (existing.length > 0) throw new Error("ΠΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄ΠΈΠ½ ScreenSecondSection"); } }, }, }), ScreenThreeStatCard: list({ access: allowAll, ui: { label: 'Screen3 β’ Stat Card', listView: { initialColumns: ['order', 'value', 'label', 'color'] }, }, fields: { value: text({ validation: { isRequired: true } }), // Π½Π°ΠΏΡ. "USD ~ 10.8 bn*" label: text({ validation: { isRequired: true } }), // "Current market capitalization" color: text({ ui: { description: 'HEX ΡΠ²Π΅Ρ Π·Π½Π°ΡΠ΅Π½ΠΈΡ (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ #16a34a). ΠΠΎΠΆΠ½ΠΎ ΠΎΡΡΠ°Π²ΠΈΡΡ ΠΏΡΡΡΡΠΌ.' }, }), order: integer({ defaultValue: 0 }), section: relationship({ ref: 'ScreenThreeSection.statCards' }), }, }), /* π¨ ΠΠ»Π΅ΠΌΠ΅Π½Ρ ΡΠΏΠΈΡΠΊΠ° ΠΈΠ½Π΄Π΅ΠΊΡΠΎΠ² (bullet) */ ScreenThreeIndex: list({ access: allowAll, ui: { label: 'Screen3 β’ Index Item', listView: { initialColumns: ['order', 'text'] }, }, fields: { text: text({ validation: { isRequired: true } }), // Π½Π°ΠΏΡ. "MSCI U.S. Small Cap 1750" order: integer({ defaultValue: 0 }), section: relationship({ ref: 'ScreenThreeSection.indexes' }), }, }), /* π¦ Π‘Π΅ΠΊΡΠΈΡ Financial performance (singleton) */ ScreenThreeSection: list({ access: allowAll, ui: { label: 'Screen Three (Financial performance)', listView: { initialColumns: ['title'] }, }, fields: { title: text({ validation: { isRequired: true }, ui: { description: 'ΠΠ°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΡΠ΅ΠΊΡΠΈΠΈ (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ: Financial performance)' }, }), description: text({ ui: { displayMode: 'textarea', description: 'ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ ΠΏΠΎΠ΄ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΎΠΌ' }, }), // ΠΠ°ΡΡΠΎΡΠΊΠΈ ΡΠΏΡΠ°Π²Π° (Π΄Π²Π΅ Ρ ΡΠ΅Π±Ρ Π² ΠΌΠ°ΠΊΠ΅ΡΠ΅, Π½ΠΎ ΠΌΠΎΠΆΠ½ΠΎ Π±ΠΎΠ»ΡΡΠ΅/ΠΌΠ΅Π½ΡΡΠ΅) statCards: relationship({ ref: 'ScreenThreeStatCard.section', many: true, ui: { displayMode: 'cards', cardFields: ['value', 'label', 'color', 'order'], inlineCreate: { fields: ['value', 'label', 'color', 'order'] }, inlineEdit: { fields: ['value', 'label', 'color', 'order'] }, linkToItem: true, }, }), // Π‘ΠΏΠΈΡΠΎΠΊ ΠΈΠ½Π΄Π΅ΠΊΡΠΎΠ² (Π±ΡΠ»Π»ΠΈΡΡ) indexes: relationship({ ref: 'ScreenThreeIndex.section', many: true, ui: { displayMode: 'cards', cardFields: ['text', 'order'], inlineCreate: { fields: ['text', 'order'] }, inlineEdit: { fields: ['text', 'order'] }, linkToItem: true, }, }), footnote: text({ ui: { description: 'Π‘Π½ΠΎΡΠΊΠ° ΠΏΠΎΠ΄ ΡΠ΅ΠΊΡΠΈΠ΅ΠΉ (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ: * Based on the price ...)' }, }), }, hooks: { // singleton: ΡΠ°Π·ΡΠ΅ΡΠ°Π΅ΠΌ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄Π½Ρ Π·Π°ΠΏΠΈΡΡ async beforeOperation({ operation, listKey, context }) { if (operation === 'create') { const existing = await context.db[listKey].findMany({ take: 1 }); if (existing.length > 0) { throw new Error('ΠΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄Π½Ρ ScreenThreeSection'); } } }, }, }), KeyBusinessSegmentsSection: list({ access: allowAll, ui: { label: "Key Business Segments (Section)", listView: { initialColumns: ["title"] } }, fields: { title: text({ validation: { isRequired: true } }), segments: relationship({ ref: "KeySegment.section", many: true, ui: { displayMode: "cards", cardFields: ["name", "key", "order", "logo"], inlineCreate: { fields: ["name", "key", "logo", "order"] }, inlineEdit: { fields: ["name", "key", "logo", "order"] }, linkToItem: true, }, }), }, hooks: { async beforeOperation({ operation, listKey, context }) { if (operation === "create") { const exists = await context.db[listKey].findMany({ take: 1 }); if (exists.length) throw new Error("ΠΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄ΠΈΠ½ KeyBusinessSegmentsSection"); } }, }, }), /* Segment */ KeySegment: list({ access: allowAll, ui: { label: "Key Segment", listView: { initialColumns: ["order", "name", "key"] } }, fields: { key: text({ isIndexed: 'unique', ui: { description: "ΠΠΎΠ΄: broker/bank/telecomβ¦" } }), name: text({ validation: { isRequired: true } }), order: integer({ defaultValue: 0 }), logo: image({ storage: 'local_images' }), section: relationship({ ref: "KeyBusinessSegmentsSection.segments" }), info: relationship({ ref: "KeySegmentInfo.segment", many: true, ui: { displayMode: "cards", cardFields: ["title", "order"], inlineCreate: { fields: ["title", "order", "brandLink", "brandLogo"] }, inlineEdit: { fields: ["title", "order", "brandLink", "brandLogo"] }, linkToItem: true, }, }), }, }), /* Info card (column block) */ KeySegmentInfo: list({ access: allowAll, ui: { label: "Key Segment β’ Info Block", listView: { initialColumns: ["order", "title"] } }, fields: { title: text({ validation: { isRequired: true } }), order: integer({ defaultValue: 0 }), segment: relationship({ ref: "KeySegment.info" }), // Π±ΡΠ΅Π½Π΄ ΠΏΡΠΈΠ½Π°Π΄Π»Π΅ΠΆΠΈΡ ΡΠ΅Π»ΠΎΠΌΡ Π±Π»ΠΎΠΊΡ brandLink: text({ ui: { description: "Π‘ΡΡΠ»ΠΊΠ° Π±ΡΠ΅Π½Π΄Π° (ΠΎΠΏΡ.)" } }), brandLogo: image({ storage: 'local_images', ui: { description: "ΠΠΎΠ³ΠΎ Π±ΡΠ΅Π½Π΄Π° (ΠΎΠΏΡ.)" } }), items: relationship({ ref: "KeySegmentInfoItem.info", many: true, ui: { displayMode: "cards", cardFields: ["text", "order", "infoLogo", "colSpan"], inlineCreate: { fields: ["text", "infoLogo", "colSpan", "order"] }, inlineEdit: { fields: ["text", "infoLogo", "colSpan", "order"] }, linkToItem: true, }, }), }, }), /* Info list item (ΡΠΎΠ»ΡΠΊΠΎ ΡΠ΅ΠΊΡΡ + Π»ΠΎΠ³ΠΎ) */ KeySegmentInfoItem: list({ access: allowAll, ui: { label: "Key Segment β’ Info Item", listView: { initialColumns: ["order", "text", "infoLogo", "colSpan"] } }, fields: { text: text({ validation: { isRequired: true } }), infoLogo: image({ storage: 'local_images', ui: { description: "ΠΠΎΠ³ΠΎ Π² Π·Π΅Π»ΡΠ½ΠΎΠΌ ΠΊΠ²Π°Π΄ΡΠ°ΡΠΈΠΊΠ΅" } }), colSpan: select({ type: 'enum', options: [ { label: 'Normal (1 column)', value: 'ONE' }, { label: 'Wide (2 columns)', value: 'TWO' }, ], defaultValue: 'ONE', ui: { displayMode: 'segmented-control' }, }), order: integer({ defaultValue: 0 }), info: relationship({ ref: "KeySegmentInfo.items" }), }, }), WhyChooseUsItem: list({ access: allowAll, ui: { label: 'Why Choose Us β Item', listView: { initialColumns: ['text', 'order', 'section'] }, }, fields: { text: text({ validation: { isRequired: true }, ui: { displayMode: 'textarea' }, }), order: integer({ defaultValue: 0, ui: { description: 'Π‘ΠΎΡΡΠΈΡΠΎΠ²ΠΊΠ° ΠΏΠΎ Π²ΠΎΠ·ΡΠ°ΡΡΠ°Π½ΠΈΡ' }, }), section: relationship({ ref: 'WhyChooseUsSection.items' }), createdAt: timestamp({ defaultValue: { kind: 'now' } }), }, }), WhyChooseUsSection: list({ access: allowAll, ui: { label: 'Why Choose Us (section)', listView: { initialColumns: ['title'] }, }, fields: { // ΠΠ°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΡΠ΅ΠΊΡΠΈΠΈ ("Why Choose Us") title: text({ defaultValue: 'Why Choose Us' }), // ΠΠ΅Π²Π°Ρ ΠΊΠ°ΡΡΠΎΡΠΊΠ° leftTitle: text({ validation: { isRequired: true }, ui: { description: 'Π’Π΅ΠΊΡΡ Π² Π»Π΅Π²ΠΎΠΉ ΠΊΠ°ΡΡΠΎΡΠΊΠ΅' }, }), leftBackground: image({ storage: 'local_images', ui: { description: 'Π€ΠΎΠ½ Π΄Π»Ρ Π»Π΅Π²ΠΎΠΉ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ Backgroung_2.png)' }, }), ctaText: text({ defaultValue: 'Partner with us' }), ctaLink: text({ ui: { description: 'Π‘ΡΡΠ»ΠΊΠ° Π΄Π»Ρ ΠΊΠ½ΠΎΠΏΠΊΠΈ (ΠΎΠΏΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎ)' } }), // ΠΡΠ°Π²Π°Ρ ΠΊΠΎΠ»ΠΎΠ½ΠΊΠ°: ΠΏΡΠ½ΠΊΡΡ (ΠΌΠ½ΠΎΠ³ΠΎ) items: relationship({ ref: 'WhyChooseUsItem.section', many: true, ui: { displayMode: 'cards', cardFields: ['text', 'order'], inlineCreate: { fields: ['text', 'order'] }, inlineEdit: { fields: ['text', 'order'] }, linkToItem: true, }, }), } }), InnovationSection: list({ access: allowAll, ui: { label: 'Innovation (section)', listView: { initialColumns: ['title', 'updatedAt'], }, }, fields: { // ΠΠ°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΡΠ΅ΠΊΡΠΈΠΈ title: text({ defaultValue: 'Innovation at the intersection of telecom and fintech', ui: { description: 'ΠΠ°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ Π½Π°Π΄ ΡΠ΅ΡΠΊΠΎΠΉ ΠΊΠ°ΡΡΠΎΡΠ΅ΠΊ' }, }), // ΠΠΠ Π₯: Π»Π΅Π²Π°Ρ ΠΊΠ°ΡΡΠΎΡΠΊΠ° (ΡΠ΅ΠΊΡΡ + ΠΊΠ°ΡΡΠΈΠ½ΠΊΠ° ΡΠΏΡΠ°Π²Π°) topLeftTitle: text({ validation: { isRequired: true }, ui: { description: 'ΠΠ°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ Π»Π΅Π²ΠΎΠΉ Π²Π΅ΡΡ Π½Π΅ΠΉ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ' }, }), topLeftText: text({ ui: { displayMode: 'textarea', description: 'Π’Π΅ΠΊΡΡ Π»Π΅Π²ΠΎΠΉ Π²Π΅ΡΡ Π½Π΅ΠΉ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ' }, }), topLeftImage: image({ storage: 'local_images', ui: { description: 'ΠΠ°ΡΡΠΈΠ½ΠΊΠ° Π² ΠΏΡΠ°Π²ΠΎΠΉ ΡΠ°ΡΡΠΈ Π»Π΅Π²ΠΎΠΉ Π²Π΅ΡΡ Π½Π΅ΠΉ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ' }, }), // ΠΠΠ Π₯: ΠΏΡΠ°Π²Π°Ρ Π·Π΅Π»ΡΠ½Π°Ρ ΠΊΠ°ΡΡΠΎΡΠΊΠ° topRightTitle: text({ validation: { isRequired: true }, ui: { description: 'ΠΠ°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΠΏΡΠ°Π²ΠΎΠΉ Π²Π΅ΡΡ Π½Π΅ΠΉ Π·Π΅Π»ΡΠ½ΠΎΠΉ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ' }, }), topRightText: text({ ui: { displayMode: 'textarea', description: 'Π’Π΅ΠΊΡΡ ΠΏΡΠ°Π²ΠΎΠΉ Π²Π΅ΡΡ Π½Π΅ΠΉ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ' }, }), // ΠΠΠ: Π΄Π²Π΅ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ bottom1Title: text({ ui: { description: 'ΠΠ°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ Π»Π΅Π²ΠΎΠΉ Π½ΠΈΠΆΠ½Π΅ΠΉ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ' } }), bottom1Text: text({ ui: { displayMode: 'textarea', description: 'Π’Π΅ΠΊΡΡ Π»Π΅Π²ΠΎΠΉ Π½ΠΈΠΆΠ½Π΅ΠΉ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ' }, }), bottom2Title: text({ ui: { description: 'ΠΠ°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΠΏΡΠ°Π²ΠΎΠΉ Π½ΠΈΠΆΠ½Π΅ΠΉ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ' } }), bottom2Text: text({ ui: { displayMode: 'textarea', description: 'Π’Π΅ΠΊΡΡ ΠΏΡΠ°Π²ΠΎΠΉ Π½ΠΈΠΆΠ½Π΅ΠΉ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ' }, }), // ΡΠ΅Ρ .ΠΏΠΎΠ»Ρ createdAt: timestamp({ defaultValue: { kind: 'now' } }), updatedAt: timestamp({ db: { updatedAt: true }, }), }, // Singleton: ΡΠ°Π·ΡΠ΅ΡΠ°Π΅ΠΌ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄Π½Ρ Π·Π°ΠΏΠΈΡΡ hooks: { async beforeOperation({ operation, listKey, context }) { if (operation === 'create') { const existing = await context.db[listKey].findMany({ where: {} }); if (existing.length > 0) { throw new Error('ΠΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄ΠΈΠ½ InnovationSection'); } } }, }, }), SolutionsSection : list({ access: allowAll, ui: { label: 'Solutions (Section)', listView: { initialColumns: ['title'] }, }, fields: { title: text({ ui: { description: 'ΠΠ°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΡΠ΅ΠΊΡΠΈΠΈ (Π½Π° ΡΡΠΎΠ½ΡΠ΅ "Solutions")' }, defaultValue: 'Solutions', }), buttonText: text({ ui: { description: 'Π’Π΅ΠΊΡΡ ΠΊΠ½ΠΎΠΏΠΊΠΈ Π²Π½ΠΈΠ·Ρ (CTA)' }, defaultValue: 'Partner with us', }), buttonHref: text({ validation: { isRequired: false }, ui: { description: "ΠΡΠ΄Π° Π²Π΅Π΄ΡΡ ΠΊΠ½ΠΎΠΏΠΊΠ° 'Partner with us'" }, }), // ΠΌΠ°ΡΡΠΈΠ² ΠΊΠ°ΡΡΠΎΡΠ΅ΠΊ items: relationship({ ref: 'SolutionItem.section', many: true, ui: { displayMode: 'cards', cardFields: ['title', 'order', 'image'], inlineCreate: { fields: ['title', 'text', 'href', 'image', 'order'] }, inlineEdit: { fields: ['title', 'text', 'href', 'image', 'order'] }, linkToItem: true, inlineConnect: true, }, }), }, hooks: { // ΠΠ³ΡΠ°Π½ΠΈΡΠΈΠ²Π°Π΅ΠΌ Π΄ΠΎ ΠΎΠ΄Π½ΠΎΠΉ Π·Π°ΠΏΠΈΡΠΈ async beforeOperation({ operation, listKey, context }) { if (operation === 'create') { const existing = await context.db[listKey].findMany({ take: 1 }); if (existing.length) throw new Error('ΠΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄ΠΈΠ½ SolutionsSection'); } }, }, }), SolutionItem: list({ access: allowAll, ui: { label: 'Solutions β Item', listView: { initialColumns: ['title', 'order'] }, }, fields: { title: text({ validation: { isRequired: true } }), text: text({ ui: { displayMode: 'textarea' } }), href: text({ ui: { description: 'ΠΠΏΡΠΈΠΎΠ½Π°Π»ΡΠ½Π°Ρ ΡΡΡΠ»ΠΊΠ° "Learn more"' } }), image: image({ storage: 'local_images', ui: { description: 'Π€ΠΎΠ½ΠΎΠ²Π°Ρ ΠΊΠ°ΡΡΠΈΠ½ΠΊΠ° ΠΊΠ°ΡΡΠΎΡΠΊΠΈ' }, }), order: integer({ defaultValue: 0, ui: { description: 'ΠΠΎΡΡΠ΄ΠΎΠΊ ΡΠΎΡΡΠΈΡΠΎΠ²ΠΊΠΈ (ΡΠ΅ΠΌ ΠΌΠ΅Π½ΡΡΠ΅, ΡΠ΅ΠΌ Π²ΡΡΠ΅)' }, }), // ΠΎΠ±ΡΠ°ΡΠ½Π°Ρ ΡΠ²ΡΠ·Ρ ΠΊ ΡΠ΅ΠΊΡΠΈΠΈ section: relationship({ ref: 'SolutionsSection.items', ui: { hideCreate: true }, }), }, }), CountriesMapSection: list({ access: allowAll, ui: { label: 'Countries Map (Section)', listView: { initialColumns: ['title'] }, }, fields: { title: text({ ui: { description: 'ΠΠΏΡΠΈΠΎΠ½Π°Π»ΡΠ½ΡΠΉ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΡΠ΅ΠΊΡΠΈΠΈ' }, }), countries: relationship({ ref: 'MapCountry.section', many: true, ui: { displayMode: 'cards', cardFields: ['code', 'name', 'isEnabled', 'flag'], inlineCreate: { fields: ['code', 'name', 'isEnabled', 'flag'] }, inlineEdit: { fields: ['name', 'isEnabled', 'flag'] }, linkToItem: true, }, }), }, hooks: { async beforeOperation({ operation, listKey, context }) { if (operation === 'create') { const existing = await context.db[listKey].findMany({ where: {} }); if (existing.length > 0) { throw new Error('ΠΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄Π½Ρ CountriesMapSection'); } } }, }, }), /* === Π‘ΡΡΠ°Π½Π° Π½Π° ΠΊΠ°ΡΡΠ΅ === */ MapCountry: list({ access: allowAll, ui: { label: 'Countries', listView: { initialColumns: ['code', 'name', 'isEnabled'] }, }, fields: { section: relationship({ ref: 'CountriesMapSection.countries', ui: { hideCreate: true }, }), code: select({ type: 'enum', // value β ΡΠΎ, ΡΡΠΎ ΡΠ²ΠΈΠ΄ΠΈΡ ΡΡΠΎΠ½Ρ; label β ΡΠ΄ΠΎΠ±ΠΎΡΠΈΡΠ°Π΅ΠΌΠΎΠ΅ ΠΈΠΌΡ Π² Π°Π΄ΠΌΠΈΠ½ΠΊΠ΅ options: [ { label: 'United States of America', value: 'usa' }, { label: 'Kazakhstan', value: 'kaz' }, { label: 'Uzbekistan', value: 'uzb' }, { label: 'Kyrgyzstan', value: 'kgz' }, { label: 'Tajikistan', value: 'tjk' }, { label: 'Turkey', value: 'tur' }, { label: 'Cyprus', value: 'cyp' }, { label: 'Armenia', value: 'arm' }, { label: 'United Arab Emirates', value: 'uae' }, ], validation: { isRequired: true }, defaultValue: 'kaz', ui: { description: 'ΠΠΎΠ΄ ΡΡΡΠ°Π½Ρ (ΡΠΈΠΊΡΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ Π½Π°Π±ΠΎΡ Π΄Π»Ρ ΡΡΠΎΠ½ΡΠ°)' }, isIndexed: 'unique', // ΠΎΠ΄Π½Π° Π·Π°ΠΏΠΈΡΡ Π½Π° ΠΊΠΎΠ΄ }), name: text({ validation: { isRequired: true }, ui: { description: 'ΠΠΎΠ΄ΠΏΠΈΡΡ Π½Π° ΠΊΠ°ΡΡΠΎΡΠΊΠ΅ (ΠΌΠΎΠΆΠ½ΠΎ Π»ΠΎΠΊΠ°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ)' }, }), isEnabled: checkbox({ defaultValue: true, ui: { description: 'ΠΠΎΠΊΠ°Π·ΡΠ²Π°ΡΡ ΡΡΡΠ°Π½Ρ Π½Π° ΠΊΠ°ΡΡΠ΅' }, }), flag: image({ storage: 'local_images', ui: { description: 'ΠΠΊΠΎΠ½ΠΊΠ° ΡΠ»Π°Π³Π° (SVG/PNG), Π±ΡΠ΄Π΅Ρ Π²ΠΌΠ΅ΡΡΠΎ Π·Π΅Π»ΡΠ½ΠΎΠΉ ΡΠΎΡΠΊΠΈ' }, }), items: relationship({ ref: 'MapCountryItem.country', many: true, ui: { displayMode: 'cards', cardFields: ['text', 'order'], inlineCreate: { fields: ['text', 'order'] }, inlineEdit: { fields: ['text', 'order'] }, linkToItem: false, }, }), order: integer({ defaultValue: 0, ui: { description: 'ΠΡΠΎΠΈΠ·Π²ΠΎΠ»ΡΠ½Π°Ρ ΡΠΎΡΡΠΈΡΠΎΠ²ΠΊΠ° ΡΡΡΠ°Π½ (ΠΌΠ΅Π½ΡΡΠ΅ β ΡΠ°Π½ΡΡΠ΅)' }, }), }, }), /* === ΠΡΠ½ΠΊΡΡ Π²Π½ΡΡΡΠΈ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ ΡΡΡΠ°Π½Ρ === */ MapCountryItem: list({ access: allowAll, ui: { label: 'Country Items', isHidden: false, listView: { initialColumns: ['country', 'text', 'order'] }, }, fields: { country: relationship({ ref: 'MapCountry.items', ui: { hideCreate: true }, }), text: text({ validation: { isRequired: true }, ui: { description: 'Π‘ΡΡΠΎΠΊΠ° Π² ΡΠΏΠΈΡΠΊΠ΅ (bullet)' }, }), order: integer({ defaultValue: 0, ui: { description: 'ΠΠΎΡΡΠ΄ΠΎΠΊ Π²Π½ΡΡΡΠΈ ΠΊΠ°ΡΡΠΎΡΠΊΠΈ' }, }), }, }), ContactRequest: list({ access: { // Π‘ΠΎΠ·Π΄Π°Π²Π°ΡΡ ΠΌΠΎΠΆΠ΅Ρ ΠΊΡΠΎ ΡΠ³ΠΎΠ΄Π½ΠΎ, ΡΠΈΡΠ°ΡΡ/ΠΏΡΠ°Π²ΠΈΡΡ β ΡΠΎΠ»ΡΠΊΠΎ Π°Π²ΡΠΎΡΠΈΠ·ΠΎΠ²Π°Π½Π½ΡΠ΅ (Π°Π΄ΠΌΠΈΠ½ΠΊΠ°) operation: { query: ({ session }) => !!session, create: allowAll, update: ({ session }) => !!session, delete: ({ session }) => !!session, }, }, ui: { label: 'Contact requests', listView: { initialColumns: ['createdAt', 'name', 'email', 'company', 'message', 'countryCode'], initialSort: { field: 'createdAt', direction: 'DESC' }, }, }, fields: { name: text({ validation: { isRequired: true } }), company: text(), message: text(), position: text(), email: text({ validation: { isRequired: true } }), phone: text(), countryCode: text({ ui: { description: 'ISO-ΠΊΠΎΠ΄ ΡΡΡΠ°Π½Ρ (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, US)' } }), createdAt: timestamp({ defaultValue: { kind: 'now' } }), }, hooks: { // ΠΏΠΎΡΠ»Π΅ ΡΡΠΏΠ΅ΡΠ½ΠΎΠ³ΠΎ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ β ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅ΠΌ ΠΏΠΈΡΡΠΌΠΎ afterOperation: async ({ operation, item }) => { if (operation !== 'create' || !item) return; const { SMTP_HOST, SMTP_PORT = '587', SMTP_USER, SMTP_PASS, SMTP_SECURE = 'false', MAIL_FROM = 'no-reply@example.com', CONTACT_TO = MAIL_FROM, } = process.env; if (!SMTP_HOST || !CONTACT_TO) { console.warn('[ContactRequest] SMTP not configured β skip email.'); return; } const transporter = nodemailer.createTransport({ host: SMTP_HOST, port: Number(SMTP_PORT), secure: SMTP_SECURE === 'true', auth: SMTP_USER ? { user: SMTP_USER, pass: SMTP_PASS } : undefined, }); const html = `