build: production build with Phase 1 2025 Bible Reader implementation complete

Includes all Phase 1 features:
- Search-first navigation with auto-complete
- Responsive reading interface (desktop/tablet/mobile)
- 4 customization presets + full fine-tuning controls
- Layered details panel with notes, bookmarks, highlights
- Smart offline caching with IndexedDB and auto-sync
- Full accessibility (WCAG 2.1 AA)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-11 20:38:01 +00:00
parent b8652b9f0a
commit 9b5c0ed8bb
50 changed files with 20146 additions and 859 deletions

View File

@@ -0,0 +1,63 @@
import { CollectionConfig } from 'payload';
export const BibleBooks: CollectionConfig = {
slug: 'bible-books',
admin: {
useAsTitle: 'name',
defaultColumns: ['name', 'abbreviation', 'testament', 'chapterCount', 'order'],
group: 'Bible Content',
},
fields: [
{
name: 'bookId',
type: 'number',
required: true,
unique: true,
index: true,
},
{
name: 'name',
type: 'text',
required: true,
localized: true,
index: true,
},
{
name: 'abbreviation',
type: 'text',
required: true,
index: true,
},
{
name: 'testament',
type: 'select',
options: [
{ label: 'Old Testament', value: 'OT' },
{ label: 'New Testament', value: 'NT' },
],
required: true,
index: true,
},
{
name: 'chapterCount',
type: 'number',
required: true,
min: 1,
},
{
name: 'order',
type: 'number',
required: true,
index: true,
admin: {
description: 'Display order in Bible',
},
},
],
access: {
read: () => true,
create: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
update: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
delete: ({ req }) => req.user?.role === 'super-admin',
},
};

View File

@@ -0,0 +1,94 @@
import { CollectionConfig } from 'payload';
export const BibleVerses: CollectionConfig = {
slug: 'bible-verses',
admin: {
useAsTitle: 'reference',
defaultColumns: ['reference', 'version', 'book', 'chapter'],
group: 'Bible Content',
pagination: {
defaultLimit: 50,
},
},
fields: [
{
name: 'book',
type: 'relationship',
relationTo: 'bible-books',
required: true,
index: true,
},
{
name: 'chapter',
type: 'number',
required: true,
min: 1,
index: true,
},
{
name: 'verse',
type: 'number',
required: true,
min: 1,
index: true,
},
{
name: 'text',
type: 'textarea',
required: true,
localized: true,
},
{
name: 'version',
type: 'select',
options: [
{ label: 'Cornilescu (VDC)', value: 'VDC' },
{ label: 'NASB', value: 'NASB' },
{ label: 'RVR', value: 'RVR' },
{ label: 'NR', value: 'NR' },
],
required: true,
index: true,
},
{
name: 'reference',
type: 'text',
admin: {
readOnly: true,
},
hooks: {
beforeChange: [
async ({ data, siblingData, req }) => {
if (!data) return '';
if (siblingData?.book && data.chapter && data.verse) {
const book = await req.payload.findByID({
collection: 'bible-books',
id: siblingData.book,
});
if (book) {
return `${book.name} ${data.chapter}:${data.verse}`;
}
}
return data.reference || '';
},
],
},
},
{
name: 'embedding',
type: 'json',
admin: {
hidden: true,
description: 'Vector embedding for semantic search',
},
},
],
access: {
read: () => true,
create: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
update: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
delete: ({ req }) => req.user?.role === 'super-admin',
},
};

View File

@@ -0,0 +1,60 @@
import { CollectionConfig } from 'payload';
export const Bookmarks: CollectionConfig = {
slug: 'bookmarks',
admin: {
useAsTitle: 'id',
defaultColumns: ['user', 'book', 'chapter', 'createdAt'],
group: 'User Content',
},
fields: [
{
name: 'user',
type: 'relationship',
relationTo: 'users',
required: true,
index: true,
},
{
name: 'book',
type: 'relationship',
relationTo: 'bible-books',
required: true,
},
{
name: 'chapter',
type: 'number',
required: true,
min: 1,
},
{
name: 'verse',
type: 'number',
min: 1,
},
{
name: 'note',
type: 'textarea',
},
],
access: {
read: ({ req }) => {
if (!req.user) {
return false;
}
if (req.user.role === 'admin' || req.user.role === 'super-admin') {
return true;
}
return {
user: {
equals: req.user.id,
},
};
},
create: ({ req }) => !!req.user,
update: ({ req }) => !!req.user,
delete: ({ req }) => !!req.user,
},
};

