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>
156 lines
3.4 KiB
TypeScript
156 lines
3.4 KiB
TypeScript
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
|
|
},
|
|
};
|