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>
123 lines
3.1 KiB
TypeScript
123 lines
3.1 KiB
TypeScript
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',
|
|
},
|
|
};
|