View File

@@ -0,0 +1,85 @@
import { CollectionConfig } from 'payload';
export const CheckoutSessions: CollectionConfig = {
slug: 'checkout-sessions',
admin: {
useAsTitle: 'sessionId',
defaultColumns: ['sessionId', 'user', 'type', 'status', 'createdAt'],
group: 'E-Commerce',
pagination: {
defaultLimit: 100,
},
},
fields: [
{
name: 'sessionId',
type: 'text',
unique: true,
required: true,
index: true,
admin: {
readOnly: true,
},
},
{
name: 'user',
type: 'relationship',
relationTo: 'users',
required: true,
index: true,
},
{
name: 'price',
type: 'relationship',
relationTo: 'prices',
},
{
name: 'type',
type: 'select',
options: [
{ label: 'Subscription', value: 'subscription' },
{ label: 'Donation', value: 'donation' },
{ label: 'One-time Purchase', value: 'one-time' },
],
required: true,
index: true,
},
{
name: 'status',
type: 'select',
options: [
{ label: 'Pending', value: 'pending' },
{ label: 'Completed', value: 'completed' },
{ label: 'Expired', value: 'expired' },
{ label: 'Failed', value: 'failed' },
],
defaultValue: 'pending',
required: true,
index: true,
},
{
name: 'metadata',
type: 'json',
},
],
access: {
read: ({ req }) => {
if (!req.user) {
return false;
}
if (req.user.role === 'admin' || req.user.role === 'super-admin') {
return true;
}
return {
user: {
equals: req.user.id,
},
};
},
create: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
update: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
delete: ({ req }) => req.user?.role === 'super-admin',
},
};

View File

@@ -0,0 +1,80 @@
import { CollectionConfig } from 'payload';
export const Customers: CollectionConfig = {
slug: 'customers',
admin: {
useAsTitle: 'email',
defaultColumns: ['email', 'name', 'stripeCustomerId', 'createdAt'],
group: 'E-Commerce',
},
fields: [
{
name: 'stripeCustomerId',
type: 'text',
unique: true,
required: true,
index: true,
admin: {
readOnly: true,
},
},
{
name: 'email',
type: 'email',
required: true,
index: true,
},
{
name: 'name',
type: 'text',
},
{
name: 'user',
type: 'relationship',
relationTo: 'users',
hasMany: false,
unique: true,
admin: {
description: 'Associated user account',
},
},
{
name: 'metadata',
type: 'json',
admin: {
description: 'Custom Stripe metadata',
},
},
{
name: 'description',
type: 'textarea',
},
],
access: {
read: ({ req }) => {
if (!req.user) {
return false;
}
if (req.user.role === 'admin' || req.user.role === 'super-admin') {
return true;
}
// Users can only read their own customer record
return {
user: {
equals: req.user.id,
},
};
},
create: ({ req }) => {
return req.user?.role === 'admin' || req.user?.role === 'super-admin';
},
update: ({ req }) => {
return req.user?.role === 'admin' || req.user?.role === 'super-admin';
},
delete: ({ req }) => {
return req.user?.role === 'super-admin';
},
},
};

View File

@@ -0,0 +1,74 @@
import { CollectionConfig } from 'payload';
export const Donations: CollectionConfig = {
slug: 'donations',
admin: {
useAsTitle: 'donorName',
defaultColumns: ['donorName', 'amount', 'currency', 'status', 'createdAt'],
group: 'E-Commerce',
},
fields: [
{
name: 'donorName',
type: 'text',
required: true,
},
{
name: 'donorEmail',
type: 'email',
required: true,
index: true,
},
{
name: 'amount',
type: 'number',
required: true,
min: 0,
admin: {
description: 'Amount in dollars',
},
},
{
name: 'currency',
type: 'text',
defaultValue: 'USD',
},
{
name: 'stripeSessionId',
type: 'text',
unique: true,
index: true,
},
{
name: 'stripePaymentIntentId',
type: 'text',
unique: true,
index: true,
},
{
name: 'status',
type: 'select',
options: [
{ label: 'Pending', value: 'pending' },
{ label: 'Completed', value: 'completed' },
{ label: 'Failed', value: 'failed' },
],
required: true,
},
{
name: 'message',
type: 'textarea',
},
{
name: 'anonymous',
type: 'checkbox',
defaultValue: false,
},
],
access: {
read: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
create: () => true,
update: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
delete: ({ req }) => req.user?.role === 'super-admin',
},
};

