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,190 @@
1
+ 'use client'
2
+
3
+ import { PayloadSDK } from '@payloadcms/sdk'
4
+ import { type ChangeEvent, useEffect, useState } from 'react'
5
+
6
+ import type { Config, OptInChannel } from '../../copied/payload-types.js'
7
+ import type { SubscribeResponse } from '../../endpoints/subscribe.js'
8
+
9
+ export { SubscribeResponse }
10
+
11
+ import { useSubscriber } from '../../contexts/SubscriberProvider.js'
12
+ import { useServerUrl } from '../../react-hooks/useServerUrl.js'
13
+ import { mergeClassNames } from './helpers.js'
14
+ import { SelectOptInChannels } from './SelectOptInChannels.js'
15
+ import styles from './shared.module.css'
16
+
17
+ // const payload = await getPayload({
18
+ // config: configPromise,
19
+ // })
20
+
21
+ // Pass your config from generated types as generic
22
+
23
+ export interface ISubscribe {
24
+ classNames?: SubscribeClasses
25
+ handleSubscribe?: (result: SubscribeResponse) => void
26
+ props?: any
27
+ }
28
+
29
+ export type SubscribeClasses = {
30
+ button?: string
31
+ container?: string
32
+ emailInput?: string
33
+ error?: string
34
+ form?: string
35
+ loading?: string
36
+ message?: string
37
+ section?: string
38
+ }
39
+
40
+ type statusValues = 'default' | 'error' | 'sent' | 'updated'
41
+
42
+ export const Subscribe = ({
43
+ classNames = {
44
+ button: '',
45
+ container: '',
46
+ emailInput: '',
47
+ error: '',
48
+ form: '',
49
+ loading: '',
50
+ message: '',
51
+ section: '',
52
+ },
53
+ handleSubscribe,
54
+ }: ISubscribe) => {
55
+ const { refreshSubscriber, subscriber } = useSubscriber()
56
+
57
+ const { serverURL } = useServerUrl()
58
+
59
+ const sdk = new PayloadSDK<Config>({
60
+ baseURL: serverURL || '',
61
+ })
62
+
63
+ const flattenChannels = (channels: (OptInChannel | string)[] | null | undefined) => {
64
+ if (!channels) {
65
+ return []
66
+ }
67
+ return channels.map((channel: OptInChannel | string) =>
68
+ typeof channel == 'string' ? channel : channel.id,
69
+ )
70
+ }
71
+
72
+ const [status, setStatus] = useState<statusValues>('default')
73
+ const [result, setResult] = useState<string>()
74
+ const [email, setEmail] = useState(subscriber ? subscriber.email : '')
75
+ const [selectedChannelIDs, setSelectedChannelIDs] = useState<string[]>(() =>
76
+ flattenChannels(subscriber?.optIns),
77
+ )
78
+
79
+ useEffect(() => {
80
+ setEmail(subscriber?.email || '')
81
+ setSelectedChannelIDs(flattenChannels(subscriber?.optIns))
82
+ }, [subscriber])
83
+
84
+ const handleOptInChannelsSelected = (result: OptInChannel[]) => {
85
+ setSelectedChannelIDs(result.map((channel) => channel.id))
86
+ }
87
+
88
+ const handleSubmit = async () => {
89
+ const subscribeResult = await sdk.request({
90
+ json: {
91
+ afterVerifyUrl: window.location.pathname + '?now=' + new Date().toISOString(),
92
+ email,
93
+ optIns: selectedChannelIDs,
94
+ },
95
+ method: 'POST',
96
+ path: '/api/subscribe',
97
+ })
98
+ if (subscribeResult.ok) {
99
+ const resultJson: SubscribeResponse = await subscribeResult.json()
100
+ // // When subscriber optIns are updated...
101
+ // | {
102
+ // email: string
103
+ // now: string
104
+ // optIns: string[]
105
+ // }
106
+ // // When a verify link is emailed...
107
+ // | {
108
+ // emailResult: any
109
+ // now: string
110
+ // }
111
+ // // When any error occurs...
112
+ // | {
113
+ // error: string
114
+ // now: string
115
+ // }
116
+ // @ts-expect-error Silly type confusion
117
+ const { emailResult, error } = resultJson
118
+ if (error) {
119
+ setStatus('error')
120
+ setResult(`An error occured. Please try again. \n ${error}`)
121
+ } else if (emailResult) {
122
+ setStatus('sent')
123
+ setResult('An email has been sent containing your magic link.')
124
+ } else if (email) {
125
+ setStatus('updated')
126
+ setResult(`You're subscriptions have been updated.`)
127
+ } else {
128
+ setStatus('error')
129
+ setResult(`An error occured. Please try again. \nResult unknown`)
130
+ }
131
+
132
+ refreshSubscriber()
133
+
134
+ if (handleSubscribe) {
135
+ handleSubscribe(resultJson)
136
+ }
137
+ } else {
138
+ // const resultText = await subscribeResult.text()
139
+ setStatus('error')
140
+ setResult(`An error occured. Please try again. \nResult unknown`)
141
+ }
142
+ }
143
+
144
+ return (
145
+ <div className={mergeClassNames([styles.container, classNames.container])}>
146
+ <h2>Subscribe</h2>
147
+ <div className={mergeClassNames([styles.section, classNames.section])}>
148
+ <SelectOptInChannels
149
+ handleOptInChannelsSelected={handleOptInChannelsSelected}
150
+ selectedOptInChannelIDs={selectedChannelIDs}
151
+ />
152
+ </div>
153
+ <form
154
+ className={mergeClassNames([styles.form, classNames.form])}
155
+ method="POST"
156
+ onSubmit={async (e) => {
157
+ e.preventDefault()
158
+ await handleSubmit()
159
+ }}
160
+ >
161
+ <div className={mergeClassNames([styles.section, classNames.section])}>
162
+ {!subscriber && (
163
+ <input
164
+ aria-label="enter your email"
165
+ className={mergeClassNames([styles.emailInput, classNames.emailInput])}
166
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setEmail(e.target.value)}
167
+ placeholder="enter your email"
168
+ type="email"
169
+ value={email}
170
+ />
171
+ )}
172
+ <button className={mergeClassNames([styles.button, classNames.button])} type="submit">
173
+ Save choices
174
+ </button>
175
+ </div>
176
+ </form>
177
+ {!!result && (
178
+ <p
179
+ className={mergeClassNames([
180
+ styles.message,
181
+ classNames.message,
182
+ status == 'error' ? [styles.error, classNames.error] : [],
183
+ ])}
184
+ >
185
+ {result}
186
+ </p>
187
+ )}
188
+ </div>
189
+ )
190
+ }
@@ -0,0 +1,46 @@
1
+ 'use client'
2
+
3
+ import { useSubscriber } from '../../contexts/SubscriberProvider.js'
4
+
5
+ import { mergeClassNames } from './helpers.js'
6
+ import styles from './shared.module.css'
7
+
8
+ // interface IAuth {
9
+ // props?: any
10
+ // }
11
+
12
+ export type SubscriberMenuClasses = {
13
+ button?: string
14
+ container?: string
15
+ }
16
+
17
+ export const SubscriberMenu = ({
18
+ classNames = {
19
+ button: '',
20
+ container: '',
21
+ },
22
+ }: {
23
+ classNames?: SubscriberMenuClasses
24
+ }) => {
25
+ const { logOut, subscriber } = useSubscriber()
26
+ return (
27
+ <div className={mergeClassNames([styles.container, classNames.container])}>
28
+ {/* <pre>{JSON.stringify(result, null, 2)}</pre> */}
29
+ {subscriber && (
30
+ <div>
31
+ Welcome, {subscriber?.email} - <a href={'/subscribe'}>Manage subscriptions</a> -{' '}
32
+ <button
33
+ className={mergeClassNames([styles.button, classNames.button])}
34
+ onClick={(e) => {
35
+ e.preventDefault()
36
+ logOut()
37
+ }}
38
+ type="button"
39
+ >
40
+ Log out
41
+ </button>
42
+ </div>
43
+ )}
44
+ </div>
45
+ )
46
+ }
@@ -0,0 +1,197 @@
1
+ 'use client'
2
+
3
+ import { PayloadSDK } from '@payloadcms/sdk'
4
+ import { useSearchParams } from 'next/navigation.js'
5
+ import { useCallback, useEffect, useState } from 'react'
6
+
7
+ import type { RequestMagicLinkResponse } from '../..//endpoints/requestMagicLink.js'
8
+ import type { Config } from '../../copied/payload-types.js'
9
+ import type { VerifyMagicLinkResponse } from '../../endpoints/verifyMagicLink.js'
10
+
11
+ export { VerifyMagicLinkResponse }
12
+ import { useSubscriber } from '../../exports/ui.js'
13
+ import { useServerUrl } from '../../react-hooks/useServerUrl.js'
14
+ import { mergeClassNames } from './helpers.js'
15
+ import styles from './shared.module.css'
16
+
17
+ // const payload = await getPayload({
18
+ // config: configPromise,
19
+ // })
20
+
21
+ // Pass your config from generated types as generic
22
+
23
+ export interface IVerifyMagicLink {
24
+ classNames?: VerifyMagicLinkClasses
25
+ handleMagicLinkRequested?: (result: RequestMagicLinkResponse) => void
26
+ handleMagicLinkVerified?: (result: VerifyMagicLinkResponse) => void
27
+ renderButton?: (props: {
28
+ forwardUrl?: string
29
+ name?: string
30
+ onClick?: () => any
31
+ text?: string
32
+ }) => React.ReactNode
33
+ }
34
+
35
+ export type VerifyMagicLinkClasses = {
36
+ button?: string
37
+ container?: string
38
+ error?: string
39
+ form?: string
40
+ loading?: string
41
+ message?: string
42
+ }
43
+
44
+ export const VerifyMagicLink = ({
45
+ classNames = {
46
+ button: '',
47
+ container: '',
48
+ error: '',
49
+ form: '',
50
+ loading: '',
51
+ message: '',
52
+ },
53
+ handleMagicLinkRequested,
54
+ handleMagicLinkVerified,
55
+ renderButton = ({ name, forwardUrl, onClick, text }) =>
56
+ forwardUrl ? (
57
+ <a href={forwardUrl}>
58
+ <button
59
+ className={mergeClassNames([styles.button, classNames.button])}
60
+ name={name}
61
+ type="button"
62
+ >
63
+ {text}
64
+ </button>
65
+ </a>
66
+ ) : (
67
+ <button
68
+ className={mergeClassNames([styles.button, classNames.button])}
69
+ name={name}
70
+ onClick={onClick}
71
+ type="button"
72
+ >
73
+ {text}
74
+ </button>
75
+ ),
76
+ }: IVerifyMagicLink) => {
77
+ const { serverURL } = useServerUrl()
78
+ const {
79
+ // refreshSubscriber,
80
+ subscriber,
81
+ } = useSubscriber()
82
+
83
+ const searchParams = useSearchParams()
84
+ const email = searchParams.get('email')
85
+ const forwardUrl = searchParams.get('forwardUrl')
86
+ const token = searchParams.get('token')
87
+
88
+ const [result, setResult] = useState<string>()
89
+ const [isError, setIsError] = useState<boolean>(false)
90
+ // const [email, setEmail] = useState('')
91
+
92
+ const { refreshSubscriber } = useSubscriber()
93
+
94
+ const callVerify = useCallback(async () => {
95
+ const sdk = new PayloadSDK<Config>({
96
+ baseURL: serverURL || '',
97
+ })
98
+
99
+ const verifyResult = await sdk.request({
100
+ json: {
101
+ email,
102
+ token,
103
+ },
104
+ method: 'POST',
105
+ path: '/api/verifyToken',
106
+ })
107
+
108
+ return verifyResult
109
+ }, [email, serverURL, token])
110
+
111
+ useEffect(() => {
112
+ async function verify() {
113
+ const verifyResult = await callVerify()
114
+ if (verifyResult.ok) {
115
+ const resultJson = await verifyResult.json()
116
+ setResult(resultJson.message || resultJson.error)
117
+ setIsError(resultJson.error && !resultJson.message)
118
+
119
+ // // This is causing out of control rendering. Not totally sure why, or of another way to do it.
120
+ // refreshSubscriber()
121
+
122
+ // // This is also causing out of control rendering. Not totally sure why, or of another way to do it.
123
+ // if (handleMagicLinkVerified) {
124
+ // handleMagicLinkVerified(resultJson)
125
+ // }
126
+ } else {
127
+ // const resultText = await verifyResult.text()
128
+ setResult('An error occured. Please try again')
129
+ setIsError(true)
130
+ }
131
+ }
132
+ if (!subscriber) {
133
+ void verify()
134
+ }
135
+ }, [callVerify, serverURL, email, handleMagicLinkVerified, refreshSubscriber, subscriber, token])
136
+
137
+ const handleRequestMagicLink = async () => {
138
+ const sdk = new PayloadSDK<Config>({
139
+ baseURL: serverURL || '',
140
+ })
141
+
142
+ const emailResult = await sdk.request({
143
+ json: {
144
+ email,
145
+ forwardUrl,
146
+ },
147
+ method: 'POST',
148
+ path: '/api/emailToken',
149
+ })
150
+ if (emailResult.ok) {
151
+ const resultJson = await emailResult.json()
152
+ setResult('An email has been sent containing your magic link.')
153
+ setIsError(false)
154
+ if (handleMagicLinkRequested) {
155
+ handleMagicLinkRequested(resultJson)
156
+ }
157
+ } else {
158
+ // const resultText = await emailResult.text()
159
+ setResult('An error occured. Please try again.')
160
+ setIsError(true)
161
+ }
162
+ }
163
+ return (
164
+ <div className={mergeClassNames([styles.container, classNames.container])}>
165
+ {!result && (
166
+ <p className={mergeClassNames([styles.loading, classNames.loading])}>verifying...</p>
167
+ )}
168
+ {result && (
169
+ <p
170
+ className={mergeClassNames([
171
+ styles.message,
172
+ classNames.message,
173
+ isError ? [styles.error, classNames.error] : [],
174
+ ])}
175
+ >
176
+ {result}
177
+ </p>
178
+ )}
179
+ <div className={mergeClassNames([styles.form, classNames.form])}>
180
+ {result &&
181
+ isError &&
182
+ renderButton({
183
+ name: 'request',
184
+ onClick: handleRequestMagicLink,
185
+ text: 'Request another magic link',
186
+ })}
187
+ {result &&
188
+ forwardUrl &&
189
+ renderButton({
190
+ name: 'continue',
191
+ forwardUrl,
192
+ text: 'Continue',
193
+ })}
194
+ </div>
195
+ </div>
196
+ )
197
+ }
@@ -0,0 +1,6 @@
1
+ export const mergeClassNames = (classNames: ((string | undefined)[] | string | undefined)[]) => {
2
+ return classNames
3
+ .flat(Infinity)
4
+ .filter((className) => !!className)
5
+ .join(' ')
6
+ }
@@ -0,0 +1,14 @@
1
+ .container {
2
+ display: flex;
3
+ gap: 5px;
4
+ flex-direction: column;
5
+ }
6
+
7
+ .section {
8
+ margin-bottom: 14px;
9
+ margin-top: 14px;
10
+ }
11
+
12
+ .error {
13
+ color: red;
14
+ }
@@ -0,0 +1,122 @@
1
+ 'use client'
2
+
3
+ import { PayloadSDK } from '@payloadcms/sdk'
4
+ import { type ReactNode, useCallback, useEffect } from 'react'
5
+ import { createContext, useContext, useMemo, useState } from 'react'
6
+
7
+ import type { Config, Subscriber } from '../copied/payload-types.js'
8
+
9
+ import { useServerUrl } from '../react-hooks/useServerUrl.js'
10
+
11
+ export type SubscriberContextType = {
12
+ isLoaded: boolean
13
+ logOut: () => void
14
+ permissions: any
15
+ refreshSubscriber: () => void
16
+ subscriber: null | Subscriber
17
+ }
18
+
19
+ const SubscriberContext = createContext<SubscriberContextType | undefined>(undefined)
20
+
21
+ interface ProviderProps {
22
+ children?: ReactNode // Recommended type for children
23
+ }
24
+
25
+ export function SubscriberProvider({ children }: ProviderProps) {
26
+ // eslint-disable-next-line
27
+ const [subscriber, setSubscriber] = useState<null | (Subscriber & { optIns: string[] })>(null)
28
+
29
+ const { serverURL } = useServerUrl()
30
+
31
+ // Keep track of if the selection content is loaded yet
32
+ const [isLoaded, setIsLoaded] = useState(false)
33
+
34
+ const [permissions, setPermissions] = useState<any>()
35
+
36
+ const refreshSubscriber = useCallback(async () => {
37
+ const initSubscriber = async () => {
38
+ setIsLoaded(false)
39
+ try {
40
+ const sdk = new PayloadSDK<Config>({
41
+ baseURL: serverURL || '',
42
+ })
43
+ const authResponse = await sdk.request({
44
+ json: {},
45
+ method: 'POST',
46
+ path: '/api/subscriberAuth',
47
+ })
48
+
49
+ if (authResponse.ok) {
50
+ // Call the server function to get the user data
51
+ const { permissions, subscriber } = await authResponse.json()
52
+ // console.log(`subscriber = `, subscriber)
53
+ // console.log(`permissions = `, permissions)
54
+ setPermissions(permissions)
55
+ setSubscriber(subscriber)
56
+ } else {
57
+ setPermissions(null)
58
+ setSubscriber(null)
59
+ }
60
+ } catch (error: unknown) {
61
+ console.log(`authResponse error`, error)
62
+ }
63
+ setIsLoaded(true)
64
+ }
65
+ await initSubscriber()
66
+ }, [serverURL])
67
+
68
+ const logOut = useCallback(async () => {
69
+ setIsLoaded(false)
70
+ try {
71
+ // const sdk = new PayloadSDK<Config>({
72
+ // baseURL: serverURL || '',
73
+ // })
74
+ // const logoutResponse = await sdk.request({
75
+ // json: {},
76
+ // method: 'POST',
77
+ // path: '/api/logout',
78
+ // })
79
+ // Unsure why sdk isn't working here
80
+ const logoutResponse = await fetch('/api/logout', {
81
+ method: 'POST',
82
+ })
83
+
84
+ // console.log(`logoutResponse`, logoutResponse)
85
+
86
+ if (logoutResponse.ok) {
87
+ setSubscriber(null)
88
+ setPermissions(null)
89
+ }
90
+ } catch (error: unknown) {
91
+ console.log(`logoutResponse error`, error)
92
+ }
93
+ setIsLoaded(true)
94
+ }, [])
95
+
96
+ useEffect(() => {
97
+ void refreshSubscriber()
98
+ }, [refreshSubscriber]) // Empty dependency array for mount/unmount
99
+
100
+ // Memoize the value to prevent unnecessary re-renders in consumers
101
+ const contextValue: SubscriberContextType = useMemo(
102
+ () => ({
103
+ isLoaded,
104
+ logOut,
105
+ permissions,
106
+ refreshSubscriber,
107
+ subscriber,
108
+ }),
109
+ [isLoaded, logOut, permissions, refreshSubscriber, subscriber],
110
+ )
111
+
112
+ return <SubscriberContext.Provider value={contextValue}>{children}</SubscriberContext.Provider>
113
+ }
114
+
115
+ // Custom hook to easily consume the context and add error handling
116
+ export function useSubscriber() {
117
+ const context = useContext(SubscriberContext)
118
+ if (context === undefined) {
119
+ throw new Error('useSubscriber must be used within a SubscriberProvider')
120
+ }
121
+ return context
122
+ }