mirror of
https://github.com/xtr-dev/payload-notifications.git
synced 2025-12-10 02:43:23 +00:00
Refactor: Simplify notifications plugin configuration, remove unused code, and improve channel handling
This commit is contained in:
@@ -1,174 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
// Enhanced demo implementation with real service worker registration
|
||||
class DemoClientPushManager {
|
||||
private vapidPublicKey: string
|
||||
private serviceWorkerPath: string
|
||||
private apiEndpoint: string
|
||||
|
||||
constructor(vapidPublicKey: string, options: { serviceWorkerPath?: string; apiEndpoint?: string } = {}) {
|
||||
this.vapidPublicKey = vapidPublicKey
|
||||
this.serviceWorkerPath = options.serviceWorkerPath || '/sw.js'
|
||||
this.apiEndpoint = options.apiEndpoint || '/api/push-notifications'
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
if (typeof window === 'undefined') return false
|
||||
return (
|
||||
'serviceWorker' in navigator &&
|
||||
'PushManager' in window &&
|
||||
'Notification' in window
|
||||
)
|
||||
}
|
||||
|
||||
public getPermissionStatus(): NotificationPermission {
|
||||
if (typeof window === 'undefined' || typeof Notification === 'undefined') return 'default'
|
||||
return Notification.permission
|
||||
}
|
||||
|
||||
public async requestPermission(): Promise<NotificationPermission> {
|
||||
if (!this.isSupported()) {
|
||||
throw new Error('Push notifications are not supported')
|
||||
}
|
||||
return await Notification.requestPermission()
|
||||
}
|
||||
|
||||
public async registerServiceWorker(): Promise<ServiceWorkerRegistration> {
|
||||
if (!this.isSupported()) {
|
||||
throw new Error('Service workers are not supported')
|
||||
}
|
||||
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.register(this.serviceWorkerPath)
|
||||
console.log('Service worker registered:', registration)
|
||||
|
||||
// Wait for service worker to be ready
|
||||
await navigator.serviceWorker.ready
|
||||
|
||||
return registration
|
||||
} catch (error) {
|
||||
console.error('Service worker registration failed:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
public async subscribe(): Promise<any> {
|
||||
const permission = await this.requestPermission()
|
||||
if (permission !== 'granted') {
|
||||
throw new Error('Notification permission not granted')
|
||||
}
|
||||
|
||||
const registration = await this.registerServiceWorker()
|
||||
|
||||
// For demo purposes, we'll simulate subscription without actual VAPID keys
|
||||
// In production, you would use real VAPID keys here
|
||||
try {
|
||||
const subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: this.urlBase64ToUint8Array(this.vapidPublicKey),
|
||||
})
|
||||
|
||||
console.log('Push subscription:', subscription)
|
||||
return {
|
||||
endpoint: subscription.endpoint,
|
||||
keys: {
|
||||
p256dh: this.arrayBufferToBase64(subscription.getKey('p256dh')!),
|
||||
auth: this.arrayBufferToBase64(subscription.getKey('auth')!),
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Real push subscription failed, simulating for demo:', error)
|
||||
// Return simulated subscription for demo
|
||||
return {
|
||||
endpoint: 'demo-endpoint',
|
||||
keys: { p256dh: 'demo-key', auth: 'demo-auth' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async isSubscribed(): Promise<boolean> {
|
||||
if (!this.isSupported()) return false
|
||||
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.getRegistration()
|
||||
if (!registration) return false
|
||||
|
||||
const subscription = await registration.pushManager.getSubscription()
|
||||
return subscription !== null
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public async unsubscribe(): Promise<void> {
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.getRegistration()
|
||||
if (!registration) return
|
||||
|
||||
const subscription = await registration.pushManager.getSubscription()
|
||||
if (subscription) {
|
||||
await subscription.unsubscribe()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Unsubscribe failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
public async sendTestNotification(): Promise<void> {
|
||||
// Send a test notification using the service worker
|
||||
const registration = await navigator.serviceWorker.getRegistration()
|
||||
if (!registration) {
|
||||
throw new Error('Service worker not registered')
|
||||
}
|
||||
|
||||
// Simulate receiving a push message
|
||||
if (registration.active) {
|
||||
registration.active.postMessage({
|
||||
type: 'TEST_NOTIFICATION',
|
||||
payload: {
|
||||
title: 'Test Notification',
|
||||
body: 'This is a test notification from the demo!',
|
||||
icon: '/icons/notification-icon.png',
|
||||
badge: '/icons/notification-badge.png',
|
||||
data: {
|
||||
url: '/admin/collections/notifications',
|
||||
notificationId: 'demo-' + Date.now()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Also show a direct notification for testing
|
||||
if (Notification.permission === 'granted') {
|
||||
new Notification('Direct Test Notification', {
|
||||
body: 'This notification was sent directly from JavaScript',
|
||||
icon: '/icons/notification-icon.png',
|
||||
tag: 'direct-test'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private urlBase64ToUint8Array(base64String: string): Uint8Array {
|
||||
const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
|
||||
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/')
|
||||
|
||||
const rawData = window.atob(base64)
|
||||
const outputArray = new Uint8Array(rawData.length)
|
||||
|
||||
for (let i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i)
|
||||
}
|
||||
return outputArray
|
||||
}
|
||||
|
||||
private arrayBufferToBase64(buffer: ArrayBuffer): string {
|
||||
const bytes = new Uint8Array(buffer)
|
||||
const binary = Array.from(bytes).map(b => String.fromCharCode(b)).join('')
|
||||
return window.btoa(binary)
|
||||
}
|
||||
}
|
||||
import {ClientPushManager} from "../../../../src/client/push-manager"
|
||||
|
||||
// Available channels (should match the configuration in payload.config.ts)
|
||||
const AVAILABLE_CHANNELS = [
|
||||
@@ -182,7 +15,7 @@ export default function DemoPage() {
|
||||
const [isSupported, setIsSupported] = useState(false)
|
||||
const [isSubscribed, setIsSubscribed] = useState(false)
|
||||
const [permission, setPermission] = useState<NotificationPermission>('default')
|
||||
const [pushManager, setPushManager] = useState<DemoClientPushManager | null>(null)
|
||||
const [pushManager, setPushManager] = useState<ClientPushManager | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [selectedChannels, setSelectedChannels] = useState<string[]>(
|
||||
AVAILABLE_CHANNELS.filter(channel => channel.defaultEnabled).map(channel => channel.id)
|
||||
@@ -191,7 +24,7 @@ export default function DemoPage() {
|
||||
useEffect(() => {
|
||||
// Use the real VAPID public key from environment
|
||||
const vapidPublicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY || 'BNde-uFUkQB5BweFbOt_40Tn3xZahMop2JKT8kqRn4UqMMinieguHmVCTxwN_qfM-jZ0YFpVpIk3CWehlXcTl8A'
|
||||
const manager = new DemoClientPushManager(vapidPublicKey)
|
||||
const manager = new ClientPushManager(vapidPublicKey)
|
||||
setPushManager(manager)
|
||||
setIsSupported(manager.isSupported())
|
||||
setPermission(manager.getPermissionStatus())
|
||||
@@ -203,11 +36,11 @@ export default function DemoPage() {
|
||||
|
||||
const handleSubscribe = async () => {
|
||||
if (!pushManager) return
|
||||
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const subscription = await pushManager.subscribe()
|
||||
|
||||
const subscription = await pushManager.subscribe(selectedChannels)
|
||||
|
||||
// Save the subscription to Payload's database using the plugin's API endpoint
|
||||
const response = await fetch('/api/push-notifications/subscribe', {
|
||||
method: 'POST',
|
||||
@@ -221,7 +54,6 @@ export default function DemoPage() {
|
||||
channels: selectedChannels,
|
||||
}),
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
setIsSubscribed(true)
|
||||
setPermission('granted')
|
||||
@@ -239,11 +71,11 @@ export default function DemoPage() {
|
||||
|
||||
const handleUnsubscribe = async () => {
|
||||
if (!pushManager) return
|
||||
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
await pushManager.unsubscribe()
|
||||
|
||||
|
||||
// Remove the subscription from Payload's database
|
||||
const response = await fetch('/api/push-notifications/unsubscribe', {
|
||||
method: 'POST',
|
||||
@@ -271,27 +103,13 @@ export default function DemoPage() {
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
const handleTestNotification = async () => {
|
||||
if (!pushManager) return
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
await pushManager.sendTestNotification()
|
||||
alert('Test notification sent! Check your browser notifications.')
|
||||
} catch (error) {
|
||||
console.error('Failed to send test notification:', error)
|
||||
alert('Failed to send test notification: ' + (error as Error).message)
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: '2rem', maxWidth: '800px', margin: '0 auto' }}>
|
||||
<h1>Payload Notifications Plugin Demo</h1>
|
||||
|
||||
|
||||
<div style={{ marginBottom: '2rem', padding: '1rem', border: '1px solid #ccc', borderRadius: '8px' }}>
|
||||
<h2>🔔 Web Push Notifications</h2>
|
||||
|
||||
|
||||
{!isSupported ? (
|
||||
<div style={{ color: 'red' }}>
|
||||
❌ Push notifications are not supported in this browser
|
||||
@@ -300,7 +118,7 @@ export default function DemoPage() {
|
||||
<div>
|
||||
<p><strong>Status:</strong> {isSubscribed ? '✅ Subscribed' : '❌ Not subscribed'}</p>
|
||||
<p><strong>Permission:</strong> {permission}</p>
|
||||
|
||||
|
||||
{!isSubscribed && (
|
||||
<div style={{ marginTop: '1rem', marginBottom: '1rem' }}>
|
||||
<h3 style={{ marginBottom: '0.5rem', fontSize: '1rem' }}>📢 Select Notification Channels</h3>
|
||||
@@ -309,11 +127,11 @@ export default function DemoPage() {
|
||||
</p>
|
||||
<div style={{ display: 'grid', gap: '0.75rem' }}>
|
||||
{AVAILABLE_CHANNELS.map(channel => (
|
||||
<label
|
||||
key={channel.id}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
<label
|
||||
key={channel.id}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: '0.5rem',
|
||||
padding: '0.75rem',
|
||||
border: '1px solid #e0e0e0',
|
||||
@@ -347,10 +165,10 @@ export default function DemoPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<div style={{ marginTop: '1rem', display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
|
||||
{!isSubscribed ? (
|
||||
<button
|
||||
<button
|
||||
onClick={handleSubscribe}
|
||||
disabled={loading || selectedChannels.length === 0}
|
||||
style={{
|
||||
@@ -367,7 +185,7 @@ export default function DemoPage() {
|
||||
</button>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
<button
|
||||
onClick={handleUnsubscribe}
|
||||
disabled={loading}
|
||||
style={{
|
||||
@@ -382,21 +200,6 @@ export default function DemoPage() {
|
||||
>
|
||||
{loading ? 'Unsubscribing...' : 'Disable Notifications'}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleTestNotification}
|
||||
disabled={loading}
|
||||
style={{
|
||||
padding: '0.75rem 1.5rem',
|
||||
backgroundColor: '#28a745',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
opacity: loading ? 0.6 : 1
|
||||
}}
|
||||
>
|
||||
{loading ? 'Sending...' : 'Send Test Notification'}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -490,4 +293,4 @@ export default function DemoPage() {
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,10 +68,6 @@ export interface Config {
|
||||
blocks: {};
|
||||
collections: {
|
||||
users: User;
|
||||
orders: Order;
|
||||
products: Product;
|
||||
posts: Post;
|
||||
media: Media;
|
||||
notifications: Notification;
|
||||
'push-subscriptions': PushSubscription;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
@@ -81,10 +77,6 @@ export interface Config {
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
orders: OrdersSelect<false> | OrdersSelect<true>;
|
||||
products: ProductsSelect<false> | ProductsSelect<true>;
|
||||
posts: PostsSelect<false> | PostsSelect<true>;
|
||||
media: MediaSelect<false> | MediaSelect<true>;
|
||||
notifications: NotificationsSelect<false> | NotificationsSelect<true>;
|
||||
'push-subscriptions': PushSubscriptionsSelect<false> | PushSubscriptionsSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
@@ -143,79 +135,6 @@ export interface User {
|
||||
lockUntil?: string | null;
|
||||
password?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "orders".
|
||||
*/
|
||||
export interface Order {
|
||||
id: string;
|
||||
orderNumber: string;
|
||||
customer: string | User;
|
||||
status?: ('pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled') | null;
|
||||
total: number;
|
||||
products?: (string | Product)[] | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "products".
|
||||
*/
|
||||
export interface Product {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
price: number;
|
||||
category?: ('electronics' | 'clothing' | 'books' | 'home-garden') | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: string;
|
||||
title: string;
|
||||
content?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
author?: (string | null) | User;
|
||||
publishedAt?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media".
|
||||
*/
|
||||
export interface Media {
|
||||
id: string;
|
||||
alt?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
thumbnailURL?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* Manage user notifications and messaging
|
||||
*
|
||||
@@ -247,9 +166,13 @@ export interface Notification {
|
||||
[k: string]: unknown;
|
||||
};
|
||||
/**
|
||||
* The user who should receive this notification
|
||||
* The user who should receive this notification (optional if using custom recipient fields)
|
||||
*/
|
||||
recipient: string | User;
|
||||
recipient?: (string | null) | User;
|
||||
/**
|
||||
* The notification channel - only subscribers to this channel will receive the notification
|
||||
*/
|
||||
channel?: ('general' | 'orders' | 'products' | 'marketing') | null;
|
||||
/**
|
||||
* Whether this notification has been read by the recipient
|
||||
*/
|
||||
@@ -258,11 +181,6 @@ export interface Notification {
|
||||
* When this notification was marked as read
|
||||
*/
|
||||
readAt?: string | null;
|
||||
attachments?: {
|
||||
order?: (string | null) | Order;
|
||||
product?: (string | Product)[] | null;
|
||||
post?: (string | null) | Post;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -294,6 +212,10 @@ export interface PushSubscription {
|
||||
* Browser/device information
|
||||
*/
|
||||
userAgent?: string | null;
|
||||
/**
|
||||
* Channels this subscription is subscribed to - leave empty for all notifications
|
||||
*/
|
||||
channels?: ('general' | 'orders' | 'products' | 'marketing')[] | null;
|
||||
/**
|
||||
* Whether this subscription is still active
|
||||
*/
|
||||
@@ -312,22 +234,6 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'orders';
|
||||
value: string | Order;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'products';
|
||||
value: string | Product;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: string | Post;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'media';
|
||||
value: string | Media;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'notifications';
|
||||
value: string | Notification;
|
||||
@@ -396,61 +302,6 @@ export interface UsersSelect<T extends boolean = true> {
|
||||
loginAttempts?: T;
|
||||
lockUntil?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "orders_select".
|
||||
*/
|
||||
export interface OrdersSelect<T extends boolean = true> {
|
||||
orderNumber?: T;
|
||||
customer?: T;
|
||||
status?: T;
|
||||
total?: T;
|
||||
products?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "products_select".
|
||||
*/
|
||||
export interface ProductsSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
description?: T;
|
||||
price?: T;
|
||||
category?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts_select".
|
||||
*/
|
||||
export interface PostsSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
content?: T;
|
||||
author?: T;
|
||||
publishedAt?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media_select".
|
||||
*/
|
||||
export interface MediaSelect<T extends boolean = true> {
|
||||
alt?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
url?: T;
|
||||
thumbnailURL?: T;
|
||||
filename?: T;
|
||||
mimeType?: T;
|
||||
filesize?: T;
|
||||
width?: T;
|
||||
height?: T;
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "notifications_select".
|
||||
@@ -459,15 +310,9 @@ export interface NotificationsSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
message?: T;
|
||||
recipient?: T;
|
||||
channel?: T;
|
||||
isRead?: T;
|
||||
readAt?: T;
|
||||
attachments?:
|
||||
| T
|
||||
| {
|
||||
order?: T;
|
||||
product?: T;
|
||||
post?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
@@ -481,6 +326,7 @@ export interface PushSubscriptionsSelect<T extends boolean = true> {
|
||||
p256dh?: T;
|
||||
auth?: T;
|
||||
userAgent?: T;
|
||||
channels?: T;
|
||||
isActive?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
|
||||
@@ -5,8 +5,8 @@ import { buildConfig } from 'payload'
|
||||
import sharp from 'sharp'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { testEmailAdapter } from './helpers/testEmailAdapter.ts'
|
||||
import { seed } from './seed.ts'
|
||||
import {testEmailAdapter} from "./helpers/testEmailAdapter"
|
||||
import {seed} from "./seed"
|
||||
import { notificationsPlugin } from '@xtr-dev/payload-notifications'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
@@ -79,45 +79,28 @@ const buildConfigWithMemoryDB = async () => {
|
||||
plugins: [
|
||||
// Demo of the notifications plugin with relationships and channels
|
||||
notificationsPlugin({
|
||||
collections: {
|
||||
slug: 'notifications',
|
||||
labels: {
|
||||
singular: 'Notification',
|
||||
plural: 'Notifications'
|
||||
}
|
||||
},
|
||||
channels: [
|
||||
{
|
||||
id: 'general',
|
||||
name: 'General Notifications',
|
||||
description: 'General updates and announcements',
|
||||
defaultEnabled: true
|
||||
},
|
||||
{
|
||||
id: 'orders',
|
||||
name: 'Order Updates',
|
||||
description: 'Order status changes and shipping notifications',
|
||||
defaultEnabled: true
|
||||
},
|
||||
{
|
||||
id: 'products',
|
||||
name: 'Product Updates',
|
||||
description: 'New products, restocks, and price changes',
|
||||
defaultEnabled: false
|
||||
},
|
||||
{
|
||||
id: 'marketing',
|
||||
name: 'Marketing & Promotions',
|
||||
description: 'Special offers, sales, and promotional content',
|
||||
defaultEnabled: false
|
||||
}
|
||||
],
|
||||
access: {
|
||||
read: ({ req }: { req: any }) => Boolean(req.user),
|
||||
create: ({ req }: { req: any }) => Boolean(req.user),
|
||||
update: ({ req }: { req: any }) => Boolean(req.user),
|
||||
delete: ({ req }: { req: any }) => Boolean(req.user?.role === 'admin'),
|
||||
},
|
||||
webPush: {
|
||||
enabled: true,
|
||||
autoPush: true, // Enable automatic push notifications
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"rootDir": "./",
|
||||
"paths": {
|
||||
"@payload-config": [
|
||||
"./payload.config.ts"
|
||||
|
||||
Reference in New Issue
Block a user