View File

@@ -0,0 +1,78 @@
import { CollectionConfig } from 'payload';
export const FailedPayments: CollectionConfig = {
slug: 'failed-payments',
admin: {
useAsTitle: 'id',
defaultColumns: ['stripePaymentIntentId', 'amount', 'errorCode', 'createdAt'],
group: 'E-Commerce',
},
fields: [
{
name: 'stripePaymentIntentId',
type: 'text',
unique: true,
required: true,
index: true,
admin: {
readOnly: true,
},
},
{
name: 'customerId',
type: 'text',
index: true,
},
{
name: 'amount',
type: 'number',
required: true,
admin: {
description: 'Amount in cents',
},
},
{
name: 'currency',
type: 'text',
required: true,
},
{
name: 'error',
type: 'textarea',
required: true,
},
{
name: 'errorCode',
type: 'text',
},
{
name: 'retryCount',
type: 'number',
defaultValue: 0,
min: 0,
},
{
name: 'resolved',
type: 'checkbox',
defaultValue: false,
index: true,
},
{
name: 'resolvedAt',
type: 'date',
},
{
name: 'notes',
type: 'textarea',
admin: {
description: 'Internal notes about this failure',
},
},
],
access: {
read: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
create: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
update: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
delete: ({ req }) => req.user?.role === 'super-admin',
},
};

View File

@@ -0,0 +1,62 @@
import { CollectionConfig } from 'payload';
export const Highlights: CollectionConfig = {
slug: 'highlights',
admin: {
useAsTitle: 'id',
defaultColumns: ['user', 'color', 'createdAt'],
group: 'User Content',
},
fields: [
{
name: 'user',
type: 'relationship',
relationTo: 'users',
required: true,
index: true,
},
{
name: 'verse',
type: 'relationship',
relationTo: 'bible-verses',
required: true,
},
{
name: 'color',
type: 'select',
options: [
{ label: 'Yellow', value: 'yellow' },
{ label: 'Green', value: 'green' },
{ label: 'Blue', value: 'blue' },
{ label: 'Red', value: 'red' },
{ label: 'Pink', value: 'pink' },
],
defaultValue: 'yellow',
required: true,
},
{
name: 'note',
type: 'textarea',
},
],
access: {
read: ({ req }) => {
if (!req.user) {
return false;
}
if (req.user.role === 'admin' || req.user.role === 'super-admin') {
return true;
}
return {
user: {
equals: req.user.id,
},
};
},
create: ({ req }) => !!req.user,
update: ({ req }) => !!req.user,
delete: ({ req }) => !!req.user,
},
};

View File

