payload-subscribers-plugin 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +305 -0
- package/dist/collections/OptInChannels.d.ts +3 -0
- package/dist/collections/OptInChannels.js +44 -0
- package/dist/collections/OptInChannels.js.map +1 -0
- package/dist/collections/Subscribers.d.ts +8 -0
- package/dist/collections/Subscribers.js +88 -0
- package/dist/collections/Subscribers.js.map +1 -0
- package/dist/collections/fields/OptedInChannels.d.ts +2 -0
- package/dist/collections/fields/OptedInChannels.js +12 -0
- package/dist/collections/fields/OptedInChannels.js.map +1 -0
- package/dist/components/BeforeDashboardClient.d.ts +1 -0
- package/dist/components/BeforeDashboardClient.js +40 -0
- package/dist/components/BeforeDashboardClient.js.map +1 -0
- package/dist/components/BeforeDashboardServer.d.ts +2 -0
- package/dist/components/BeforeDashboardServer.js +22 -0
- package/dist/components/BeforeDashboardServer.js.map +1 -0
- package/dist/components/BeforeDashboardServer.module.css +5 -0
- package/dist/components/app/RequestMagicLink.d.ts +16 -0
- package/dist/components/app/RequestMagicLink.js +114 -0
- package/dist/components/app/RequestMagicLink.js.map +1 -0
- package/dist/components/app/RequestMagicLink.module.css +5 -0
- package/dist/components/app/RequestOrSubscribe.d.ts +17 -0
- package/dist/components/app/RequestOrSubscribe.js +28 -0
- package/dist/components/app/RequestOrSubscribe.js.map +1 -0
- package/dist/components/app/SelectOptInChannels.d.ts +20 -0
- package/dist/components/app/SelectOptInChannels.js +120 -0
- package/dist/components/app/SelectOptInChannels.js.map +1 -0
- package/dist/components/app/SelectOptInChannels.module.css +5 -0
- package/dist/components/app/Subscribe.d.ts +18 -0
- package/dist/components/app/Subscribe.js +169 -0
- package/dist/components/app/Subscribe.js.map +1 -0
- package/dist/components/app/Subscribe.module.css +5 -0
- package/dist/components/app/SubscriberMenu.d.ts +7 -0
- package/dist/components/app/SubscriberMenu.js +44 -0
- package/dist/components/app/SubscriberMenu.js.map +1 -0
- package/dist/components/app/VerifyMagicLink.d.ts +23 -0
- package/dist/components/app/VerifyMagicLink.js +169 -0
- package/dist/components/app/VerifyMagicLink.js.map +1 -0
- package/dist/components/app/VerifyMagicLink.module.css +5 -0
- package/dist/components/app/helpers.d.ts +1 -0
- package/dist/components/app/helpers.js +5 -0
- package/dist/components/app/helpers.js.map +1 -0
- package/dist/components/app/shared.module.css +14 -0
- package/dist/contexts/SubscriberProvider.d.ts +15 -0
- package/dist/contexts/SubscriberProvider.js +105 -0
- package/dist/contexts/SubscriberProvider.js.map +1 -0
- package/dist/copied/payload-types.d.ts +395 -0
- package/dist/copied/payload-types.js +15 -0
- package/dist/copied/payload-types.js.map +1 -0
- package/dist/copied/payload.config.d.ts +2 -0
- package/dist/endpoints/customEndpointHandler.d.ts +2 -0
- package/dist/endpoints/customEndpointHandler.js +7 -0
- package/dist/endpoints/customEndpointHandler.js.map +1 -0
- package/dist/endpoints/getOptInChannels.d.ts +19 -0
- package/dist/endpoints/getOptInChannels.js +42 -0
- package/dist/endpoints/getOptInChannels.js.map +1 -0
- package/dist/endpoints/logout.d.ts +20 -0
- package/dist/endpoints/logout.js +60 -0
- package/dist/endpoints/logout.js.map +1 -0
- package/dist/endpoints/requestMagicLink.d.ts +20 -0
- package/dist/endpoints/requestMagicLink.js +122 -0
- package/dist/endpoints/requestMagicLink.js.map +1 -0
- package/dist/endpoints/subscribe.d.ts +24 -0
- package/dist/endpoints/subscribe.js +343 -0
- package/dist/endpoints/subscribe.js.map +1 -0
- package/dist/endpoints/subscriberAuth.d.ts +22 -0
- package/dist/endpoints/subscriberAuth.js +69 -0
- package/dist/endpoints/subscriberAuth.js.map +1 -0
- package/dist/endpoints/verifyMagicLink.d.ts +20 -0
- package/dist/endpoints/verifyMagicLink.js +142 -0
- package/dist/endpoints/verifyMagicLink.js.map +1 -0
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.js +3 -0
- package/dist/exports/client.js.map +1 -0
- package/dist/exports/index.d.ts +1 -0
- package/dist/exports/index.js +3 -0
- package/dist/exports/index.js.map +1 -0
- package/dist/exports/rsc.d.ts +1 -0
- package/dist/exports/rsc.js +3 -0
- package/dist/exports/rsc.js.map +1 -0
- package/dist/exports/ui.d.ts +11 -0
- package/dist/exports/ui.js +9 -0
- package/dist/exports/ui.js.map +1 -0
- package/dist/helpers/serverConfig.d.ts +4 -0
- package/dist/helpers/serverConfig.js +22 -0
- package/dist/helpers/serverConfig.js.map +1 -0
- package/dist/helpers/testData.d.ts +2 -0
- package/dist/helpers/testData.js +4 -0
- package/dist/helpers/testData.js.map +1 -0
- package/dist/helpers/token.d.ts +9 -0
- package/dist/helpers/token.js +20 -0
- package/dist/helpers/token.js.map +1 -0
- package/dist/helpers/verifyOptIns.d.ts +5 -0
- package/dist/helpers/verifyOptIns.js +33 -0
- package/dist/helpers/verifyOptIns.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +147 -0
- package/dist/index.js.map +1 -0
- package/dist/react-hooks/useServerUrl.d.ts +3 -0
- package/dist/react-hooks/useServerUrl.js +19 -0
- package/dist/react-hooks/useServerUrl.js.map +1 -0
- package/dist/server-functions/serverUrl.d.ts +3 -0
- package/dist/server-functions/serverUrl.js +31 -0
- package/dist/server-functions/serverUrl.js.map +1 -0
- package/dist/server-functions/subscriberAuth.d.ts +11 -0
- package/package.json +94 -0
- package/src/collections/OptInChannels.ts +45 -0
- package/src/collections/Subscribers.ts +99 -0
- package/src/collections/fields/OptedInChannels.ts +12 -0
- package/src/components/app/RequestMagicLink.tsx +129 -0
- package/src/components/app/RequestOrSubscribe.tsx +58 -0
- package/src/components/app/SelectOptInChannels.tsx +147 -0
- package/src/components/app/Subscribe.tsx +190 -0
- package/src/components/app/SubscriberMenu.tsx +46 -0
- package/src/components/app/VerifyMagicLink.tsx +197 -0
- package/src/components/app/helpers.ts +6 -0
- package/src/components/app/shared.module.css +14 -0
- package/src/contexts/SubscriberProvider.tsx +122 -0
- package/src/copied/payload-types.ts +478 -0
- package/src/endpoints/getOptInChannels.ts +56 -0
- package/src/endpoints/logout.ts +104 -0
- package/src/endpoints/requestMagicLink.ts +139 -0
- package/src/endpoints/subscribe.ts +435 -0
- package/src/endpoints/subscriberAuth.ts +100 -0
- package/src/endpoints/verifyMagicLink.ts +164 -0
- package/src/exports/index.ts +1 -0
- package/src/exports/ui.ts +17 -0
- package/src/helpers/testData.ts +2 -0
- package/src/helpers/token.ts +14 -0
- package/src/helpers/verifyOptIns.ts +39 -0
- package/src/index.ts +207 -0
- package/src/react-hooks/useServerUrl.tsx +18 -0
- package/src/server-functions/serverUrl.ts +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# Payload Subscribers Plugin
|
|
2
|
+
|
|
3
|
+
A plugin to manage subscribers and the "channels" they can subscribe to.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add payload-subscribers-plugin
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Add the plugin to your Payload config.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// payload.config.ts
|
|
17
|
+
|
|
18
|
+
export default buildConfig({
|
|
19
|
+
plugins: [
|
|
20
|
+
payloadSubscribersPlugin({
|
|
21
|
+
collections: {
|
|
22
|
+
// Add slugs of your collections which should have a relationship field to the optInChannels.
|
|
23
|
+
posts: true,
|
|
24
|
+
},
|
|
25
|
+
// Easily disable the collection logic.
|
|
26
|
+
disabled: false,
|
|
27
|
+
// Provide a custom expiration for magic link tokens. The default is 30 minutes.
|
|
28
|
+
tokenExpiration: 60 * 60,
|
|
29
|
+
}),
|
|
30
|
+
],
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Place the **SubscriberProvider** at the a good location in your app structure. For example, in your root layout:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// layout.tsx
|
|
38
|
+
|
|
39
|
+
import { SubscriberProvider } from 'payload-subscribers-plugin/ui'
|
|
40
|
+
|
|
41
|
+
const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
42
|
+
return (
|
|
43
|
+
<html lang="en">
|
|
44
|
+
<head></head>
|
|
45
|
+
<body>
|
|
46
|
+
<SubscriberProvider>
|
|
47
|
+
...
|
|
48
|
+
</SubscriberProvider>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Then you can use the components in your app:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// page.tsx
|
|
59
|
+
|
|
60
|
+
import { RequestOrSubscribe } from 'payload-subscribers-plugin/ui'
|
|
61
|
+
|
|
62
|
+
const Page = () => {
|
|
63
|
+
return (
|
|
64
|
+
<main>
|
|
65
|
+
<RequestOrSubscribe
|
|
66
|
+
classNames={{ button: 'customCssClassNames', container: 'customCssClassNames', emailInput: 'customCssClassNames' }}
|
|
67
|
+
/>
|
|
68
|
+
</main>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**_IMPORANT:_** Be sure to create a /verify route
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// verify/page.tsx
|
|
77
|
+
|
|
78
|
+
import { VerifyClient } from '@/components/VerifyClient.js'
|
|
79
|
+
|
|
80
|
+
const Page = () => {
|
|
81
|
+
return (
|
|
82
|
+
<main id="main-content">
|
|
83
|
+
<VerifyMagicLink
|
|
84
|
+
classNames={{ button: 'customCssClassNames', container: 'customCssClassNames', emailInput: 'customCssClassNames' }}
|
|
85
|
+
/>
|
|
86
|
+
</main>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 🟢🔵🔴 Features
|
|
92
|
+
|
|
93
|
+
### 🟢 Plugin options
|
|
94
|
+
|
|
95
|
+
#### **collections**
|
|
96
|
+
|
|
97
|
+
You can specify collections in the plugin options which will be amended to include a relationTo field referring to the optInChannels collection. Right now this does not override the plugin-added subscribers collection, which is still used for the primary record of subscribers and used for authentication. The collections amended with an optIns can be used, for example, to manage your subscription channels and any email campaigns related.
|
|
98
|
+
|
|
99
|
+
#### **disabled**
|
|
100
|
+
|
|
101
|
+
#### **tokenExpiration**
|
|
102
|
+
|
|
103
|
+
### 🟢 Collections
|
|
104
|
+
|
|
105
|
+
#### **optInChannels**
|
|
106
|
+
|
|
107
|
+
Seeded when plugin inits.
|
|
108
|
+
|
|
109
|
+
- Fields
|
|
110
|
+
- title: text
|
|
111
|
+
- description: text
|
|
112
|
+
- active: boolean
|
|
113
|
+
- slug: text
|
|
114
|
+
|
|
115
|
+
#### **subscribers**
|
|
116
|
+
|
|
117
|
+
Seeded when plugin inits.
|
|
118
|
+
|
|
119
|
+
- Fields
|
|
120
|
+
- email: text
|
|
121
|
+
- first name: text
|
|
122
|
+
- status: Subscribed | Unsubscribed | Pending verification (default)
|
|
123
|
+
- opt-ins: referenceTo optInChannels hasMany
|
|
124
|
+
- source: text
|
|
125
|
+
- verificationToken: text hidden
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### 🔵 Fields
|
|
130
|
+
|
|
131
|
+
#### **OptedInChannels**
|
|
132
|
+
|
|
133
|
+
_THE FIELD SPEC IS CURRENTLY NOT EXPORTED_ Documenting here in case that seems useful in the future.
|
|
134
|
+
|
|
135
|
+
This is the same field used by the plugin **collections** to amended a relationTo field referring to the optInChannels collection.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### 🔴 Payload endpoints
|
|
140
|
+
|
|
141
|
+
#### **requestMagicLink**
|
|
142
|
+
|
|
143
|
+
Takes an email, verifies it, registers it if unknown, constructs a magic link, and uses your Payload emailAdapter to sendEmail.
|
|
144
|
+
|
|
145
|
+
#### **verifyMagicLink**
|
|
146
|
+
|
|
147
|
+
Takes an email and token, verifies the token, and authenticates the user, using Payload's HTTP-only cookies auth.
|
|
148
|
+
|
|
149
|
+
#### **getOptInChannels**
|
|
150
|
+
|
|
151
|
+
Returns all active optInChannels data.
|
|
152
|
+
|
|
153
|
+
#### **subscribe** a user, or update a subscriber's opt-ins.
|
|
154
|
+
|
|
155
|
+
Takes an email and list of optInChannel IDs, verifies them, and if the authenticated subscriber matches the email will update the channels that subscriber is opted into.
|
|
156
|
+
|
|
157
|
+
#### TO DO: unsubscribe
|
|
158
|
+
|
|
159
|
+
The **subscribe** endpoint will remove all optIns. But need a way to set the subscriber status to "unsubscribed"
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### 🟢 SubscriberProvider provider with useSubscriber context
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### 🔵 Provides several NextJS client components ready for use in a frontend app
|
|
168
|
+
|
|
169
|
+
- All App Components are client components that consume hooks, server components, server functions. Including the useSubscriber context, and so the must be used within the children descendent tree of the SubscriberProvider provider.
|
|
170
|
+
|
|
171
|
+
- All App Components accept a **classNames** prop to specify CSS class names to add to the different parts of the component
|
|
172
|
+
|
|
173
|
+
#### **RequestOrSubscribe**
|
|
174
|
+
|
|
175
|
+
Shows Subscribe to authenticated subscribers, otherwise shows RequestMagicLink.
|
|
176
|
+
|
|
177
|
+
<!-- <div style="border: 1px solid #ccc; padding: 15px; border-radius: 5px;">
|
|
178
|
+
</div> -->
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
<RequestMagicLink
|
|
182
|
+
// Provide your own global class names to add to the component elements. Optional
|
|
183
|
+
classNames={{
|
|
184
|
+
button: 'customCssClassNames',
|
|
185
|
+
container: 'customCssClassNames',
|
|
186
|
+
emailInput: 'customCssClassNames',
|
|
187
|
+
error: 'customCssClassNames',
|
|
188
|
+
form: 'customCssClassNames',
|
|
189
|
+
loading: 'customCssClassNames',
|
|
190
|
+
message: 'customCssClassNames',
|
|
191
|
+
section: 'customCssClassNames',
|
|
192
|
+
}}
|
|
193
|
+
// Called after a subscribers opt-ins have been updated. Optional
|
|
194
|
+
handleMagicLinkRequested={async (result: RequestMagicLinkResponse) => {}}
|
|
195
|
+
// Called after a subscribers opt-ins have been updated. Optional
|
|
196
|
+
handleSubscribe={async (result: SubscribeResponse) => {}}
|
|
197
|
+
// Provided your own button component. Optional
|
|
198
|
+
renderButton={({ name, onClick, text }) =>
|
|
199
|
+
<button name={name} onClick={onClick} type="button">
|
|
200
|
+
{text}
|
|
201
|
+
</button>
|
|
202
|
+
}
|
|
203
|
+
/>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### **RequestMagicLink**
|
|
207
|
+
|
|
208
|
+
Form to input email address and get a magic link email sent.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
<RequestMagicLink
|
|
212
|
+
// Provide your own global class names to add to the component elements. Optional
|
|
213
|
+
classNames={{
|
|
214
|
+
button: 'customCssClassNames',
|
|
215
|
+
container: 'customCssClassNames',
|
|
216
|
+
emailInput: 'customCssClassNames',
|
|
217
|
+
error: 'customCssClassNames',
|
|
218
|
+
form: 'customCssClassNames',
|
|
219
|
+
message: 'customCssClassNames',
|
|
220
|
+
}}
|
|
221
|
+
// Called after a subscribers opt-ins have been updated. Optional
|
|
222
|
+
handleMagicLinkRequested={async (result: RequestMagicLinkResponse) => {}}
|
|
223
|
+
// Provided your own button component. Optional
|
|
224
|
+
renderButton={({ name, onClick, text }) =>
|
|
225
|
+
<button name={name} onClick={onClick} type="button">
|
|
226
|
+
{text}
|
|
227
|
+
</button>
|
|
228
|
+
}
|
|
229
|
+
/>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### **VerifyMagicLink**
|
|
233
|
+
|
|
234
|
+
Component that verifies a magic link using expected url parameters.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
<VerifyMagicLink
|
|
238
|
+
// Provide your own global class names to add to the component elements. Optional
|
|
239
|
+
classNames={{
|
|
240
|
+
button: 'customCssClassNames',
|
|
241
|
+
container: 'customCssClassNames',
|
|
242
|
+
error: 'customCssClassNames',
|
|
243
|
+
form: 'customCssClassNames',
|
|
244
|
+
loading: 'customCssClassNames',
|
|
245
|
+
message: 'customCssClassNames',
|
|
246
|
+
}}
|
|
247
|
+
// Called after a magic link email has been sent. Optional
|
|
248
|
+
handleMagicLinkRequested={async (result: RequestMagicLinkResponse) => {}}
|
|
249
|
+
// Called after a magic link has been verified. Optional
|
|
250
|
+
handleMagicLinkVerified={async (result: RequestMagicLinkResponse) => {}}
|
|
251
|
+
// Provided your own button component. Optional
|
|
252
|
+
renderButton={({ name, forwardUrl, onClick, text }) =>
|
|
253
|
+
forwardUrl ? (
|
|
254
|
+
<a href={forwardUrl}>
|
|
255
|
+
<button name={name} type="button">
|
|
256
|
+
{text}
|
|
257
|
+
</button>
|
|
258
|
+
</a>
|
|
259
|
+
) : (
|
|
260
|
+
<button name={name} onClick={onClick} type="button">
|
|
261
|
+
{text}
|
|
262
|
+
</button>
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
/>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### **Subscribe**
|
|
269
|
+
|
|
270
|
+
Allows a subscriber to select from among all active optInChannels.
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
<Subscribe
|
|
274
|
+
// Provide your own global class names to add to the component elements. Optional
|
|
275
|
+
classNames={{
|
|
276
|
+
button: 'customCssClassNames',
|
|
277
|
+
container: 'customCssClassNames',
|
|
278
|
+
emailInput: 'customCssClassNames',
|
|
279
|
+
error: 'customCssClassNames',
|
|
280
|
+
form: 'customCssClassNames',
|
|
281
|
+
loading: 'customCssClassNames',
|
|
282
|
+
message: 'customCssClassNames',
|
|
283
|
+
section: 'customCssClassNames',
|
|
284
|
+
}}
|
|
285
|
+
// Called after a subscribers opt-ins have been updated. Optional
|
|
286
|
+
handleSubscribe={async (result: SubscribeResponse) => {}}
|
|
287
|
+
// Provided your own button component. Optional
|
|
288
|
+
renderButton={({ name, onClick, text }) =>
|
|
289
|
+
<button name={name} onClick={onClick} type="button">
|
|
290
|
+
{text}
|
|
291
|
+
</button>
|
|
292
|
+
}
|
|
293
|
+
/>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
#### **SubscriberMenu**
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// classNames prop
|
|
300
|
+
|
|
301
|
+
export type SubscriberMenuClasses = {
|
|
302
|
+
button?: string
|
|
303
|
+
container?: string
|
|
304
|
+
}
|
|
305
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const OptInChannels = {
|
|
2
|
+
slug: 'opt-in-channels',
|
|
3
|
+
access: {
|
|
4
|
+
// Public access for creation (signup form)
|
|
5
|
+
create: ()=>true,
|
|
6
|
+
// Admin-only access for reading, updating, and deleting
|
|
7
|
+
delete: ({ req })=>req.user ? true : false,
|
|
8
|
+
// read: ({ req }) => (req.user ? true : false),
|
|
9
|
+
read: ()=>true,
|
|
10
|
+
update: ({ req })=>req.user ? true : false
|
|
11
|
+
},
|
|
12
|
+
admin: {
|
|
13
|
+
useAsTitle: 'title'
|
|
14
|
+
},
|
|
15
|
+
fields: [
|
|
16
|
+
{
|
|
17
|
+
name: 'title',
|
|
18
|
+
type: 'text',
|
|
19
|
+
label: 'Title',
|
|
20
|
+
required: true,
|
|
21
|
+
unique: true
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'description',
|
|
25
|
+
type: 'text',
|
|
26
|
+
label: 'Description'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'active',
|
|
30
|
+
type: 'checkbox',
|
|
31
|
+
defaultValue: true,
|
|
32
|
+
label: 'Active',
|
|
33
|
+
required: true
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'slug',
|
|
37
|
+
type: 'text',
|
|
38
|
+
label: 'slug'
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
export default OptInChannels;
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=OptInChannels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/collections/OptInChannels.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nexport const OptInChannels: CollectionConfig = {\n slug: 'opt-in-channels',\n access: {\n // Public access for creation (signup form)\n create: () => true,\n // Admin-only access for reading, updating, and deleting\n delete: ({ req }) => (req.user ? true : false),\n // read: ({ req }) => (req.user ? true : false),\n read: () => true,\n update: ({ req }) => (req.user ? true : false),\n },\n admin: {\n useAsTitle: 'title', // Specify the field to use as the title\n },\n fields: [\n {\n name: 'title',\n type: 'text', // Enforces valid email format\n label: 'Title',\n required: true,\n unique: true, // Ensures no duplicate titles\n },\n {\n name: 'description',\n type: 'text',\n label: 'Description',\n },\n {\n name: 'active',\n type: 'checkbox',\n defaultValue: true, // Default to pending until verified\n label: 'Active',\n required: true,\n },\n {\n name: 'slug',\n type: 'text',\n label: 'slug',\n },\n ],\n}\n\nexport default OptInChannels\n"],"names":["OptInChannels","slug","access","create","delete","req","user","read","update","admin","useAsTitle","fields","name","type","label","required","unique","defaultValue"],"mappings":"AAEA,OAAO,MAAMA,gBAAkC;IAC7CC,MAAM;IACNC,QAAQ;QACN,2CAA2C;QAC3CC,QAAQ,IAAM;QACd,wDAAwD;QACxDC,QAAQ,CAAC,EAAEC,GAAG,EAAE,GAAMA,IAAIC,IAAI,GAAG,OAAO;QACxC,gDAAgD;QAChDC,MAAM,IAAM;QACZC,QAAQ,CAAC,EAAEH,GAAG,EAAE,GAAMA,IAAIC,IAAI,GAAG,OAAO;IAC1C;IACAG,OAAO;QACLC,YAAY;IACd;IACAC,QAAQ;QACN;YACEC,MAAM;YACNC,MAAM;YACNC,OAAO;YACPC,UAAU;YACVC,QAAQ;QACV;QACA;YACEJ,MAAM;YACNC,MAAM;YACNC,OAAO;QACT;QACA;YACEF,MAAM;YACNC,MAAM;YACNI,cAAc;YACdH,OAAO;YACPC,UAAU;QACZ;QACA;YACEH,MAAM;YACNC,MAAM;YACNC,OAAO;QACT;KACD;AACH,EAAC;AAED,eAAed,cAAa"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CollectionConfig, CollectionSlug, Field } from 'payload';
|
|
2
|
+
export declare const defaultTokenExpiration: number;
|
|
3
|
+
export declare const defaultCollectionSlug = "subscribers";
|
|
4
|
+
export declare const SubscribersCollectionFactory: ({ slug, tokenExpiration, }: {
|
|
5
|
+
slug?: CollectionSlug;
|
|
6
|
+
tokenExpiration?: number;
|
|
7
|
+
}) => CollectionConfig;
|
|
8
|
+
export declare const subscribersCollectionFields: Field[];
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { OptedInChannels } from './fields/OptedInChannels.js';
|
|
2
|
+
export const defaultTokenExpiration = 30 * 60 // 30 minutes
|
|
3
|
+
;
|
|
4
|
+
export const defaultCollectionSlug = 'subscribers';
|
|
5
|
+
export const SubscribersCollectionFactory = ({ slug, tokenExpiration = defaultTokenExpiration })=>{
|
|
6
|
+
const Subscribers = {
|
|
7
|
+
slug: slug ? slug : defaultCollectionSlug,
|
|
8
|
+
access: {
|
|
9
|
+
// Public access for creation (signup form)
|
|
10
|
+
create: ()=>true,
|
|
11
|
+
// Admin-only access for reading, updating, and deleting
|
|
12
|
+
delete: ({ req })=>req.user ? true : false,
|
|
13
|
+
read: ({ req })=>req.user ? true : false,
|
|
14
|
+
update: ({ req })=>req.user ? true : false
|
|
15
|
+
},
|
|
16
|
+
admin: {
|
|
17
|
+
useAsTitle: 'email'
|
|
18
|
+
},
|
|
19
|
+
auth: {
|
|
20
|
+
tokenExpiration
|
|
21
|
+
},
|
|
22
|
+
fields: [
|
|
23
|
+
...subscribersCollectionFields
|
|
24
|
+
]
|
|
25
|
+
};
|
|
26
|
+
return Subscribers;
|
|
27
|
+
};
|
|
28
|
+
export const subscribersCollectionFields = [
|
|
29
|
+
{
|
|
30
|
+
name: 'email',
|
|
31
|
+
type: 'email',
|
|
32
|
+
label: 'Email Address',
|
|
33
|
+
required: true,
|
|
34
|
+
unique: true
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'firstName',
|
|
38
|
+
type: 'text',
|
|
39
|
+
label: 'First Name'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'status',
|
|
43
|
+
type: 'select',
|
|
44
|
+
defaultValue: 'pending',
|
|
45
|
+
label: 'Subscription Status',
|
|
46
|
+
options: [
|
|
47
|
+
{
|
|
48
|
+
label: 'Subscribed',
|
|
49
|
+
value: 'subscribed'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
label: 'Unsubscribed',
|
|
53
|
+
value: 'unsubscribed'
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: 'Pending Verification',
|
|
57
|
+
value: 'pending'
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
required: true
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'source',
|
|
64
|
+
type: 'text',
|
|
65
|
+
label: 'Signup Source'
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'verificationToken',
|
|
69
|
+
type: 'text',
|
|
70
|
+
admin: {
|
|
71
|
+
hidden: true
|
|
72
|
+
},
|
|
73
|
+
label: 'Verification Token'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'verificationTokenExpires',
|
|
77
|
+
type: 'date',
|
|
78
|
+
admin: {
|
|
79
|
+
hidden: true
|
|
80
|
+
},
|
|
81
|
+
label: 'Verification Token Expiration'
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* Plugin field relationship to optinchannels
|
|
85
|
+
*/ OptedInChannels
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=Subscribers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/collections/Subscribers.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug, Field } from 'payload'\n\nimport { OptedInChannels } from './fields/OptedInChannels.js'\n\nexport const defaultTokenExpiration = 30 * 60 // 30 minutes\n\nexport const defaultCollectionSlug = 'subscribers'\n\nexport const SubscribersCollectionFactory = ({\n slug,\n tokenExpiration = defaultTokenExpiration,\n}: {\n slug?: CollectionSlug\n tokenExpiration?: number\n}) => {\n const Subscribers: CollectionConfig = {\n slug: slug ? slug : defaultCollectionSlug,\n access: {\n // Public access for creation (signup form)\n create: () => true,\n // Admin-only access for reading, updating, and deleting\n delete: ({ req }) => (req.user ? true : false),\n read: ({ req }) => (req.user ? true : false),\n update: ({ req }) => (req.user ? true : false),\n },\n admin: { useAsTitle: 'email' },\n auth: {\n tokenExpiration,\n // verify: true, // Require email verification before being allowed to authenticate\n // maxLoginAttempts: 5, // Automatically lock a user out after X amount of failed logins\n // lockTime: 600 * 1000, // Time period to allow the max login attempts\n },\n fields: [...subscribersCollectionFields],\n }\n\n return Subscribers\n}\n\nexport const subscribersCollectionFields: Field[] = [\n {\n name: 'email',\n type: 'email', // Enforces valid email format\n label: 'Email Address',\n required: true,\n unique: true, // Ensures no duplicate emails\n },\n {\n name: 'firstName',\n type: 'text',\n label: 'First Name',\n },\n {\n name: 'status',\n type: 'select',\n defaultValue: 'pending', // Default to pending until verified\n label: 'Subscription Status',\n options: [\n {\n label: 'Subscribed',\n value: 'subscribed',\n },\n {\n label: 'Unsubscribed',\n value: 'unsubscribed',\n },\n {\n label: 'Pending Verification',\n value: 'pending',\n },\n ],\n required: true,\n },\n {\n name: 'source',\n type: 'text', // e.g., 'Homepage form', 'Blog post A', etc.\n label: 'Signup Source',\n },\n {\n name: 'verificationToken',\n type: 'text',\n admin: {\n hidden: true, // Hide this field in the admin panel for security/cleanliness\n },\n label: 'Verification Token',\n },\n {\n name: 'verificationTokenExpires',\n type: 'date',\n admin: {\n hidden: true, // Hide this field in the admin panel for security/cleanliness\n },\n label: 'Verification Token Expiration',\n },\n\n /**\n * Plugin field relationship to optinchannels\n */\n OptedInChannels,\n]\n"],"names":["OptedInChannels","defaultTokenExpiration","defaultCollectionSlug","SubscribersCollectionFactory","slug","tokenExpiration","Subscribers","access","create","delete","req","user","read","update","admin","useAsTitle","auth","fields","subscribersCollectionFields","name","type","label","required","unique","defaultValue","options","value","hidden"],"mappings":"AAEA,SAASA,eAAe,QAAQ,8BAA6B;AAE7D,OAAO,MAAMC,yBAAyB,KAAK,GAAG,aAAa;CAAd;AAE7C,OAAO,MAAMC,wBAAwB,cAAa;AAElD,OAAO,MAAMC,+BAA+B,CAAC,EAC3CC,IAAI,EACJC,kBAAkBJ,sBAAsB,EAIzC;IACC,MAAMK,cAAgC;QACpCF,MAAMA,OAAOA,OAAOF;QACpBK,QAAQ;YACN,2CAA2C;YAC3CC,QAAQ,IAAM;YACd,wDAAwD;YACxDC,QAAQ,CAAC,EAAEC,GAAG,EAAE,GAAMA,IAAIC,IAAI,GAAG,OAAO;YACxCC,MAAM,CAAC,EAAEF,GAAG,EAAE,GAAMA,IAAIC,IAAI,GAAG,OAAO;YACtCE,QAAQ,CAAC,EAAEH,GAAG,EAAE,GAAMA,IAAIC,IAAI,GAAG,OAAO;QAC1C;QACAG,OAAO;YAAEC,YAAY;QAAQ;QAC7BC,MAAM;YACJX;QAIF;QACAY,QAAQ;eAAIC;SAA4B;IAC1C;IAEA,OAAOZ;AACT,EAAC;AAED,OAAO,MAAMY,8BAAuC;IAClD;QACEC,MAAM;QACNC,MAAM;QACNC,OAAO;QACPC,UAAU;QACVC,QAAQ;IACV;IACA;QACEJ,MAAM;QACNC,MAAM;QACNC,OAAO;IACT;IACA;QACEF,MAAM;QACNC,MAAM;QACNI,cAAc;QACdH,OAAO;QACPI,SAAS;YACP;gBACEJ,OAAO;gBACPK,OAAO;YACT;YACA;gBACEL,OAAO;gBACPK,OAAO;YACT;YACA;gBACEL,OAAO;gBACPK,OAAO;YACT;SACD;QACDJ,UAAU;IACZ;IACA;QACEH,MAAM;QACNC,MAAM;QACNC,OAAO;IACT;IACA;QACEF,MAAM;QACNC,MAAM;QACNN,OAAO;YACLa,QAAQ;QACV;QACAN,OAAO;IACT;IACA;QACEF,MAAM;QACNC,MAAM;QACNN,OAAO;YACLa,QAAQ;QACV;QACAN,OAAO;IACT;IAEA;;GAEC,GACDrB;CACD,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/collections/fields/OptedInChannels.ts"],"sourcesContent":["import type { Field } from 'payload'\n\nexport const OptedInChannels: Field = {\n name: 'optIns',\n type: 'relationship',\n admin: {\n position: 'sidebar',\n },\n hasMany: true,\n label: 'Opted-in channels',\n relationTo: 'opt-in-channels',\n}\n"],"names":["OptedInChannels","name","type","admin","position","hasMany","label","relationTo"],"mappings":"AAEA,OAAO,MAAMA,kBAAyB;IACpCC,MAAM;IACNC,MAAM;IACNC,OAAO;QACLC,UAAU;IACZ;IACAC,SAAS;IACTC,OAAO;IACPC,YAAY;AACd,EAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const BeforeDashboardClient: () => import("react").JSX.Element;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useConfig } from '@payloadcms/ui';
|
|
4
|
+
import { formatAdminURL } from 'payload/shared';
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
export const BeforeDashboardClient = ()=>{
|
|
7
|
+
const { config } = useConfig();
|
|
8
|
+
const [message, setMessage] = useState('');
|
|
9
|
+
useEffect(()=>{
|
|
10
|
+
const fetchMessage = async ()=>{
|
|
11
|
+
const response = await fetch(formatAdminURL({
|
|
12
|
+
adminRoute: config.routes.api,
|
|
13
|
+
path: '/my-plugin-endpoint'
|
|
14
|
+
}));
|
|
15
|
+
const result = await response.json();
|
|
16
|
+
setMessage(result.message);
|
|
17
|
+
};
|
|
18
|
+
void fetchMessage();
|
|
19
|
+
}, [
|
|
20
|
+
config.serverURL,
|
|
21
|
+
config.routes.api
|
|
22
|
+
]);
|
|
23
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
24
|
+
children: [
|
|
25
|
+
/*#__PURE__*/ _jsx("h1", {
|
|
26
|
+
children: "Added by the plugin: Before Dashboard Client"
|
|
27
|
+
}),
|
|
28
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
29
|
+
children: [
|
|
30
|
+
"Message from the endpoint:",
|
|
31
|
+
/*#__PURE__*/ _jsx("div", {
|
|
32
|
+
children: message || 'Loading...'
|
|
33
|
+
})
|
|
34
|
+
]
|
|
35
|
+
})
|
|
36
|
+
]
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=BeforeDashboardClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/BeforeDashboardClient.tsx"],"sourcesContent":["'use client'\nimport { useConfig } from '@payloadcms/ui'\nimport { formatAdminURL } from 'payload/shared'\nimport { useEffect, useState } from 'react'\n\nexport const BeforeDashboardClient = () => {\n const { config } = useConfig()\n\n const [message, setMessage] = useState('')\n\n useEffect(() => {\n const fetchMessage = async () => {\n const response = await fetch(\n formatAdminURL({\n adminRoute: config.routes.api,\n path: '/my-plugin-endpoint',\n }),\n )\n const result = await response.json()\n setMessage(result.message)\n }\n\n void fetchMessage()\n }, [config.serverURL, config.routes.api])\n\n return (\n <div>\n <h1>Added by the plugin: Before Dashboard Client</h1>\n <div>\n Message from the endpoint:\n <div>{message || 'Loading...'}</div>\n </div>\n </div>\n )\n}\n"],"names":["useConfig","formatAdminURL","useEffect","useState","BeforeDashboardClient","config","message","setMessage","fetchMessage","response","fetch","adminRoute","routes","api","path","result","json","serverURL","div","h1"],"mappings":"AAAA;;AACA,SAASA,SAAS,QAAQ,iBAAgB;AAC1C,SAASC,cAAc,QAAQ,iBAAgB;AAC/C,SAASC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAE3C,OAAO,MAAMC,wBAAwB;IACnC,MAAM,EAAEC,MAAM,EAAE,GAAGL;IAEnB,MAAM,CAACM,SAASC,WAAW,GAAGJ,SAAS;IAEvCD,UAAU;QACR,MAAMM,eAAe;YACnB,MAAMC,WAAW,MAAMC,MACrBT,eAAe;gBACbU,YAAYN,OAAOO,MAAM,CAACC,GAAG;gBAC7BC,MAAM;YACR;YAEF,MAAMC,SAAS,MAAMN,SAASO,IAAI;YAClCT,WAAWQ,OAAOT,OAAO;QAC3B;QAEA,KAAKE;IACP,GAAG;QAACH,OAAOY,SAAS;QAAEZ,OAAOO,MAAM,CAACC,GAAG;KAAC;IAExC,qBACE,MAACK;;0BACC,KAACC;0BAAG;;0BACJ,MAACD;;oBAAI;kCAEH,KAACA;kCAAKZ,WAAW;;;;;;AAIzB,EAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import styles from './BeforeDashboardServer.module.css';
|
|
3
|
+
export const BeforeDashboardServer = async (props)=>{
|
|
4
|
+
const { payload } = props;
|
|
5
|
+
const { docs } = await payload.find({
|
|
6
|
+
collection: 'subscribers'
|
|
7
|
+
});
|
|
8
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
9
|
+
className: styles.wrapper,
|
|
10
|
+
children: [
|
|
11
|
+
/*#__PURE__*/ _jsx("h1", {
|
|
12
|
+
children: "Added by the plugin: Before Dashboard Server"
|
|
13
|
+
}),
|
|
14
|
+
"Docs from Local API:",
|
|
15
|
+
docs.map((doc)=>/*#__PURE__*/ _jsx("div", {
|
|
16
|
+
children: doc.id
|
|
17
|
+
}, doc.id))
|
|
18
|
+
]
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=BeforeDashboardServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/BeforeDashboardServer.tsx"],"sourcesContent":["import type { ServerComponentProps } from 'payload'\n\nimport styles from './BeforeDashboardServer.module.css'\n\nexport const BeforeDashboardServer = async (props: ServerComponentProps) => {\n const { payload } = props\n\n const { docs } = await payload.find({ collection: 'subscribers' })\n\n return (\n <div className={styles.wrapper}>\n <h1>Added by the plugin: Before Dashboard Server</h1>\n Docs from Local API:\n {docs.map((doc) => (\n <div key={doc.id}>{doc.id}</div>\n ))}\n </div>\n )\n}\n"],"names":["styles","BeforeDashboardServer","props","payload","docs","find","collection","div","className","wrapper","h1","map","doc","id"],"mappings":";AAEA,OAAOA,YAAY,qCAAoC;AAEvD,OAAO,MAAMC,wBAAwB,OAAOC;IAC1C,MAAM,EAAEC,OAAO,EAAE,GAAGD;IAEpB,MAAM,EAAEE,IAAI,EAAE,GAAG,MAAMD,QAAQE,IAAI,CAAC;QAAEC,YAAY;IAAc;IAEhE,qBACE,MAACC;QAAIC,WAAWR,OAAOS,OAAO;;0BAC5B,KAACC;0BAAG;;YAAiD;YAEpDN,KAAKO,GAAG,CAAC,CAACC,oBACT,KAACL;8BAAkBK,IAAIC,EAAE;mBAAfD,IAAIC,EAAE;;;AAIxB,EAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RequestMagicLinkResponse } from '../../endpoints/requestMagicLink.js';
|
|
2
|
+
export { RequestMagicLinkResponse };
|
|
3
|
+
export interface IRequestMagicLink {
|
|
4
|
+
classNames?: RequestMagicLinkClasses;
|
|
5
|
+
handleMagicLinkRequested?: (result: RequestMagicLinkResponse) => void;
|
|
6
|
+
props?: any;
|
|
7
|
+
}
|
|
8
|
+
export type RequestMagicLinkClasses = {
|
|
9
|
+
button?: string;
|
|
10
|
+
container?: string;
|
|
11
|
+
emailInput?: string;
|
|
12
|
+
error?: string;
|
|
13
|
+
form?: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare const RequestMagicLink: ({ classNames, handleMagicLinkRequested, }: IRequestMagicLink) => import("react").JSX.Element;
|