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
@@ -0,0 +1,164 @@
1
+ import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'
2
+ import type { Subscriber } from 'src/copied/payload-types.js'
3
+
4
+ import { defaultCollectionSlug } from '../collections/Subscribers.js'
5
+ import { getHash, getTokenAndHash } from '../helpers/token.js'
6
+
7
+ export type VerifyMagicLinkResponse =
8
+ | {
9
+ error: string
10
+ now: string
11
+ }
12
+ | {
13
+ message: string
14
+ now: string
15
+ }
16
+
17
+ /**
18
+ * createEndpointLogout
19
+ * @param options
20
+ * @returns
21
+ *
22
+ * Factory to generate the endpoint config with handler based on input option for subscribersCollectionSlug
23
+ *
24
+ */
25
+ function createEndpointVerifyMagicLink({
26
+ subscribersCollectionSlug = defaultCollectionSlug,
27
+ }: {
28
+ subscribersCollectionSlug: CollectionSlug
29
+ }): Endpoint {
30
+ /**
31
+ * verifyMagicLink Endpoint Handler
32
+ * @param req
33
+ * @data { email, forwardUrl, token }
34
+ * @returns { status: 200, json: {message: string, now: date} }
35
+ * @returns { status: 400, json: {error: ('Bad data' | 'Token not verified' | 'Token expired'), now: date} }
36
+ */
37
+ const verifyMagicLinkHandler: PayloadHandler = async (req) => {
38
+ const reqData = req?.json ? await req.json() : {}
39
+ const { email, token }: { email: string; token: string } = reqData // if by POST reqData
40
+ // const { email, token } = req.routeParams // if by path
41
+
42
+ if (!email || !token) {
43
+ return Response.json(
44
+ { error: 'Bad data', now: new Date().toISOString() } as VerifyMagicLinkResponse,
45
+ { status: 400 },
46
+ )
47
+ }
48
+
49
+ const userResults = await req.payload.find({
50
+ collection: subscribersCollectionSlug,
51
+ where: {
52
+ email: { equals: email },
53
+ },
54
+ })
55
+
56
+ type SubscriberType = {
57
+ // @ts-expect-error Why is this not correct, isn't it how Payload does it?
58
+ collection: subscribersCollectionSlug
59
+ } & Subscriber
60
+
61
+ const user = userResults.docs[0] as SubscriberType
62
+
63
+ if (!user) {
64
+ return Response.json(
65
+ { error: 'Bad data', now: new Date().toISOString() } as VerifyMagicLinkResponse,
66
+ { status: 400 },
67
+ )
68
+ }
69
+
70
+ const { tokenHash } = getHash(token)
71
+
72
+ // req.payload.logger.info(
73
+ // `verifyMagicLinkHandler ${email} \n ${tokenHash} \n ${user.verificationTokenExpires} \n ${user.verificationToken}`,
74
+ // )
75
+ if (!user.verificationTokenExpires || tokenHash != user.verificationToken) {
76
+ // req.payload.logger.info(`Token not verified: ${tokenHash} != ${user.verificationToken}`)
77
+ return Response.json(
78
+ { error: 'Token not verified', now: new Date().toISOString() } as VerifyMagicLinkResponse,
79
+ { status: 400 },
80
+ )
81
+ }
82
+
83
+ if (new Date(Date.now()) > new Date(user.verificationTokenExpires)) {
84
+ return Response.json(
85
+ { error: 'Token expired', now: new Date().toISOString() } as VerifyMagicLinkResponse,
86
+ { status: 400 },
87
+ )
88
+ }
89
+
90
+ // Update user
91
+ await req.payload.update({
92
+ collection: subscribersCollectionSlug,
93
+ data: {
94
+ password: tokenHash,
95
+ },
96
+ where: {
97
+ email: { equals: user.email },
98
+ },
99
+ })
100
+
101
+ // Log the user in via Payload headers
102
+ let headers
103
+ try {
104
+ const loginReq = await fetch(
105
+ `${req.payload.config.serverURL}/api/${subscribersCollectionSlug}/login`,
106
+ {
107
+ body: JSON.stringify({
108
+ email,
109
+ password: tokenHash,
110
+ }),
111
+ credentials: 'include',
112
+ headers: {
113
+ 'Content-Type': 'application/json',
114
+ },
115
+ method: 'POST',
116
+ },
117
+ )
118
+ if (loginReq && loginReq.ok) {
119
+ headers = loginReq.headers
120
+ }
121
+ } catch (error) {
122
+ // console.log(error)
123
+ return Response.json({ error } as VerifyMagicLinkResponse, { status: 400 })
124
+ }
125
+ // console.log('login', headers)
126
+
127
+ const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable
128
+ const data = {
129
+ password: tokenHash2,
130
+ status: 'subscribed' as 'pending' | 'subscribed' | 'unsubscribed' | undefined,
131
+ verificationToken: '',
132
+ verificationTokenExpires: null,
133
+ }
134
+ // Update user
135
+ await req.payload.update({
136
+ collection: subscribersCollectionSlug,
137
+ data,
138
+ where: {
139
+ email: { equals: user.email },
140
+ },
141
+ })
142
+
143
+ return Response.json(
144
+ {
145
+ message: 'Token verified',
146
+ now: new Date().toISOString(),
147
+ } as VerifyMagicLinkResponse,
148
+ { headers },
149
+ )
150
+ }
151
+
152
+ /**
153
+ * verifyMagicLink Endpoint Config
154
+ */
155
+ const verifyMagicLinkEndpoint: Endpoint = {
156
+ handler: verifyMagicLinkHandler,
157
+ method: 'post',
158
+ path: '/verifyToken',
159
+ }
160
+
161
+ return verifyMagicLinkEndpoint
162
+ }
163
+
164
+ export default createEndpointVerifyMagicLink
@@ -0,0 +1 @@
1
+ export * from './ui.js'
@@ -0,0 +1,17 @@
1
+ export type { RequestMagicLinkResponse } from '../components/app/RequestMagicLink.js'
2
+ export { RequestMagicLink } from '../components/app/RequestMagicLink.js'
3
+
4
+ export { RequestOrSubscribe } from '../components/app/RequestOrSubscribe.js'
5
+
6
+ export type { SubscribeResponse } from '../components/app/Subscribe.js'
7
+ export { Subscribe } from '../components/app/Subscribe.js'
8
+
9
+ export { SubscriberMenu } from '../components/app/SubscriberMenu.js'
10
+
11
+ export type { VerifyMagicLinkResponse } from '../components/app/VerifyMagicLink.js'
12
+ export { VerifyMagicLink } from '../components/app/VerifyMagicLink.js'
13
+
14
+ export type { SubscriberContextType } from '../contexts/SubscriberProvider.js'
15
+ export { SubscriberProvider, useSubscriber } from '../contexts/SubscriberProvider.js'
16
+
17
+ export { getServerUrl } from '../server-functions/serverUrl.js'
@@ -0,0 +1,2 @@
1
+ export const testEmail = 'seeded-by-plugin@crume.org'
2
+ export const getTestEmail = () => testEmail
@@ -0,0 +1,14 @@
1
+ import crypto from 'crypto'
2
+
3
+ export const getTokenAndHash = (milliseconds?: number) => {
4
+ const token = crypto.randomBytes(32).toString('hex')
5
+ const tokenHash = crypto.createHash('sha256').update(token).digest('hex')
6
+ const expiresAt = milliseconds ? new Date(Date.now() + milliseconds) : undefined
7
+
8
+ return { expiresAt, token, tokenHash }
9
+ }
10
+
11
+ export const getHash = (token: string) => {
12
+ const tokenHash = crypto.createHash('sha256').update(token).digest('hex')
13
+ return { token, tokenHash }
14
+ }
@@ -0,0 +1,39 @@
1
+ import type { BasePayload, CollectionSlug } from 'payload'
2
+
3
+ import OptInChannels from '../collections/OptInChannels.js'
4
+
5
+ export const verifyOptIns = async (
6
+ payload: BasePayload,
7
+ optIns?: string[],
8
+ ): Promise<{
9
+ invalidOptInsInput: string[] | undefined
10
+ verifiedOptInIDs: string[] | undefined
11
+ }> => {
12
+ if (optIns) {
13
+ //
14
+ // Get all matching OptInChannels
15
+ const optInChannelResults = await payload.find({
16
+ collection: OptInChannels.slug as CollectionSlug,
17
+ where: {
18
+ id: { in: optIns },
19
+ },
20
+ })
21
+ const verifiedOptInIDs: string[] | undefined = optInChannelResults.docs.map(
22
+ (channel) => channel.id,
23
+ )
24
+
25
+ //
26
+ // Separate all non-matching OptInChannels
27
+ const checkInvalidOptInsInput: string[] | undefined = optIns?.filter(
28
+ (channelID) => !verifiedOptInIDs.includes(channelID),
29
+ )
30
+ const invalidOptInsInput: string[] | undefined =
31
+ checkInvalidOptInsInput.length > 0 ? checkInvalidOptInsInput : undefined
32
+
33
+ // req.payload.logger.info(`optIns = ${JSON.stringify(optIns)}`)
34
+ // req.payload.logger.info(`invalidOptInsInput = ${JSON.stringify(invalidOptInsInput)}`)
35
+ // req.payload.logger.info(`verifiedOptInIDs = ${JSON.stringify(verifiedOptInIDs)}`)
36
+ return { invalidOptInsInput, verifiedOptInIDs }
37
+ }
38
+ return { invalidOptInsInput: undefined, verifiedOptInIDs: undefined }
39
+ }
package/src/index.ts ADDED
@@ -0,0 +1,207 @@
1
+ import type { BasePayload, CollectionSlug, Config } from 'payload'
2
+
3
+ import { OptedInChannels } from './collections/fields/OptedInChannels.js'
4
+ import OptInChannels from './collections/OptInChannels.js'
5
+ import {
6
+ defaultTokenExpiration,
7
+ SubscribersCollectionFactory,
8
+ subscribersCollectionFields,
9
+ } from './collections/Subscribers.js'
10
+ import getOptInChannelsEndpoint from './endpoints/getOptInChannels.js'
11
+ import createEndpointLogout from './endpoints/logout.js'
12
+ import createEndpointRequestMagicLink from './endpoints/requestMagicLink.js'
13
+ import createEndpointSubscribe from './endpoints/subscribe.js'
14
+ import createEndpointSubscriberAuth from './endpoints/subscriberAuth.js'
15
+ import createEndpointVerifyMagicLink from './endpoints/verifyMagicLink.js'
16
+ import { getTestEmail } from './helpers/testData.js'
17
+ import { getTokenAndHash } from './helpers/token.js'
18
+
19
+ export type PayloadSubscribersConfig = {
20
+ /**
21
+ * List of collections to add a custom field
22
+ */
23
+ collections?: Partial<Record<CollectionSlug, true>>
24
+ /**
25
+ * Defaults to false-y. When true:
26
+ * - Database schema changes are still made and seeded
27
+ * - APIs return null or undefined success
28
+ * - Admin components are not added
29
+ * - App components return nothing
30
+ */
31
+ disabled?: boolean
32
+ /**
33
+ * The collection to make the subscribers collection
34
+ * - Set auth if not
35
+ * - Add optIns field
36
+ */
37
+ subscribersCollectionSlug?: CollectionSlug
38
+ /**
39
+ * Defaults to 30 minutes
40
+ */
41
+ tokenExpiration?: number
42
+ }
43
+
44
+ export const payloadSubscribersPlugin =
45
+ (pluginOptions: PayloadSubscribersConfig) =>
46
+ (config: Config): Config => {
47
+ if (!config.collections) {
48
+ config.collections = []
49
+ }
50
+
51
+ config.collections.push(OptInChannels)
52
+ let subscribersCollection = pluginOptions.subscribersCollectionSlug
53
+ ? config.collections.find(
54
+ (collection) => collection.slug == pluginOptions.subscribersCollectionSlug,
55
+ )
56
+ : undefined
57
+
58
+ if (subscribersCollection) {
59
+ // Configure the input collection to be the subscribers collection
60
+ config.collections = config.collections.filter(
61
+ (collection) => collection.slug != subscribersCollection?.slug,
62
+ )
63
+ subscribersCollection.fields.push(...subscribersCollectionFields)
64
+ if (!subscribersCollection.auth) {
65
+ subscribersCollection = {
66
+ ...subscribersCollection,
67
+ auth: { tokenExpiration: defaultTokenExpiration },
68
+ }
69
+ }
70
+ if (!subscribersCollection.admin?.useAsTitle) {
71
+ if (!subscribersCollection.admin) {
72
+ subscribersCollection.admin = { useAsTitle: 'email' }
73
+ } else {
74
+ // Throw error? Or override?
75
+ subscribersCollection.admin.useAsTitle = 'email'
76
+ }
77
+ }
78
+ config.collections.push(subscribersCollection)
79
+ } else {
80
+ // Configure the default built-in subscribers collection
81
+ subscribersCollection = SubscribersCollectionFactory({
82
+ slug: pluginOptions.subscribersCollectionSlug,
83
+ tokenExpiration: pluginOptions.tokenExpiration,
84
+ })
85
+ config.collections.push(subscribersCollection)
86
+ }
87
+
88
+ if (pluginOptions.collections) {
89
+ for (const collectionSlug in pluginOptions.collections) {
90
+ const collection = config.collections.find(
91
+ (collection) => collection.slug === collectionSlug,
92
+ )
93
+
94
+ if (collection) {
95
+ collection.fields.push(OptedInChannels)
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * If the plugin is disabled, we still want to keep added collections/fields so the database schema is consistent which is important for migrations.
102
+ * If your plugin heavily modifies the database schema, you may want to remove this property.
103
+ */
104
+ if (pluginOptions.disabled) {
105
+ return config
106
+ }
107
+
108
+ if (!config.admin) {
109
+ config.admin = {}
110
+ }
111
+
112
+ if (!config.admin.components) {
113
+ config.admin.components = {}
114
+ }
115
+
116
+ if (!config.admin.components.beforeDashboard) {
117
+ config.admin.components.beforeDashboard = []
118
+ }
119
+
120
+ if (!config.endpoints) {
121
+ config.endpoints = []
122
+ }
123
+
124
+ config.endpoints.push(
125
+ getOptInChannelsEndpoint,
126
+ createEndpointLogout({
127
+ subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,
128
+ }),
129
+ createEndpointRequestMagicLink({
130
+ subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,
131
+ }),
132
+ createEndpointSubscribe({
133
+ subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,
134
+ }),
135
+ createEndpointSubscriberAuth({
136
+ subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,
137
+ }),
138
+ createEndpointVerifyMagicLink({
139
+ subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,
140
+ }),
141
+ )
142
+
143
+ const incomingOnInit = config.onInit
144
+
145
+ const genInit = (testData: { testEmail: string }) => async (payload: BasePayload) => {
146
+ // Ensure we are executing any existing onInit functions before running our own.
147
+ if (incomingOnInit) {
148
+ await incomingOnInit(payload)
149
+ }
150
+
151
+ // console.log('Object.keys(payload.collections)', Object.keys(payload.collections))
152
+ const { totalDocs: totalOptIns } = await payload.count({
153
+ collection: 'opt-in-channels',
154
+ where: {
155
+ title: {
156
+ equals: 'seeded-by-plugin',
157
+ },
158
+ },
159
+ })
160
+
161
+ if (totalOptIns === 0) {
162
+ await payload.create({
163
+ collection: 'opt-in-channels',
164
+ data: {
165
+ active: true,
166
+ title: 'seeded-by-plugin',
167
+ },
168
+ })
169
+ }
170
+
171
+ // const { seededChannel } = await payload.find({
172
+ // collection: 'opt-in-channels',
173
+ // where: {
174
+ // title: {
175
+ // equals: 'seeded-by-plugin',
176
+ // },
177
+ // },
178
+ // })
179
+
180
+ const { totalDocs: totalSubscribers } = await payload.count({
181
+ collection: subscribersCollection.slug as CollectionSlug,
182
+ where: {
183
+ email: {
184
+ equals: testData.testEmail,
185
+ },
186
+ },
187
+ })
188
+
189
+ const { tokenHash } = getTokenAndHash() // Unknowable
190
+ // payload.logger.info(`testData.testEmail == '${testData.testEmail}'`)
191
+ if (totalSubscribers === 0) {
192
+ await payload.create({
193
+ collection: subscribersCollection.slug as CollectionSlug,
194
+ data: {
195
+ email: testData.testEmail,
196
+ password: tokenHash,
197
+ status: 'pending',
198
+ },
199
+ })
200
+ }
201
+ }
202
+
203
+ // console.log(`getTestEmail == '${getTestEmail()}'`)
204
+ config.onInit = genInit({ testEmail: getTestEmail() })
205
+
206
+ return config
207
+ }
@@ -0,0 +1,18 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState } from 'react'
4
+
5
+ import { getServerUrl } from '../server-functions/serverUrl.js'
6
+
7
+ // Custom hook to easily consume the context and add error handling
8
+ export function useServerUrl() {
9
+ const [serverURL, setServerURL] = useState<string>()
10
+ useEffect(() => {
11
+ const fetchServerUrl = async () => {
12
+ const { serverURL } = await getServerUrl()
13
+ setServerURL(serverURL)
14
+ }
15
+ void fetchServerUrl()
16
+ }, [])
17
+ return { serverURL }
18
+ }
@@ -0,0 +1,38 @@
1
+ 'use server'
2
+
3
+ const getServerSideURL = () => {
4
+ const serverSideURL = process.env.NEXT_PUBLIC_VERCEL_URL
5
+ ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
6
+ : process.env.VERCEL_PROJECT_PRODUCTION_URL
7
+ ? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
8
+ : process.env.NEXT_PUBLIC_DEV_URL
9
+ ? `http://${process.env.NEXT_PUBLIC_DEV_URL}`
10
+ : 'http://localhost:3000'
11
+ // console.log(`process.env.NEXT_PUBLIC_DEV_URL: ${process.env.NEXT_PUBLIC_DEV_URL}`)
12
+ // console.log(`serverSideURL: ${serverSideURL}`)
13
+ return serverSideURL
14
+ }
15
+
16
+ // const canUseDOM = !!(
17
+ // typeof window !== 'undefined' &&
18
+ // window.document &&
19
+ // window.document.createElement
20
+ // )
21
+
22
+ // const getClientSideURL = () => {
23
+ // if (canUseDOM) {
24
+ // const protocol = window.location.protocol
25
+ // const domain = window.location.hostname
26
+ // const port = window.location.port
27
+ // // `${window.location.protocol}//${window.location.host}
28
+ // const clientSideURL = `${protocol}//${domain}${port ? `:${port}` : ''}`
29
+ // // console.log(`clientSideURL: ${clientSideURL}`)
30
+ // return clientSideURL
31
+ // }
32
+
33
+ // return getServerSideURL()
34
+ // }
35
+
36
+ export const getServerUrl = async (): Promise<{ serverURL: string }> => {
37
+ return { serverURL: getServerSideURL() }
38
+ }