@@ -0,0 +1,122 @@
import { CollectionConfig } from 'payload';
export const Prices: CollectionConfig = {
slug: 'prices',
admin: {
useAsTitle: 'displayName',
defaultColumns: ['displayName', 'stripePriceId', 'unitAmount', 'currency', 'active'],
group: 'E-Commerce',
},
fields: [
{
name: 'displayName',
type: 'text',
admin: {
readOnly: true,
},
hooks: {
beforeChange: [
({ data, siblingData }) => {
const amount = ((siblingData.unitAmount || 0) / 100).toFixed(2);
const currency = (siblingData.currency || 'USD').toUpperCase();
const interval = siblingData.recurring?.interval;
if (interval) {
const intervalCount = siblingData.recurring?.intervalCount || 1;
const label = intervalCount > 1 ? `${intervalCount} ${interval}s` : interval;
return `${currency} ${amount}/${label}`;
}
return `${currency} ${amount}`;
},
],
},
},
{
name: 'product',
type: 'relationship',
relationTo: 'products',
required: true,
index: true,
},
{
name: 'stripePriceId',
type: 'text',
unique: true,
index: true,
admin: {
readOnly: true,
},
},
{
name: 'unitAmount',
type: 'number',
required: true,
min: 0,
admin: {
description: 'Amount in cents (e.g., 9999 = $99.99)',
},
},
{
name: 'currency',
type: 'select',
options: [
{ label: 'USD ($)', value: 'usd' },
{ label: 'EUR (€)', value: 'eur' },
{ label: 'GBP (£)', value: 'gbp' },
{ label: 'RON (lei)', value: 'ron' },
],
defaultValue: 'usd',
required: true,
},
{
name: 'recurring',
type: 'group',
admin: {
description: 'Leave empty for one-time prices',
},
fields: [
{
name: 'interval',
type: 'select',
options: [
{ label: 'Daily', value: 'day' },
{ label: 'Weekly', value: 'week' },
{ label: 'Monthly', value: 'month' },
{ label: 'Yearly', value: 'year' },
],
required: true,
admin: {
condition: (_, siblingData) => !!siblingData?.interval !== false,
},
},
{
name: 'intervalCount',
type: 'number',
defaultValue: 1,
min: 1,
required: true,
},
{
name: 'trialPeriodDays',
type: 'number',
min: 0,
admin: {
description: 'Number of trial days (optional)',
},
},
],
},
{
name: 'active',
type: 'checkbox',
defaultValue: true,
required: true,
},
],
access: {
read: () => true, // Prices are public
create: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
update: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
delete: ({ req }) => req.user?.role === 'super-admin',
},
};

View File

@@ -0,0 +1,102 @@
import { CollectionConfig } from 'payload';
export const Products: CollectionConfig = {
slug: 'products',
admin: {
useAsTitle: 'name',
defaultColumns: ['name', 'stripeProductId', 'active', 'createdAt'],
group: 'E-Commerce',
},
fields: [
{
name: 'name',
type: 'text',
required: true,
localized: true,
},
{
name: 'description',
type: 'richText',
localized: true,
},
{
name: 'stripeProductId',
type: 'text',
unique: true,
index: true,
admin: {
readOnly: true,
},
},
{
name: 'active',
type: 'checkbox',
defaultValue: true,
required: true,
},
{
name: 'metadata',
type: 'group',
fields: [
{
name: 'planType',
type: 'select',
options: [
{ label: 'Free', value: 'free' },
{ label: 'Premium', value: 'premium' },
{ label: 'Enterprise', value: 'enterprise' },
],
required: true,
},
{
name: 'features',
type: 'array',
fields: [
{
name: 'feature',
type: 'text',
required: true,
},
{
name: 'included',
type: 'checkbox',
defaultValue: true,
},
{
name: 'limit',
type: 'number',
admin: {
condition: (data, siblingData) => !siblingData.included,
},
},
],
},
],
},
{
name: 'prices',
type: 'relationship',
relationTo: 'prices',
hasMany: true,
admin: {
description: 'Associated price points for this product',
},
},
],
hooks: {
beforeChange: [
async ({ data, operation }) => {
if (operation === 'create' && !data.stripeProductId) {
console.log('Product created:', data.name, '- Stripe ID should be synced from Stripe plugin');
}
return data;
},
],
},
access: {
read: () => true, // Products are public
create: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
update: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
delete: ({ req }) => req.user?.role === 'super-admin',
},
};

View File

