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.
Files changed (133) hide show
  1. package/README.md +305 -0
  2. package/dist/collections/OptInChannels.d.ts +3 -0
  3. package/dist/collections/OptInChannels.js +44 -0
  4. package/dist/collections/OptInChannels.js.map +1 -0
  5. package/dist/collections/Subscribers.d.ts +8 -0
  6. package/dist/collections/Subscribers.js +88 -0
  7. package/dist/collections/Subscribers.js.map +1 -0
  8. package/dist/collections/fields/OptedInChannels.d.ts +2 -0
  9. package/dist/collections/fields/OptedInChannels.js +12 -0
  10. package/dist/collections/fields/OptedInChannels.js.map +1 -0
  11. package/dist/components/BeforeDashboardClient.d.ts +1 -0
  12. package/dist/components/BeforeDashboardClient.js +40 -0
  13. package/dist/components/BeforeDashboardClient.js.map +1 -0
  14. package/dist/components/BeforeDashboardServer.d.ts +2 -0
  15. package/dist/components/BeforeDashboardServer.js +22 -0
  16. package/dist/components/BeforeDashboardServer.js.map +1 -0
  17. package/dist/components/BeforeDashboardServer.module.css +5 -0
  18. package/dist/components/app/RequestMagicLink.d.ts +16 -0
  19. package/dist/components/app/RequestMagicLink.js +114 -0
  20. package/dist/components/app/RequestMagicLink.js.map +1 -0
  21. package/dist/components/app/RequestMagicLink.module.css +5 -0
  22. package/dist/components/app/RequestOrSubscribe.d.ts +17 -0
  23. package/dist/components/app/RequestOrSubscribe.js +28 -0
  24. package/dist/components/app/RequestOrSubscribe.js.map +1 -0
  25. package/dist/components/app/SelectOptInChannels.d.ts +20 -0
  26. package/dist/components/app/SelectOptInChannels.js +120 -0
  27. package/dist/components/app/SelectOptInChannels.js.map +1 -0
  28. package/dist/components/app/SelectOptInChannels.module.css +5 -0
  29. package/dist/components/app/Subscribe.d.ts +18 -0
  30. package/dist/components/app/Subscribe.js +169 -0
  31. package/dist/components/app/Subscribe.js.map +1 -0
  32. package/dist/components/app/Subscribe.module.css +5 -0
  33. package/dist/components/app/SubscriberMenu.d.ts +7 -0
  34. package/dist/components/app/SubscriberMenu.js +44 -0
  35. package/dist/components/app/SubscriberMenu.js.map +1 -0
  36. package/dist/components/app/VerifyMagicLink.d.ts +23 -0
  37. package/dist/components/app/VerifyMagicLink.js +169 -0
  38. package/dist/components/app/VerifyMagicLink.js.map +1 -0
  39. package/dist/components/app/VerifyMagicLink.module.css +5 -0
  40. package/dist/components/app/helpers.d.ts +1 -0
  41. package/dist/components/app/helpers.js +5 -0
  42. package/dist/components/app/helpers.js.map +1 -0
  43. package/dist/components/app/shared.module.css +14 -0
  44. package/dist/contexts/SubscriberProvider.d.ts +15 -0
  45. package/dist/contexts/SubscriberProvider.js +105 -0
  46. package/dist/contexts/SubscriberProvider.js.map +1 -0
  47. package/dist/copied/payload-types.d.ts +395 -0
  48. package/dist/copied/payload-types.js +15 -0
  49. package/dist/copied/payload-types.js.map +1 -0
  50. package/dist/copied/payload.config.d.ts +2 -0
  51. package/dist/endpoints/customEndpointHandler.d.ts +2 -0
  52. package/dist/endpoints/customEndpointHandler.js +7 -0
  53. package/dist/endpoints/customEndpointHandler.js.map +1 -0
  54. package/dist/endpoints/getOptInChannels.d.ts +19 -0
  55. package/dist/endpoints/getOptInChannels.js +42 -0
  56. package/dist/endpoints/getOptInChannels.js.map +1 -0
  57. package/dist/endpoints/logout.d.ts +20 -0
  58. package/dist/endpoints/logout.js +60 -0
  59. package/dist/endpoints/logout.js.map +1 -0
  60. package/dist/endpoints/requestMagicLink.d.ts +20 -0
  61. package/dist/endpoints/requestMagicLink.js +122 -0
  62. package/dist/endpoints/requestMagicLink.js.map +1 -0
  63. package/dist/endpoints/subscribe.d.ts +24 -0
  64. package/dist/endpoints/subscribe.js +343 -0
  65. package/dist/endpoints/subscribe.js.map +1 -0
  66. package/dist/endpoints/subscriberAuth.d.ts +22 -0
  67. package/dist/endpoints/subscriberAuth.js +69 -0
  68. package/dist/endpoints/subscriberAuth.js.map +1 -0
  69. package/dist/endpoints/verifyMagicLink.d.ts +20 -0
  70. package/dist/endpoints/verifyMagicLink.js +142 -0
  71. package/dist/endpoints/verifyMagicLink.js.map +1 -0
  72. package/dist/exports/client.d.ts +1 -0
  73. package/dist/exports/client.js +3 -0
  74. package/dist/exports/client.js.map +1 -0
  75. package/dist/exports/index.d.ts +1 -0
  76. package/dist/exports/index.js +3 -0
  77. package/dist/exports/index.js.map +1 -0
  78. package/dist/exports/rsc.d.ts +1 -0
  79. package/dist/exports/rsc.js +3 -0
  80. package/dist/exports/rsc.js.map +1 -0
  81. package/dist/exports/ui.d.ts +11 -0
  82. package/dist/exports/ui.js +9 -0
  83. package/dist/exports/ui.js.map +1 -0
  84. package/dist/helpers/serverConfig.d.ts +4 -0
  85. package/dist/helpers/serverConfig.js +22 -0
  86. package/dist/helpers/serverConfig.js.map +1 -0
  87. package/dist/helpers/testData.d.ts +2 -0
  88. package/dist/helpers/testData.js +4 -0
  89. package/dist/helpers/testData.js.map +1 -0
  90. package/dist/helpers/token.d.ts +9 -0
  91. package/dist/helpers/token.js +20 -0
  92. package/dist/helpers/token.js.map +1 -0
  93. package/dist/helpers/verifyOptIns.d.ts +5 -0
  94. package/dist/helpers/verifyOptIns.js +33 -0
  95. package/dist/helpers/verifyOptIns.js.map +1 -0
  96. package/dist/index.d.ts +26 -0
  97. package/dist/index.js +147 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/react-hooks/useServerUrl.d.ts +3 -0
  100. package/dist/react-hooks/useServerUrl.js +19 -0
  101. package/dist/react-hooks/useServerUrl.js.map +1 -0
  102. package/dist/server-functions/serverUrl.d.ts +3 -0
  103. package/dist/server-functions/serverUrl.js +31 -0
  104. package/dist/server-functions/serverUrl.js.map +1 -0
  105. package/dist/server-functions/subscriberAuth.d.ts +11 -0
  106. package/package.json +94 -0
  107. package/src/collections/OptInChannels.ts +45 -0
  108. package/src/collections/Subscribers.ts +99 -0
  109. package/src/collections/fields/OptedInChannels.ts +12 -0
  110. package/src/components/app/RequestMagicLink.tsx +129 -0
  111. package/src/components/app/RequestOrSubscribe.tsx +58 -0
  112. package/src/components/app/SelectOptInChannels.tsx +147 -0
  113. package/src/components/app/Subscribe.tsx +190 -0
  114. package/src/components/app/SubscriberMenu.tsx +46 -0
  115. package/src/components/app/VerifyMagicLink.tsx +197 -0
  116. package/src/components/app/helpers.ts +6 -0
  117. package/src/components/app/shared.module.css +14 -0
  118. package/src/contexts/SubscriberProvider.tsx +122 -0
  119. package/src/copied/payload-types.ts +478 -0
  120. package/src/endpoints/getOptInChannels.ts +56 -0
  121. package/src/endpoints/logout.ts +104 -0
  122. package/src/endpoints/requestMagicLink.ts +139 -0
  123. package/src/endpoints/subscribe.ts +435 -0
  124. package/src/endpoints/subscriberAuth.ts +100 -0
  125. package/src/endpoints/verifyMagicLink.ts +164 -0
  126. package/src/exports/index.ts +1 -0
  127. package/src/exports/ui.ts +17 -0
  128. package/src/helpers/testData.ts +2 -0
  129. package/src/helpers/token.ts +14 -0
  130. package/src/helpers/verifyOptIns.ts +39 -0
  131. package/src/index.ts +207 -0
  132. package/src/react-hooks/useServerUrl.tsx +18 -0
  133. 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,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare const OptInChannels: CollectionConfig;
3
+ export default OptInChannels;
@@ -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,2 @@
1
+ import type { Field } from 'payload';
2
+ export declare const OptedInChannels: Field;
@@ -0,0 +1,12 @@
1
+ export const OptedInChannels = {
2
+ name: 'optIns',
3
+ type: 'relationship',
4
+ admin: {
5
+ position: 'sidebar'
6
+ },
7
+ hasMany: true,
8
+ label: 'Opted-in channels',
9
+ relationTo: 'opt-in-channels'
10
+ };
11
+
12
+ //# sourceMappingURL=OptedInChannels.js.map
@@ -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,2 @@
1
+ import type { ServerComponentProps } from 'payload';
2
+ export declare const BeforeDashboardServer: (props: ServerComponentProps) => Promise<import("react").JSX.Element>;
@@ -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,5 @@
1
+ .wrapper {
2
+ display: flex;
3
+ gap: 5px;
4
+ flex-direction: column;
5
+ }
@@ -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;