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
|
@@ -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,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
|
+
}
|