@@ -0,0 +1,155 @@
import { CollectionConfig } from 'payload';
export const Subscriptions: CollectionConfig = {
slug: 'subscriptions',
admin: {
useAsTitle: 'id',
defaultColumns: ['customer', 'status', 'currentPeriodEnd', 'active'],
group: 'E-Commerce',
},
fields: [
{
name: 'stripeSubscriptionId',
type: 'text',
unique: true,
required: true,
index: true,
admin: {
readOnly: true,
},
},
{
name: 'customer',
type: 'relationship',
relationTo: 'customers',
required: true,
index: true,
},
{
name: 'user',
type: 'relationship',
relationTo: 'users',
required: true,
unique: true,
index: true,
},
{
name: 'prices',
type: 'relationship',
relationTo: 'prices',
hasMany: true,
required: true,
},
{
name: 'status',
type: 'select',
options: [
{ label: 'Active', value: 'active' },
{ label: 'Past Due', value: 'past_due' },
{ label: 'Canceled', value: 'canceled' },
{ label: 'Incomplete', value: 'incomplete' },
{ label: 'Incomplete Expired', value: 'incomplete_expired' },
{ label: 'Trialing', value: 'trialing' },
{ label: 'Unpaid', value: 'unpaid' },
{ label: 'Paused', value: 'paused' },
],
required: true,
admin: {
readOnly: true,
},
},
{
name: 'currentPeriodStart',
type: 'date',
required: true,
admin: {
readOnly: true,
},
},
{
name: 'currentPeriodEnd',
type: 'date',
required: true,
index: true,
admin: {
readOnly: true,
},
},
{
name: 'canceledAt',
type: 'date',
admin: {
readOnly: true,
},
},
{
name: 'cancelAtPeriodEnd',
type: 'checkbox',
defaultValue: false,
admin: {
readOnly: true,
},
},
{
name: 'metadata',
type: 'group',
fields: [
{
name: 'planName',
type: 'text',
required: true,
},
{
name: 'conversationCount',
type: 'number',
defaultValue: 0,
admin: {
description: 'Monthly conversation count for free tier',
},
},
{
name: 'lastResetDate',
type: 'date',
},
],
},
],
hooks: {
afterChange: [
async ({ doc, operation, req }) => {
if (operation === 'create' || operation === 'update') {
// Update user's subscription reference
if (doc.user) {
await req.payload.update({
collection: 'users',
id: doc.user,
data: {
subscription: doc.id,
},
});
}
}
},
],
},
access: {
read: ({ req }) => {
if (!req.user) {
return false;
}
if (req.user.role === 'admin' || req.user.role === 'super-admin') {
return true;
}
return {
user: {
equals: req.user.id,
},
};
},
create: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
update: ({ req }) => req.user?.role === 'admin' || req.user?.role === 'super-admin',
delete: () => false, // Never delete subscription records
},
};

View File

@@ -0,0 +1,207 @@
import { CollectionConfig } from 'payload';
export const Users: CollectionConfig = {
slug: 'users',
auth: {
tokenExpiration: 604800, // 7 days
cookies: {
secure: process.env.NODE_ENV === 'production',
sameSite: 'Lax',
},
},
admin: {
useAsTitle: 'email',
defaultColumns: ['email', 'name', 'role', 'createdAt'],
},
fields: [
{
name: 'name',
type: 'text',
required: true,
localized: false,
},
{
name: 'email',
type: 'email',
required: true,
unique: true,
index: true,
},
{
name: 'role',
type: 'select',
options: [
{
label: 'User',
value: 'user',
},
{
label: 'Admin',
value: 'admin',
},
{
label: 'Super Admin',
value: 'super-admin',
},
],
defaultValue: 'user',
required: true,
admin: {
position: 'sidebar',
},
},
{
name: 'favoriteVersion',
type: 'select',
options: [
{ label: 'Cornilescu', value: 'VDC' },
{ label: 'NASB', value: 'NASB' },
{ label: 'RVR', value: 'RVR' },
{ label: 'NR', value: 'NR' },
],
defaultValue: 'VDC',
admin: {
position: 'sidebar',
},
},
{
name: 'stripeCustomerId',
type: 'text',
unique: true,
admin: {
position: 'sidebar',
readOnly: true,
description: 'Automatically set by Stripe integration',
},
},
{
name: 'subscription',
type: 'relationship',
relationTo: 'subscriptions',
hasMany: false,
admin: {
position: 'sidebar',
},
},
{
name: 'profileSettings',
type: 'group',
fields: [
{
name: 'fontSize',
type: 'number',
defaultValue: 16,
min: 12,
max: 24,
},
{
name: 'theme',
type: 'select',
options: [
{ label: 'Light', value: 'light' },
{ label: 'Dark', value: 'dark' },
],
defaultValue: 'light',
},
{
name: 'showVerseNumbers',
type: 'checkbox',
defaultValue: true,
},
{
name: 'enableNotifications',
type: 'checkbox',
defaultValue: true,
},
],
label: 'Profile Settings',
},
{
name: 'activityLog',
type: 'array',
fields: [
{
name: 'action',
type: 'text',
required: true,
},
{
name: 'timestamp',
type: 'date',
required: true,
admin: {
readOnly: true,
},
},
],
admin: {
readOnly: true,
description: 'Automatically tracked user activities',
},
},
],
hooks: {
beforeChange: [
async ({ data, operation }) => {
if (operation === 'create' && !data.email) {
throw new Error('Email is required');
}
return data;
},
],
afterChange: [
async ({ doc, operation }) => {
if (operation === 'create') {
console.log(`New user created: ${doc.email}`);
}
return doc;
},
],
},
access: {
read: ({ req }) => {
// Users can read their own data, admins can read all
if (!req.user) {
return false;
}
if (req.user.role === 'admin' || req.user.role === 'super-admin') {
return true;
}
return {
id: {
equals: req.user.id,
},
};
},
create: () => {
// Public can create accounts (registration)
return true;
},
update: ({ req }) => {
// Users can update their own data, admins can update all
if (!req.user) {
return false;
}
if (req.user.role === 'admin' || req.user.role === 'super-admin') {
return true;
}
return {
id: {
equals: req.user.id,
},
};
},
delete: ({ req }) => {
// Only super admins can delete users
if (!req.user) {
return false;
}
return req.user.role === 'super-admin';
},
},
};

View File

@@ -0,0 +1,12 @@
export { Users } from './Users';
export { Customers } from './Customers';
export { Subscriptions } from './Subscriptions';
export { Products } from './Products';
export { Prices } from './Prices';
export { BibleBooks } from './BibleBooks';
export { BibleVerses } from './BibleVerses';
export { Bookmarks } from './Bookmarks';
export { Highlights } from './Highlights';
export { Donations } from './Donations';
export { CheckoutSessions } from './CheckoutSessions';
export { FailedPayments } from './FailedPayments';

View File

@@ -0,0 +1,141 @@
import { GlobalConfig } from 'payload';
export const SiteSettings: GlobalConfig = {
slug: 'site-settings',
admin: {
group: 'Configuration',
},
fields: [
{
name: 'siteName',
type: 'text',
required: true,
defaultValue: 'Biblical Guide',
},
{
name: 'siteDescription',
type: 'textarea',
required: true,
},
{
name: 'siteUrl',
type: 'text',
required: true,
defaultValue: 'https://biblical-guide.com',
},
{
name: 'contactEmail',
type: 'email',
required: true,
},
{
name: 'paymentSettings',
type: 'group',
fields: [
{
name: 'stripePublishableKey',
type: 'text',
required: true,
admin: {
description: 'Public Stripe key for frontend',
},
},
{
name: 'enableDonations',
type: 'checkbox',
defaultValue: true,
},
{
name: 'minimumDonation',
type: 'number',
defaultValue: 1,
min: 0,
admin: {
description: 'Minimum donation amount in dollars',
},
},
],
},
{
name: 'emailSettings',
type: 'group',
fields: [
{
name: 'fromEmail',
type: 'email',
required: true,
admin: {
description: 'Email address for transactional emails',
},
},
{
name: 'fromName',
type: 'text',
defaultValue: 'Biblical Guide',
},
{
name: 'adminEmail',
type: 'email',
required: true,
admin: {
description: 'Admin notification email',
},
},
],
},
{
name: 'socialMedia',
type: 'group',
fields: [
{
name: 'facebook',
type: 'text',
admin: {
description: 'Facebook URL',
},
},
{
name: 'twitter',
type: 'text',
admin: {
description: 'Twitter/X URL',
},
},
{
name: 'instagram',
type: 'text',
admin: {
description: 'Instagram URL',
},
},
{
name: 'youtube',
type: 'text',
admin: {
description: 'YouTube channel URL',
},
},
],
},
{
name: 'maintenanceMode',
type: 'checkbox',
defaultValue: false,
admin: {
description: 'Enable to put the site in maintenance mode',
},
},
{
name: 'maintenanceMessage',
type: 'textarea',
admin: {
condition: (data) => data?.maintenanceMode === true,
description: 'Message to display during maintenance',
},
},
],
access: {
read: () => true,
update: ({ req }) => req.user?.role === 'super-admin',
},
};