payload-subscribers-plugin 0.0.7 → 0.0.9
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 +83 -6
- package/dist/components/app/RequestMagicLink.d.ts +12 -1
- package/dist/components/app/RequestMagicLink.js +59 -31
- package/dist/components/app/RequestMagicLink.js.map +1 -1
- package/dist/components/app/RequestOrSubscribe.d.ts +21 -6
- package/dist/components/app/RequestOrSubscribe.js +13 -1
- package/dist/components/app/RequestOrSubscribe.js.map +1 -1
- package/dist/components/app/SelectOptInChannels.d.ts +9 -0
- package/dist/components/app/SelectOptInChannels.js +7 -1
- package/dist/components/app/SelectOptInChannels.js.map +1 -1
- package/dist/components/app/Subscribe.d.ts +11 -1
- package/dist/components/app/Subscribe.js +42 -9
- package/dist/components/app/Subscribe.js.map +1 -1
- package/dist/components/app/SubscriberMenu.d.ts +16 -3
- package/dist/components/app/SubscriberMenu.js +26 -17
- package/dist/components/app/SubscriberMenu.js.map +1 -1
- package/dist/components/app/Unsubscribe.d.ts +31 -0
- package/dist/components/app/Unsubscribe.js +155 -0
- package/dist/components/app/Unsubscribe.js.map +1 -0
- package/dist/components/app/VerifyMagicLink.d.ts +11 -1
- package/dist/components/app/VerifyMagicLink.js +19 -7
- package/dist/components/app/VerifyMagicLink.js.map +1 -1
- package/dist/components/app/helpers.d.ts +8 -0
- package/dist/components/app/helpers.js +8 -1
- package/dist/components/app/helpers.js.map +1 -1
- package/dist/components/app/shared.module.css +10 -1
- package/dist/contexts/SubscriberProvider.d.ts +16 -0
- package/dist/contexts/SubscriberProvider.js +14 -3
- package/dist/contexts/SubscriberProvider.js.map +1 -1
- package/dist/endpoints/getOptInChannels.d.ts +6 -2
- package/dist/endpoints/getOptInChannels.js +6 -2
- package/dist/endpoints/getOptInChannels.js.map +1 -1
- package/dist/endpoints/logout.d.ts +5 -5
- package/dist/endpoints/logout.js +6 -8
- package/dist/endpoints/logout.js.map +1 -1
- package/dist/endpoints/requestMagicLink.d.ts +8 -6
- package/dist/endpoints/requestMagicLink.js +24 -23
- package/dist/endpoints/requestMagicLink.js.map +1 -1
- package/dist/endpoints/subscribe.d.ts +6 -5
- package/dist/endpoints/subscribe.js +34 -21
- package/dist/endpoints/subscribe.js.map +1 -1
- package/dist/endpoints/subscriberAuth.d.ts +5 -5
- package/dist/endpoints/subscriberAuth.js +11 -12
- package/dist/endpoints/subscriberAuth.js.map +1 -1
- package/dist/endpoints/unsubscribe.d.ts +21 -0
- package/dist/endpoints/unsubscribe.js +118 -0
- package/dist/endpoints/unsubscribe.js.map +1 -0
- package/dist/endpoints/verifyMagicLink.d.ts +5 -5
- package/dist/endpoints/verifyMagicLink.js +14 -15
- package/dist/endpoints/verifyMagicLink.js.map +1 -1
- package/dist/exports/ui.d.ts +2 -0
- package/dist/exports/ui.js +1 -0
- package/dist/exports/ui.js.map +1 -1
- package/dist/helpers/token.d.ts +3 -0
- package/dist/helpers/token.js +12 -2
- package/dist/helpers/token.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -7,7 +7,14 @@ import { RequestMagicLink, useSubscriber } from '../../exports/ui.js';
|
|
|
7
7
|
import { useServerUrl } from '../../react-hooks/useServerUrl.js';
|
|
8
8
|
import { mergeClassNames } from './helpers.js';
|
|
9
9
|
import styles from './shared.module.css';
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Handles the verify step of magic-link flow. When URL has email and token query params, calls
|
|
12
|
+
* POST /api/verifyToken to verify and log in; otherwise shows RequestMagicLink. Supports
|
|
13
|
+
* "Request another magic link" via renderButton and optional callbacks.
|
|
14
|
+
*
|
|
15
|
+
* @param props - See IVerifyMagicLink
|
|
16
|
+
* @returns RequestMagicLink when no token/email; otherwise verifying state, result message, and optional button/children
|
|
17
|
+
*/ export const VerifyMagicLink = ({ children, classNames = {
|
|
11
18
|
button: '',
|
|
12
19
|
container: '',
|
|
13
20
|
emailInput: '',
|
|
@@ -17,6 +24,7 @@ export const VerifyMagicLink = ({ children, classNames = {
|
|
|
17
24
|
message: ''
|
|
18
25
|
}, handleMagicLinkRequested, handleMagicLinkVerified, renderButton = ({ name, onClick, text })=>/*#__PURE__*/ _jsx("button", {
|
|
19
26
|
className: mergeClassNames([
|
|
27
|
+
'subscribers-button',
|
|
20
28
|
styles.button,
|
|
21
29
|
classNames.button
|
|
22
30
|
]),
|
|
@@ -25,6 +33,9 @@ export const VerifyMagicLink = ({ children, classNames = {
|
|
|
25
33
|
type: "button",
|
|
26
34
|
children: text
|
|
27
35
|
}), verifyUrl })=>{
|
|
36
|
+
if (typeof verifyUrl == 'string') {
|
|
37
|
+
verifyUrl = new URL(verifyUrl);
|
|
38
|
+
}
|
|
28
39
|
const { serverURL } = useServerUrl();
|
|
29
40
|
const { // refreshSubscriber,
|
|
30
41
|
subscriber } = useSubscriber();
|
|
@@ -37,7 +48,6 @@ export const VerifyMagicLink = ({ children, classNames = {
|
|
|
37
48
|
const { refreshSubscriber } = useSubscriber();
|
|
38
49
|
const callVerify = useCallback(async ()=>{
|
|
39
50
|
if (!email || !token) {
|
|
40
|
-
console.info('Invalid input');
|
|
41
51
|
return {
|
|
42
52
|
error: 'Invalid input'
|
|
43
53
|
};
|
|
@@ -47,7 +57,7 @@ export const VerifyMagicLink = ({ children, classNames = {
|
|
|
47
57
|
// returns a not-okay status, PayloadSDK.request returns its
|
|
48
58
|
// own "Bad request" error, and doesn't share the endpoint
|
|
49
59
|
// result data.
|
|
50
|
-
const verifyEndpointResult = await fetch(serverURL
|
|
60
|
+
const verifyEndpointResult = await fetch(`${serverURL ? serverURL : ''}/api/verifyToken`, {
|
|
51
61
|
body: JSON.stringify({
|
|
52
62
|
email,
|
|
53
63
|
token
|
|
@@ -56,26 +66,22 @@ export const VerifyMagicLink = ({ children, classNames = {
|
|
|
56
66
|
});
|
|
57
67
|
// return verifyEndpointResult
|
|
58
68
|
if (verifyEndpointResult && verifyEndpointResult.json) {
|
|
59
|
-
console.log(1);
|
|
60
69
|
const resultJson = await verifyEndpointResult.json();
|
|
61
70
|
return {
|
|
62
71
|
error: resultJson.error,
|
|
63
72
|
message: resultJson.message
|
|
64
73
|
};
|
|
65
74
|
} else if (verifyEndpointResult && verifyEndpointResult.text) {
|
|
66
|
-
console.log(2);
|
|
67
75
|
const resultText = await verifyEndpointResult.text();
|
|
68
76
|
return {
|
|
69
77
|
error: resultText
|
|
70
78
|
};
|
|
71
79
|
} else {
|
|
72
|
-
console.log(3);
|
|
73
80
|
return {
|
|
74
81
|
error: verifyEndpointResult.status
|
|
75
82
|
};
|
|
76
83
|
}
|
|
77
84
|
} catch (error) {
|
|
78
|
-
console.log('catch');
|
|
79
85
|
return {
|
|
80
86
|
error
|
|
81
87
|
};
|
|
@@ -88,6 +94,7 @@ export const VerifyMagicLink = ({ children, classNames = {
|
|
|
88
94
|
useEffect(()=>{
|
|
89
95
|
async function verify() {
|
|
90
96
|
const { error, message } = await callVerify();
|
|
97
|
+
console.log(`Unknown error: (${error})`);
|
|
91
98
|
setResult(message || `An error occured. Please try again. (${error})`);
|
|
92
99
|
setIsError(error && !message);
|
|
93
100
|
// console.info('callVerify not okay', { error, message })
|
|
@@ -136,12 +143,14 @@ export const VerifyMagicLink = ({ children, classNames = {
|
|
|
136
143
|
}),
|
|
137
144
|
email && token && /*#__PURE__*/ _jsxs("div", {
|
|
138
145
|
className: mergeClassNames([
|
|
146
|
+
'subscribers-verify subscribers-container',
|
|
139
147
|
styles.container,
|
|
140
148
|
classNames.container
|
|
141
149
|
]),
|
|
142
150
|
children: [
|
|
143
151
|
!result && /*#__PURE__*/ _jsx("p", {
|
|
144
152
|
className: mergeClassNames([
|
|
153
|
+
'subscribers-loading',
|
|
145
154
|
styles.loading,
|
|
146
155
|
classNames.loading
|
|
147
156
|
]),
|
|
@@ -149,9 +158,11 @@ export const VerifyMagicLink = ({ children, classNames = {
|
|
|
149
158
|
}),
|
|
150
159
|
result && /*#__PURE__*/ _jsx("p", {
|
|
151
160
|
className: mergeClassNames([
|
|
161
|
+
'subscribers-message',
|
|
152
162
|
styles.message,
|
|
153
163
|
classNames.message,
|
|
154
164
|
isError ? [
|
|
165
|
+
'subscribers-error',
|
|
155
166
|
styles.error,
|
|
156
167
|
classNames.error
|
|
157
168
|
] : []
|
|
@@ -160,6 +171,7 @@ export const VerifyMagicLink = ({ children, classNames = {
|
|
|
160
171
|
}),
|
|
161
172
|
/*#__PURE__*/ _jsxs("div", {
|
|
162
173
|
className: mergeClassNames([
|
|
174
|
+
'subscribers-form',
|
|
163
175
|
styles.form,
|
|
164
176
|
classNames.form
|
|
165
177
|
]),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/app/VerifyMagicLink.tsx"],"sourcesContent":["'use client'\n\nimport { PayloadSDK } from '@payloadcms/sdk'\nimport { useSearchParams } from 'next/navigation.js'\nimport { useCallback, useEffect, useState } from 'react'\n\nimport type { RequestMagicLinkResponse } from '../..//endpoints/requestMagicLink.js'\nimport type { Config } from '../../copied/payload-types.js'\nimport type { VerifyMagicLinkResponse } from '../../endpoints/verifyMagicLink.js'\n\nexport { VerifyMagicLinkResponse }\nimport { RequestMagicLink, useSubscriber } from '../../exports/ui.js'\nimport { useServerUrl } from '../../react-hooks/useServerUrl.js'\nimport { mergeClassNames } from './helpers.js'\nimport styles from './shared.module.css'\n\n// const payload = await getPayload({\n// config: configPromise,\n// })\n\n// Pass your config from generated types as generic\n\nexport interface IVerifyMagicLink {\n children?: React.ReactNode\n classNames?: VerifyMagicLinkClasses\n handleMagicLinkRequested?: (result: RequestMagicLinkResponse) => void\n handleMagicLinkVerified?: (result: VerifyMagicLinkResponse) => void\n renderButton?: (props: { name?: string; onClick?: () => any; text?: string }) => React.ReactNode\n verifyUrl?: URL\n}\n\nexport type VerifyMagicLinkClasses = {\n button?: string\n container?: string\n emailInput?: string\n error?: string\n form?: string\n loading?: string\n message?: string\n}\n\nexport const VerifyMagicLink = ({\n children,\n classNames = {\n button: '',\n container: '',\n emailInput: '',\n error: '',\n form: '',\n loading: '',\n message: '',\n },\n handleMagicLinkRequested,\n handleMagicLinkVerified,\n renderButton = ({ name, onClick, text }) => (\n <button\n className={mergeClassNames([styles.button, classNames.button])}\n name={name}\n onClick={onClick}\n type=\"button\"\n >\n {text}\n </button>\n ),\n verifyUrl,\n}: IVerifyMagicLink) => {\n const { serverURL } = useServerUrl()\n const {\n // refreshSubscriber,\n subscriber,\n } = useSubscriber()\n\n const searchParams = useSearchParams()\n const email = searchParams.get('email')\n const token = searchParams.get('token')\n\n const [result, setResult] = useState<string>()\n const [isError, setIsError] = useState<boolean>(false)\n // const [email, setEmail] = useState('')\n\n const { refreshSubscriber } = useSubscriber()\n\n const callVerify = useCallback(async () => {\n if (!email || !token) {\n console.info('Invalid input')\n return { error: 'Invalid input' }\n }\n try {\n // I tried using PayloadSDK.request, but when the endpoint\n // returns a not-okay status, PayloadSDK.request returns its\n // own \"Bad request\" error, and doesn't share the endpoint\n // result data.\n const verifyEndpointResult = await fetch(serverURL + '/api/verifyToken', {\n body: JSON.stringify({\n email,\n token,\n }),\n method: 'POST',\n })\n\n // return verifyEndpointResult\n if (verifyEndpointResult && verifyEndpointResult.json) {\n console.log(1)\n const resultJson = await verifyEndpointResult.json()\n return { error: resultJson.error, message: resultJson.message }\n } else if (verifyEndpointResult && verifyEndpointResult.text) {\n console.log(2)\n const resultText = await verifyEndpointResult.text()\n return { error: resultText }\n } else {\n console.log(3)\n return { error: verifyEndpointResult.status }\n }\n } catch (error: unknown) {\n console.log('catch')\n return { error }\n }\n }, [email, serverURL, token])\n\n useEffect(() => {\n async function verify() {\n const { error, message } = await callVerify()\n setResult(message || `An error occured. Please try again. (${error})`)\n setIsError(error && !message)\n // console.info('callVerify not okay', { error, message })\n }\n if (!subscriber) {\n void verify()\n }\n }, [callVerify, serverURL, email, handleMagicLinkVerified, refreshSubscriber, subscriber, token])\n\n const handleRequestAnother = async () => {\n const sdk = new PayloadSDK<Config>({\n baseURL: serverURL || '',\n })\n\n const emailResult = await sdk.request({\n json: {\n email,\n verifyUrl: verifyUrl?.href,\n },\n method: 'POST',\n path: '/api/emailToken',\n })\n if (emailResult.ok) {\n const resultJson = await emailResult.json()\n setResult('An email has been sent containing your magic link.')\n setIsError(false)\n if (handleMagicLinkRequested) {\n handleMagicLinkRequested(resultJson)\n }\n } else {\n // const resultText = await emailResult.text()\n setResult('An error occured. Please try again.')\n setIsError(true)\n }\n }\n\n return (\n <>\n {(!email || !token) && <RequestMagicLink classNames={classNames} />}\n {email && token && (\n <div className={mergeClassNames([styles.container, classNames.container])}>\n {!result && (\n <p className={mergeClassNames([styles.loading, classNames.loading])}>verifying...</p>\n )}\n {result && (\n <p\n className={mergeClassNames([\n styles.message,\n classNames.message,\n isError ? [styles.error, classNames.error] : [],\n ])}\n >\n {result}\n </p>\n )}\n <div className={mergeClassNames([styles.form, classNames.form])}>\n {result &&\n isError &&\n renderButton &&\n renderButton({\n name: 'request',\n onClick: handleRequestAnother,\n text: 'Request another magic link',\n })}\n {result && children}\n </div>\n </div>\n )}\n </>\n )\n}\n"],"names":["PayloadSDK","useSearchParams","useCallback","useEffect","useState","RequestMagicLink","useSubscriber","useServerUrl","mergeClassNames","styles","VerifyMagicLink","children","classNames","button","container","emailInput","error","form","loading","message","handleMagicLinkRequested","handleMagicLinkVerified","renderButton","name","onClick","text","className","type","verifyUrl","serverURL","subscriber","searchParams","email","get","token","result","setResult","isError","setIsError","refreshSubscriber","callVerify","console","info","verifyEndpointResult","fetch","body","JSON","stringify","method","json","log","resultJson","resultText","status","verify","handleRequestAnother","sdk","baseURL","emailResult","request","href","path","ok","div","p"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/app/VerifyMagicLink.tsx"],"sourcesContent":["'use client'\n\nimport { PayloadSDK } from '@payloadcms/sdk'\nimport { useSearchParams } from 'next/navigation.js'\nimport { useCallback, useEffect, useState } from 'react'\n\nimport type { RequestMagicLinkResponse } from '../..//endpoints/requestMagicLink.js'\nimport type { Config } from '../../copied/payload-types.js'\nimport type { VerifyMagicLinkResponse } from '../../endpoints/verifyMagicLink.js'\n\nexport { VerifyMagicLinkResponse }\nimport { RequestMagicLink, useSubscriber } from '../../exports/ui.js'\nimport { useServerUrl } from '../../react-hooks/useServerUrl.js'\nimport { mergeClassNames } from './helpers.js'\nimport styles from './shared.module.css'\n\n// const payload = await getPayload({\n// config: configPromise,\n// })\n\n/** Props for the VerifyMagicLink component. */\nexport interface IVerifyMagicLink {\n children?: React.ReactNode\n classNames?: VerifyMagicLinkClasses\n handleMagicLinkRequested?: (result: RequestMagicLinkResponse) => void\n handleMagicLinkVerified?: (result: VerifyMagicLinkResponse) => void\n renderButton?: (props: { name?: string; onClick?: () => any; text?: string }) => React.ReactNode\n verifyUrl?: string | URL\n}\n\n/** Optional CSS class overrides for VerifyMagicLink elements. */\nexport type VerifyMagicLinkClasses = {\n button?: string\n container?: string\n emailInput?: string\n error?: string\n form?: string\n loading?: string\n message?: string\n}\n\n/**\n * Handles the verify step of magic-link flow. When URL has email and token query params, calls\n * POST /api/verifyToken to verify and log in; otherwise shows RequestMagicLink. Supports\n * \"Request another magic link\" via renderButton and optional callbacks.\n *\n * @param props - See IVerifyMagicLink\n * @returns RequestMagicLink when no token/email; otherwise verifying state, result message, and optional button/children\n */\nexport const VerifyMagicLink = ({\n children,\n classNames = {\n button: '',\n container: '',\n emailInput: '',\n error: '',\n form: '',\n loading: '',\n message: '',\n },\n handleMagicLinkRequested,\n handleMagicLinkVerified,\n renderButton = ({ name, onClick, text }) => (\n <button\n className={mergeClassNames(['subscribers-button', styles.button, classNames.button])}\n name={name}\n onClick={onClick}\n type=\"button\"\n >\n {text}\n </button>\n ),\n verifyUrl,\n}: IVerifyMagicLink) => {\n if (typeof verifyUrl == 'string') {\n verifyUrl = new URL(verifyUrl)\n }\n const { serverURL } = useServerUrl()\n const {\n // refreshSubscriber,\n subscriber,\n } = useSubscriber()\n\n const searchParams = useSearchParams()\n const email = searchParams.get('email')\n const token = searchParams.get('token')\n\n const [result, setResult] = useState<string>()\n const [isError, setIsError] = useState<boolean>(false)\n // const [email, setEmail] = useState('')\n\n const { refreshSubscriber } = useSubscriber()\n\n const callVerify = useCallback(async () => {\n if (!email || !token) {\n return { error: 'Invalid input' }\n }\n try {\n // I tried using PayloadSDK.request, but when the endpoint\n // returns a not-okay status, PayloadSDK.request returns its\n // own \"Bad request\" error, and doesn't share the endpoint\n // result data.\n const verifyEndpointResult = await fetch(`${serverURL ? serverURL : ''}/api/verifyToken`, {\n body: JSON.stringify({\n email,\n token,\n }),\n method: 'POST',\n })\n\n // return verifyEndpointResult\n if (verifyEndpointResult && verifyEndpointResult.json) {\n const resultJson = await verifyEndpointResult.json()\n return { error: resultJson.error, message: resultJson.message }\n } else if (verifyEndpointResult && verifyEndpointResult.text) {\n const resultText = await verifyEndpointResult.text()\n return { error: resultText }\n } else {\n return { error: verifyEndpointResult.status }\n }\n } catch (error: unknown) {\n return { error }\n }\n }, [email, serverURL, token])\n\n useEffect(() => {\n async function verify() {\n const { error, message } = await callVerify()\n console.log(`Unknown error: (${error})`)\n setResult(message || `An error occured. Please try again. (${error})`)\n setIsError(error && !message)\n // console.info('callVerify not okay', { error, message })\n }\n if (!subscriber) {\n void verify()\n }\n }, [callVerify, serverURL, email, handleMagicLinkVerified, refreshSubscriber, subscriber, token])\n\n const handleRequestAnother = async () => {\n const sdk = new PayloadSDK<Config>({\n baseURL: serverURL || '',\n })\n\n const emailResult = await sdk.request({\n json: {\n email,\n verifyUrl: verifyUrl?.href,\n },\n method: 'POST',\n path: '/api/emailToken',\n })\n if (emailResult.ok) {\n const resultJson = await emailResult.json()\n setResult('An email has been sent containing your magic link.')\n setIsError(false)\n if (handleMagicLinkRequested) {\n handleMagicLinkRequested(resultJson)\n }\n } else {\n // const resultText = await emailResult.text()\n setResult('An error occured. Please try again.')\n setIsError(true)\n }\n }\n\n return (\n <>\n {(!email || !token) && <RequestMagicLink classNames={classNames} />}\n {email && token && (\n <div\n className={mergeClassNames([\n 'subscribers-verify subscribers-container',\n styles.container,\n classNames.container,\n ])}\n >\n {!result && (\n <p\n className={mergeClassNames([\n 'subscribers-loading',\n styles.loading,\n classNames.loading,\n ])}\n >\n verifying...\n </p>\n )}\n {result && (\n <p\n className={mergeClassNames([\n 'subscribers-message',\n styles.message,\n classNames.message,\n isError ? ['subscribers-error', styles.error, classNames.error] : [],\n ])}\n >\n {result}\n </p>\n )}\n <div className={mergeClassNames(['subscribers-form', styles.form, classNames.form])}>\n {result &&\n isError &&\n renderButton &&\n renderButton({\n name: 'request',\n onClick: handleRequestAnother,\n text: 'Request another magic link',\n })}\n {result && children}\n </div>\n </div>\n )}\n </>\n )\n}\n"],"names":["PayloadSDK","useSearchParams","useCallback","useEffect","useState","RequestMagicLink","useSubscriber","useServerUrl","mergeClassNames","styles","VerifyMagicLink","children","classNames","button","container","emailInput","error","form","loading","message","handleMagicLinkRequested","handleMagicLinkVerified","renderButton","name","onClick","text","className","type","verifyUrl","URL","serverURL","subscriber","searchParams","email","get","token","result","setResult","isError","setIsError","refreshSubscriber","callVerify","verifyEndpointResult","fetch","body","JSON","stringify","method","json","resultJson","resultText","status","verify","console","log","handleRequestAnother","sdk","baseURL","emailResult","request","href","path","ok","div","p"],"mappings":"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"}
|
|
@@ -1 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merges an array of class name values (strings or arrays of strings) into a single space-separated
|
|
3
|
+
* string. Flattens nested arrays and filters out falsy values. Used by app components for
|
|
4
|
+
* combining base classes, CSS module classes, and optional overrides.
|
|
5
|
+
*
|
|
6
|
+
* @param classNames - Array of class name values (string, undefined, or array of same)
|
|
7
|
+
* @returns Single string of non-empty class names separated by spaces
|
|
8
|
+
*/
|
|
1
9
|
export declare const mergeClassNames: (classNames: ((string | undefined)[] | string | undefined)[]) => string;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Merges an array of class name values (strings or arrays of strings) into a single space-separated
|
|
3
|
+
* string. Flattens nested arrays and filters out falsy values. Used by app components for
|
|
4
|
+
* combining base classes, CSS module classes, and optional overrides.
|
|
5
|
+
*
|
|
6
|
+
* @param classNames - Array of class name values (string, undefined, or array of same)
|
|
7
|
+
* @returns Single string of non-empty class names separated by spaces
|
|
8
|
+
*/ export const mergeClassNames = (classNames)=>{
|
|
2
9
|
return classNames.flat(Infinity).filter((className)=>!!className).join(' ');
|
|
3
10
|
};
|
|
4
11
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/app/helpers.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../../src/components/app/helpers.ts"],"sourcesContent":["/**\n * Merges an array of class name values (strings or arrays of strings) into a single space-separated\n * string. Flattens nested arrays and filters out falsy values. Used by app components for\n * combining base classes, CSS module classes, and optional overrides.\n *\n * @param classNames - Array of class name values (string, undefined, or array of same)\n * @returns Single string of non-empty class names separated by spaces\n */\nexport const mergeClassNames = (classNames: ((string | undefined)[] | string | undefined)[]) => {\n return classNames\n .flat(Infinity)\n .filter((className) => !!className)\n .join(' ')\n}\n"],"names":["mergeClassNames","classNames","flat","Infinity","filter","className","join"],"mappings":"AAAA;;;;;;;CAOC,GACD,OAAO,MAAMA,kBAAkB,CAACC;IAC9B,OAAOA,WACJC,IAAI,CAACC,UACLC,MAAM,CAAC,CAACC,YAAc,CAAC,CAACA,WACxBC,IAAI,CAAC;AACV,EAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { Subscriber } from '../copied/payload-types.js';
|
|
3
|
+
/** Value provided by SubscriberProvider: current subscriber, auth state, and actions. */
|
|
3
4
|
export type SubscriberContextType = {
|
|
4
5
|
isLoaded: boolean;
|
|
5
6
|
logOut: () => void;
|
|
@@ -7,9 +8,24 @@ export type SubscriberContextType = {
|
|
|
7
8
|
refreshSubscriber: () => void;
|
|
8
9
|
subscriber: null | Subscriber;
|
|
9
10
|
};
|
|
11
|
+
/** Props for SubscriberProvider. */
|
|
10
12
|
interface ProviderProps {
|
|
11
13
|
children?: ReactNode;
|
|
12
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Provider that fetches and holds the current subscriber auth state (via POST /api/subscriberAuth).
|
|
17
|
+
* Exposes subscriber, permissions, refreshSubscriber, and logOut to descendants. Must wrap any
|
|
18
|
+
* component that uses useSubscriber().
|
|
19
|
+
*
|
|
20
|
+
* @param props.children - React tree to wrap
|
|
21
|
+
* @returns SubscriberContext.Provider with current auth state and actions
|
|
22
|
+
*/
|
|
13
23
|
export declare function SubscriberProvider({ children }: ProviderProps): import("react").JSX.Element;
|
|
24
|
+
/**
|
|
25
|
+
* Consumes SubscriberContext. Use only inside a SubscriberProvider.
|
|
26
|
+
*
|
|
27
|
+
* @returns Current subscriber (or null), permissions, isLoaded, refreshSubscriber, and logOut
|
|
28
|
+
* @throws Error if used outside SubscriberProvider
|
|
29
|
+
*/
|
|
14
30
|
export declare function useSubscriber(): SubscriberContextType;
|
|
15
31
|
export {};
|
|
@@ -4,7 +4,14 @@ import { useCallback, useEffect } from 'react';
|
|
|
4
4
|
import { createContext, useContext, useMemo, useState } from 'react';
|
|
5
5
|
import { useServerUrl } from '../react-hooks/useServerUrl.js';
|
|
6
6
|
const SubscriberContext = /*#__PURE__*/ createContext(undefined);
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Provider that fetches and holds the current subscriber auth state (via POST /api/subscriberAuth).
|
|
9
|
+
* Exposes subscriber, permissions, refreshSubscriber, and logOut to descendants. Must wrap any
|
|
10
|
+
* component that uses useSubscriber().
|
|
11
|
+
*
|
|
12
|
+
* @param props.children - React tree to wrap
|
|
13
|
+
* @returns SubscriberContext.Provider with current auth state and actions
|
|
14
|
+
*/ export function SubscriberProvider({ children }) {
|
|
8
15
|
// eslint-disable-next-line
|
|
9
16
|
const [subscriber, setSubscriber] = useState(null);
|
|
10
17
|
const { serverURL } = useServerUrl();
|
|
@@ -88,8 +95,12 @@ export function SubscriberProvider({ children }) {
|
|
|
88
95
|
children: children
|
|
89
96
|
});
|
|
90
97
|
}
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Consumes SubscriberContext. Use only inside a SubscriberProvider.
|
|
100
|
+
*
|
|
101
|
+
* @returns Current subscriber (or null), permissions, isLoaded, refreshSubscriber, and logOut
|
|
102
|
+
* @throws Error if used outside SubscriberProvider
|
|
103
|
+
*/ export function useSubscriber() {
|
|
93
104
|
const context = useContext(SubscriberContext);
|
|
94
105
|
if (context === undefined) {
|
|
95
106
|
throw new Error('useSubscriber must be used within a SubscriberProvider');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/contexts/SubscriberProvider.tsx"],"sourcesContent":["'use client'\n\nimport { PayloadSDK } from '@payloadcms/sdk'\nimport { type ReactNode, useCallback, useEffect } from 'react'\nimport { createContext, useContext, useMemo, useState } from 'react'\n\nimport type { Config, Subscriber } from '../copied/payload-types.js'\n\nimport { useServerUrl } from '../react-hooks/useServerUrl.js'\n\nexport type SubscriberContextType = {\n isLoaded: boolean\n logOut: () => void\n permissions: any\n refreshSubscriber: () => void\n subscriber: null | Subscriber\n}\n\nconst SubscriberContext = createContext<SubscriberContextType | undefined>(undefined)\n\ninterface ProviderProps {\n children?: ReactNode
|
|
1
|
+
{"version":3,"sources":["../../src/contexts/SubscriberProvider.tsx"],"sourcesContent":["'use client'\n\nimport { PayloadSDK } from '@payloadcms/sdk'\nimport { type ReactNode, useCallback, useEffect } from 'react'\nimport { createContext, useContext, useMemo, useState } from 'react'\n\nimport type { Config, Subscriber } from '../copied/payload-types.js'\n\nimport { useServerUrl } from '../react-hooks/useServerUrl.js'\n\n/** Value provided by SubscriberProvider: current subscriber, auth state, and actions. */\nexport type SubscriberContextType = {\n isLoaded: boolean\n logOut: () => void\n permissions: any\n refreshSubscriber: () => void\n subscriber: null | Subscriber\n}\n\nconst SubscriberContext = createContext<SubscriberContextType | undefined>(undefined)\n\n/** Props for SubscriberProvider. */\ninterface ProviderProps {\n children?: ReactNode\n}\n\n/**\n * Provider that fetches and holds the current subscriber auth state (via POST /api/subscriberAuth).\n * Exposes subscriber, permissions, refreshSubscriber, and logOut to descendants. Must wrap any\n * component that uses useSubscriber().\n *\n * @param props.children - React tree to wrap\n * @returns SubscriberContext.Provider with current auth state and actions\n */\nexport function SubscriberProvider({ children }: ProviderProps) {\n // eslint-disable-next-line\n const [subscriber, setSubscriber] = useState<null | (Subscriber & { optIns: string[] })>(null)\n\n const { serverURL } = useServerUrl()\n\n // Keep track of if the selection content is loaded yet\n const [isLoaded, setIsLoaded] = useState(false)\n\n const [permissions, setPermissions] = useState<any>()\n\n const refreshSubscriber = useCallback(async () => {\n const initSubscriber = async () => {\n setIsLoaded(false)\n try {\n const authResponse = await fetch('/api/subscriberAuth', {\n // body: JSON.stringify({}),\n method: 'POST',\n })\n\n if (authResponse.ok) {\n // Call the server function to get the user data\n const { permissions, subscriber } = await authResponse.json()\n // console.log(`subscriber = `, subscriber)\n // console.log(`permissions = `, permissions)\n setPermissions(permissions)\n setSubscriber(subscriber)\n } else {\n setPermissions(null)\n setSubscriber(null)\n }\n } catch (error: unknown) {\n console.log(`authResponse error`, error)\n }\n setIsLoaded(true)\n }\n await initSubscriber()\n }, [serverURL])\n\n const logOut = useCallback(async () => {\n setIsLoaded(false)\n try {\n // const sdk = new PayloadSDK<Config>({\n // baseURL: serverURL || '',\n // })\n // const logoutResponse = await sdk.request({\n // json: {},\n // method: 'POST',\n // path: '/api/logout',\n // })\n // Unsure why sdk isn't working here\n const logoutResponse = await fetch('/api/logout', {\n method: 'POST',\n })\n\n // console.log(`logoutResponse`, logoutResponse)\n\n if (logoutResponse.ok) {\n setSubscriber(null)\n setPermissions(null)\n }\n } catch (error: unknown) {\n console.log(`logoutResponse error`, error)\n }\n setIsLoaded(true)\n }, [])\n\n useEffect(() => {\n void refreshSubscriber()\n }, [refreshSubscriber]) // Empty dependency array for mount/unmount\n\n // Memoize the value to prevent unnecessary re-renders in consumers\n const contextValue: SubscriberContextType = useMemo(\n () => ({\n isLoaded,\n logOut,\n permissions,\n refreshSubscriber,\n subscriber,\n }),\n [isLoaded, logOut, permissions, refreshSubscriber, subscriber],\n )\n\n return <SubscriberContext.Provider value={contextValue}>{children}</SubscriberContext.Provider>\n}\n\n/**\n * Consumes SubscriberContext. Use only inside a SubscriberProvider.\n *\n * @returns Current subscriber (or null), permissions, isLoaded, refreshSubscriber, and logOut\n * @throws Error if used outside SubscriberProvider\n */\nexport function useSubscriber() {\n const context = useContext(SubscriberContext)\n if (context === undefined) {\n throw new Error('useSubscriber must be used within a SubscriberProvider')\n }\n return context\n}\n"],"names":["useCallback","useEffect","createContext","useContext","useMemo","useState","useServerUrl","SubscriberContext","undefined","SubscriberProvider","children","subscriber","setSubscriber","serverURL","isLoaded","setIsLoaded","permissions","setPermissions","refreshSubscriber","initSubscriber","authResponse","fetch","method","ok","json","error","console","log","logOut","logoutResponse","contextValue","Provider","value","useSubscriber","context","Error"],"mappings":"AAAA;;AAGA,SAAyBA,WAAW,EAAEC,SAAS,QAAQ,QAAO;AAC9D,SAASC,aAAa,EAAEC,UAAU,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAIpE,SAASC,YAAY,QAAQ,iCAAgC;AAW7D,MAAMC,kCAAoBL,cAAiDM;AAO3E;;;;;;;CAOC,GACD,OAAO,SAASC,mBAAmB,EAAEC,QAAQ,EAAiB;IAC5D,2BAA2B;IAC3B,MAAM,CAACC,YAAYC,cAAc,GAAGP,SAAqD;IAEzF,MAAM,EAAEQ,SAAS,EAAE,GAAGP;IAEtB,uDAAuD;IACvD,MAAM,CAACQ,UAAUC,YAAY,GAAGV,SAAS;IAEzC,MAAM,CAACW,aAAaC,eAAe,GAAGZ;IAEtC,MAAMa,oBAAoBlB,YAAY;QACpC,MAAMmB,iBAAiB;YACrBJ,YAAY;YACZ,IAAI;gBACF,MAAMK,eAAe,MAAMC,MAAM,uBAAuB;oBACtD,4BAA4B;oBAC5BC,QAAQ;gBACV;gBAEA,IAAIF,aAAaG,EAAE,EAAE;oBACnB,gDAAgD;oBAChD,MAAM,EAAEP,WAAW,EAAEL,UAAU,EAAE,GAAG,MAAMS,aAAaI,IAAI;oBAC3D,2CAA2C;oBAC3C,6CAA6C;oBAC7CP,eAAeD;oBACfJ,cAAcD;gBAChB,OAAO;oBACLM,eAAe;oBACfL,cAAc;gBAChB;YACF,EAAE,OAAOa,OAAgB;gBACvBC,QAAQC,GAAG,CAAC,CAAC,kBAAkB,CAAC,EAAEF;YACpC;YACAV,YAAY;QACd;QACA,MAAMI;IACR,GAAG;QAACN;KAAU;IAEd,MAAMe,SAAS5B,YAAY;QACzBe,YAAY;QACZ,IAAI;YACF,uCAAuC;YACvC,8BAA8B;YAC9B,KAAK;YACL,6CAA6C;YAC7C,cAAc;YACd,oBAAoB;YACpB,yBAAyB;YACzB,KAAK;YACL,oCAAoC;YACpC,MAAMc,iBAAiB,MAAMR,MAAM,eAAe;gBAChDC,QAAQ;YACV;YAEA,gDAAgD;YAEhD,IAAIO,eAAeN,EAAE,EAAE;gBACrBX,cAAc;gBACdK,eAAe;YACjB;QACF,EAAE,OAAOQ,OAAgB;YACvBC,QAAQC,GAAG,CAAC,CAAC,oBAAoB,CAAC,EAAEF;QACtC;QACAV,YAAY;IACd,GAAG,EAAE;IAELd,UAAU;QACR,KAAKiB;IACP,GAAG;QAACA;KAAkB,GAAE,2CAA2C;IAEnE,mEAAmE;IACnE,MAAMY,eAAsC1B,QAC1C,IAAO,CAAA;YACLU;YACAc;YACAZ;YACAE;YACAP;QACF,CAAA,GACA;QAACG;QAAUc;QAAQZ;QAAaE;QAAmBP;KAAW;IAGhE,qBAAO,KAACJ,kBAAkBwB,QAAQ;QAACC,OAAOF;kBAAepB;;AAC3D;AAEA;;;;;CAKC,GACD,OAAO,SAASuB;IACd,MAAMC,UAAU/B,WAAWI;IAC3B,IAAI2B,YAAY1B,WAAW;QACzB,MAAM,IAAI2B,MAAM;IAClB;IACA,OAAOD;AACT"}
|
|
@@ -8,12 +8,16 @@ export type GetOptInChannelsResponse = {
|
|
|
8
8
|
optInChannels: OptInChannel[];
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
|
+
* Payload handler for GET /optinchannels. Returns all active opt-in channels
|
|
12
|
+
* for subscription preferences.
|
|
11
13
|
*
|
|
12
|
-
* @
|
|
14
|
+
* @param req - Payload request object
|
|
15
|
+
* @returns Response with `optInChannels` array on success, or `error` and `now` on failure (400)
|
|
13
16
|
*/
|
|
14
17
|
export declare const getOptInChannelsHandler: PayloadHandler;
|
|
15
18
|
/**
|
|
16
|
-
*
|
|
19
|
+
* Endpoint config for listing active opt-in channels. Mount as GET /optinchannels.
|
|
20
|
+
* Used by the subscribe UI to fetch available subscription channels.
|
|
17
21
|
*/
|
|
18
22
|
declare const getOptInChannelsEndpoint: Endpoint;
|
|
19
23
|
export default getOptInChannelsEndpoint;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { OptInChannels as OptInChannelCollection } from '../collections/OptInChannels.js';
|
|
2
2
|
/**
|
|
3
|
+
* Payload handler for GET /optinchannels. Returns all active opt-in channels
|
|
4
|
+
* for subscription preferences.
|
|
3
5
|
*
|
|
4
|
-
* @
|
|
6
|
+
* @param req - Payload request object
|
|
7
|
+
* @returns Response with `optInChannels` array on success, or `error` and `now` on failure (400)
|
|
5
8
|
*/ export const getOptInChannelsHandler = async (req)=>{
|
|
6
9
|
const findResults = await req.payload.find({
|
|
7
10
|
collection: OptInChannelCollection.slug,
|
|
@@ -31,7 +34,8 @@ import { OptInChannels as OptInChannelCollection } from '../collections/OptInCha
|
|
|
31
34
|
});
|
|
32
35
|
};
|
|
33
36
|
/**
|
|
34
|
-
*
|
|
37
|
+
* Endpoint config for listing active opt-in channels. Mount as GET /optinchannels.
|
|
38
|
+
* Used by the subscribe UI to fetch available subscription channels.
|
|
35
39
|
*/ const getOptInChannelsEndpoint = {
|
|
36
40
|
handler: getOptInChannelsHandler,
|
|
37
41
|
method: 'get',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/getOptInChannels.ts"],"sourcesContent":["import type { OptInChannel } from '../copied/payload-types.js'\nimport type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\n\nimport { OptInChannels as OptInChannelCollection } from '../collections/OptInChannels.js'\n\nexport type GetOptInChannelsResponse =\n | {\n error: string\n now: string\n }\n | {\n now: string\n optInChannels: OptInChannel[]\n }\n\n/**\n *\n * @returns\n */\nexport const getOptInChannelsHandler: PayloadHandler = async (req) => {\n const findResults = await req.payload.find({\n collection: OptInChannelCollection.slug as CollectionSlug,\n depth: 2,\n where: {\n active: { equals: true },\n },\n })\n // .catch((error) => {\n // return Response.json({ error, now: new Date().toISOString() } as GetOptInChannelsResponse, {\n // status: 400,\n // })\n // })\n\n if (!findResults) {\n return Response.json(\n { error: 'Unknown find result', now: new Date().toISOString() } as GetOptInChannelsResponse,\n { status: 400 },\n )\n }\n\n return Response.json({\n now: new Date().toISOString(),\n optInChannels: findResults.docs,\n } as GetOptInChannelsResponse)\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/getOptInChannels.ts"],"sourcesContent":["import type { OptInChannel } from '../copied/payload-types.js'\nimport type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\n\nimport { OptInChannels as OptInChannelCollection } from '../collections/OptInChannels.js'\n\nexport type GetOptInChannelsResponse =\n | {\n error: string\n now: string\n }\n | {\n now: string\n optInChannels: OptInChannel[]\n }\n\n/**\n * Payload handler for GET /optinchannels. Returns all active opt-in channels\n * for subscription preferences.\n *\n * @param req - Payload request object\n * @returns Response with `optInChannels` array on success, or `error` and `now` on failure (400)\n */\nexport const getOptInChannelsHandler: PayloadHandler = async (req) => {\n const findResults = await req.payload.find({\n collection: OptInChannelCollection.slug as CollectionSlug,\n depth: 2,\n where: {\n active: { equals: true },\n },\n })\n // .catch((error) => {\n // return Response.json({ error, now: new Date().toISOString() } as GetOptInChannelsResponse, {\n // status: 400,\n // })\n // })\n\n if (!findResults) {\n return Response.json(\n { error: 'Unknown find result', now: new Date().toISOString() } as GetOptInChannelsResponse,\n { status: 400 },\n )\n }\n\n return Response.json({\n now: new Date().toISOString(),\n optInChannels: findResults.docs,\n } as GetOptInChannelsResponse)\n}\n\n/**\n * Endpoint config for listing active opt-in channels. Mount as GET /optinchannels.\n * Used by the subscribe UI to fetch available subscription channels.\n */\nconst getOptInChannelsEndpoint: Endpoint = {\n handler: getOptInChannelsHandler,\n method: 'get',\n path: '/optinchannels',\n}\n\nexport default getOptInChannelsEndpoint\n"],"names":["OptInChannels","OptInChannelCollection","getOptInChannelsHandler","req","findResults","payload","find","collection","slug","depth","where","active","equals","Response","json","error","now","Date","toISOString","status","optInChannels","docs","getOptInChannelsEndpoint","handler","method","path"],"mappings":"AAGA,SAASA,iBAAiBC,sBAAsB,QAAQ,kCAAiC;AAYzF;;;;;;CAMC,GACD,OAAO,MAAMC,0BAA0C,OAAOC;IAC5D,MAAMC,cAAc,MAAMD,IAAIE,OAAO,CAACC,IAAI,CAAC;QACzCC,YAAYN,uBAAuBO,IAAI;QACvCC,OAAO;QACPC,OAAO;YACLC,QAAQ;gBAAEC,QAAQ;YAAK;QACzB;IACF;IACA,sBAAsB;IACtB,iGAAiG;IACjG,mBAAmB;IACnB,OAAO;IACP,KAAK;IAEL,IAAI,CAACR,aAAa;QAChB,OAAOS,SAASC,IAAI,CAClB;YAAEC,OAAO;YAAuBC,KAAK,IAAIC,OAAOC,WAAW;QAAG,GAC9D;YAAEC,QAAQ;QAAI;IAElB;IAEA,OAAON,SAASC,IAAI,CAAC;QACnBE,KAAK,IAAIC,OAAOC,WAAW;QAC3BE,eAAehB,YAAYiB,IAAI;IACjC;AACF,EAAC;AAED;;;CAGC,GACD,MAAMC,2BAAqC;IACzCC,SAASrB;IACTsB,QAAQ;IACRC,MAAM;AACR;AAEA,eAAeH,yBAAwB"}
|
|
@@ -7,12 +7,12 @@ export type LogoutResponse = {
|
|
|
7
7
|
now: string;
|
|
8
8
|
};
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* @returns
|
|
13
|
-
*
|
|
14
|
-
* Factory to generate the endpoint config with handler based on input option for subscribersCollectionSlug
|
|
10
|
+
* Factory that creates the logout endpoint config and handler.
|
|
11
|
+
* Clears the current subscriber session by delegating to Payload's collection logout.
|
|
15
12
|
*
|
|
13
|
+
* @param options - Config options for the endpoint
|
|
14
|
+
* @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
|
|
15
|
+
* @returns Payload Endpoint config for POST /logout
|
|
16
16
|
*/
|
|
17
17
|
declare function createEndpointLogout({ subscribersCollectionSlug, }: {
|
|
18
18
|
subscribersCollectionSlug: CollectionSlug;
|
package/dist/endpoints/logout.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { headers as nextHeaders } from 'next/headers.js';
|
|
2
2
|
import { defaultCollectionSlug } from '../collections/Subscribers.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* @returns
|
|
7
|
-
*
|
|
8
|
-
* Factory to generate the endpoint config with handler based on input option for subscribersCollectionSlug
|
|
4
|
+
* Factory that creates the logout endpoint config and handler.
|
|
5
|
+
* Clears the current subscriber session by delegating to Payload's collection logout.
|
|
9
6
|
*
|
|
7
|
+
* @param options - Config options for the endpoint
|
|
8
|
+
* @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
|
|
9
|
+
* @returns Payload Endpoint config for POST /logout
|
|
10
10
|
*/ function createEndpointLogout({ subscribersCollectionSlug = defaultCollectionSlug }) {
|
|
11
11
|
const logoutHandler = async (req)=>{
|
|
12
12
|
const headers = await nextHeaders();
|
|
@@ -46,9 +46,7 @@ import { defaultCollectionSlug } from '../collections/Subscribers.js';
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
|
-
/**
|
|
50
|
-
* logout Endpoint Config
|
|
51
|
-
*/ const logoutEndpoint = {
|
|
49
|
+
/** Endpoint config for subscriber logout. Mount as POST /logout. */ const logoutEndpoint = {
|
|
52
50
|
handler: logoutHandler,
|
|
53
51
|
method: 'post',
|
|
54
52
|
path: '/logout'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/logout.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\n\nimport { headers as nextHeaders } from 'next/headers.js'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\n\nexport type LogoutResponse =\n | {\n error: string\n now: string\n }\n | {\n message: string\n now: string\n }\n\n/**\n *
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/logout.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\n\nimport { headers as nextHeaders } from 'next/headers.js'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\n\nexport type LogoutResponse =\n | {\n error: string\n now: string\n }\n | {\n message: string\n now: string\n }\n\n/**\n * Factory that creates the logout endpoint config and handler.\n * Clears the current subscriber session by delegating to Payload's collection logout.\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)\n * @returns Payload Endpoint config for POST /logout\n */\nfunction createEndpointLogout({\n subscribersCollectionSlug = defaultCollectionSlug,\n}: {\n subscribersCollectionSlug: CollectionSlug\n}): Endpoint {\n const logoutHandler: PayloadHandler = async (req) => {\n const headers = await nextHeaders()\n\n try {\n const logoutResult = await fetch(\n `${req.payload.config.serverURL}/api/${subscribersCollectionSlug}/logout`,\n {\n headers,\n method: 'POST',\n },\n )\n\n const logoutResultData = await logoutResult.json()\n\n if (logoutResult.ok) {\n return Response.json({\n message: logoutResultData.message,\n now: new Date().toISOString(),\n } as LogoutResponse)\n }\n\n if (\n logoutResult.status == 400 &&\n logoutResultData.errors?.map((e: { message: string }) => e.message).includes('No User')\n ) {\n return Response.json(\n {\n error: `Logout failed: 'No User'`,\n now: new Date().toISOString(),\n } as LogoutResponse,\n {\n status: 400,\n },\n )\n }\n return Response.json(\n {\n error: `Logout failed: ${\n logoutResultData.errors\n ? logoutResultData.errors?.map((e: { message: string }) => e.message).join(' // ')\n : JSON.stringify(logoutResultData)\n }`,\n now: new Date().toISOString(),\n } as LogoutResponse,\n {\n status: 400,\n },\n )\n } catch (error) {\n // throw new Error(`Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`)\n return Response.json(\n {\n error: `Logout failed: ${JSON.stringify(error)}`,\n now: new Date().toISOString(),\n } as LogoutResponse,\n {\n status: 400,\n },\n )\n }\n }\n\n /** Endpoint config for subscriber logout. Mount as POST /logout. */\n const logoutEndpoint: Endpoint = {\n handler: logoutHandler,\n method: 'post',\n path: '/logout',\n }\n\n return logoutEndpoint\n}\n\nexport default createEndpointLogout\n"],"names":["headers","nextHeaders","defaultCollectionSlug","createEndpointLogout","subscribersCollectionSlug","logoutHandler","req","logoutResult","fetch","payload","config","serverURL","method","logoutResultData","json","ok","Response","message","now","Date","toISOString","status","errors","map","e","includes","error","join","JSON","stringify","logoutEndpoint","handler","path"],"mappings":"AAEA,SAASA,WAAWC,WAAW,QAAQ,kBAAiB;AAExD,SAASC,qBAAqB,QAAQ,gCAA+B;AAYrE;;;;;;;CAOC,GACD,SAASC,qBAAqB,EAC5BC,4BAA4BF,qBAAqB,EAGlD;IACC,MAAMG,gBAAgC,OAAOC;QAC3C,MAAMN,UAAU,MAAMC;QAEtB,IAAI;YACF,MAAMM,eAAe,MAAMC,MACzB,GAAGF,IAAIG,OAAO,CAACC,MAAM,CAACC,SAAS,CAAC,KAAK,EAAEP,0BAA0B,OAAO,CAAC,EACzE;gBACEJ;gBACAY,QAAQ;YACV;YAGF,MAAMC,mBAAmB,MAAMN,aAAaO,IAAI;YAEhD,IAAIP,aAAaQ,EAAE,EAAE;gBACnB,OAAOC,SAASF,IAAI,CAAC;oBACnBG,SAASJ,iBAAiBI,OAAO;oBACjCC,KAAK,IAAIC,OAAOC,WAAW;gBAC7B;YACF;YAEA,IACEb,aAAac,MAAM,IAAI,OACvBR,iBAAiBS,MAAM,EAAEC,IAAI,CAACC,IAA2BA,EAAEP,OAAO,EAAEQ,SAAS,YAC7E;gBACA,OAAOT,SAASF,IAAI,CAClB;oBACEY,OAAO,CAAC,wBAAwB,CAAC;oBACjCR,KAAK,IAAIC,OAAOC,WAAW;gBAC7B,GACA;oBACEC,QAAQ;gBACV;YAEJ;YACA,OAAOL,SAASF,IAAI,CAClB;gBACEY,OAAO,CAAC,eAAe,EACrBb,iBAAiBS,MAAM,GACnBT,iBAAiBS,MAAM,EAAEC,IAAI,CAACC,IAA2BA,EAAEP,OAAO,EAAEU,KAAK,UACzEC,KAAKC,SAAS,CAAChB,mBACnB;gBACFK,KAAK,IAAIC,OAAOC,WAAW;YAC7B,GACA;gBACEC,QAAQ;YACV;QAEJ,EAAE,OAAOK,OAAO;YACd,gGAAgG;YAChG,OAAOV,SAASF,IAAI,CAClB;gBACEY,OAAO,CAAC,eAAe,EAAEE,KAAKC,SAAS,CAACH,QAAQ;gBAChDR,KAAK,IAAIC,OAAOC,WAAW;YAC7B,GACA;gBACEC,QAAQ;YACV;QAEJ;IACF;IAEA,kEAAkE,GAClE,MAAMS,iBAA2B;QAC/BC,SAAS1B;QACTO,QAAQ;QACRoB,MAAM;IACR;IAEA,OAAOF;AACT;AAEA,eAAe3B,qBAAoB"}
|
|
@@ -7,14 +7,16 @@ export type RequestMagicLinkResponse = {
|
|
|
7
7
|
now: string;
|
|
8
8
|
};
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* @returns
|
|
13
|
-
*
|
|
14
|
-
* Factory to generate the endpoint config with handler based on input option for subscribersCollectionSlug
|
|
10
|
+
* Factory that creates the request-magic-link endpoint config and handler.
|
|
11
|
+
* Sends a magic-link email to the given address (creates a pending subscriber if needed).
|
|
15
12
|
*
|
|
13
|
+
* @param options - Config options for the endpoint
|
|
14
|
+
* @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
|
|
15
|
+
* @param options.unsubscribeUrl - The URL to use for unsubscribe links
|
|
16
|
+
* @returns Payload Endpoint config for POST /emailToken
|
|
16
17
|
*/
|
|
17
|
-
declare function createEndpointRequestMagicLink({ subscribersCollectionSlug, }: {
|
|
18
|
+
declare function createEndpointRequestMagicLink({ subscribersCollectionSlug, unsubscribeUrl, }: {
|
|
18
19
|
subscribersCollectionSlug: CollectionSlug;
|
|
20
|
+
unsubscribeUrl?: URL;
|
|
19
21
|
}): Endpoint;
|
|
20
22
|
export default createEndpointRequestMagicLink;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import crypto from 'crypto';
|
|
2
1
|
import { defaultCollectionSlug } from '../collections/Subscribers.js';
|
|
3
|
-
import { getTokenAndHash } from '../helpers/token.js';
|
|
2
|
+
import { getHmacHash, getTokenAndHash } from '../helpers/token.js';
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @returns
|
|
4
|
+
* Factory that creates the request-magic-link endpoint config and handler.
|
|
5
|
+
* Sends a magic-link email to the given address (creates a pending subscriber if needed).
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
7
|
+
* @param options - Config options for the endpoint
|
|
8
|
+
* @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
|
|
9
|
+
* @param options.unsubscribeUrl - The URL to use for unsubscribe links
|
|
10
|
+
* @returns Payload Endpoint config for POST /emailToken
|
|
11
|
+
*/ function createEndpointRequestMagicLink({ subscribersCollectionSlug = defaultCollectionSlug, unsubscribeUrl }) {
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* @
|
|
17
|
-
* @returns
|
|
13
|
+
* Handler for POST /emailToken. Accepts email and verifyUrl, creates/updates a pending
|
|
14
|
+
* subscriber with a verification token, and sends a magic-link email.
|
|
15
|
+
*
|
|
16
|
+
* @param req - Payload request; body must include `email` and `verifyUrl`
|
|
17
|
+
* @returns 200 with `emailResult` and `now` on success; 400 with `error` and `now` on bad data or email failure
|
|
18
18
|
*/ const requestMagicLinkHandler = async (req)=>{
|
|
19
19
|
const data = req?.json ? await req.json() : {};
|
|
20
20
|
const { email, verifyUrl } = data // if by POST data
|
|
@@ -63,15 +63,15 @@ import { getTokenAndHash } from '../helpers/token.js';
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
// Update user with verificationToken
|
|
66
|
-
const token = crypto.randomBytes(32).toString('hex')
|
|
67
|
-
const tokenHash = crypto.createHash('sha256').update(token).digest('hex')
|
|
68
|
-
const expiresAt = new Date(Date.now() + 15 * 60 * 1000) // 15 mins
|
|
69
|
-
;
|
|
66
|
+
// const token = crypto.randomBytes(32).toString('hex')
|
|
67
|
+
// const tokenHash = crypto.createHash('sha256').update(token).digest('hex')
|
|
68
|
+
// const expiresAt = new Date(Date.now() + 15 * 60 * 1000) // 15 mins
|
|
69
|
+
const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000);
|
|
70
70
|
await req.payload.update({
|
|
71
71
|
collection: subscribersCollectionSlug,
|
|
72
72
|
data: {
|
|
73
73
|
verificationToken: tokenHash,
|
|
74
|
-
verificationTokenExpires: expiresAt
|
|
74
|
+
verificationTokenExpires: expiresAt?.toISOString()
|
|
75
75
|
},
|
|
76
76
|
where: {
|
|
77
77
|
email: {
|
|
@@ -79,12 +79,15 @@ import { getTokenAndHash } from '../helpers/token.js';
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
});
|
|
82
|
+
const { hashToken: unsubscribeHash } = getHmacHash(email);
|
|
82
83
|
// Send email
|
|
83
84
|
const magicLink = `${verifyUrl}${verifyUrl.search ? '&' : '?'}token=${token}&email=${email}`;
|
|
85
|
+
const unsubscribeLink = !unsubscribeUrl ? undefined : `${unsubscribeUrl?.href}${unsubscribeUrl?.search ? '&' : '?'}email=${email}&hash=${unsubscribeHash}`;
|
|
84
86
|
const subject = data.subject || 'Your Magic Login Link';
|
|
85
87
|
const message = `
|
|
86
|
-
${data.message || '<p>
|
|
87
|
-
<p><a href="${magicLink}"><b>Login</b></a></p>
|
|
88
|
+
${data.message || '<p>You requested a magic link to log in. Click the button below</p>'}
|
|
89
|
+
<p><a href="${magicLink}"><button><b>Login</b></button></a></p>
|
|
90
|
+
${unsubscribeLink ? `<p>Click here to <a href="${unsubscribeLink}">unsubscribe</a></p>` : ``}
|
|
88
91
|
`;
|
|
89
92
|
const emailResult = await req.payload.sendEmail({
|
|
90
93
|
html: message,
|
|
@@ -107,9 +110,7 @@ import { getTokenAndHash } from '../helpers/token.js';
|
|
|
107
110
|
now: new Date().toISOString()
|
|
108
111
|
});
|
|
109
112
|
};
|
|
110
|
-
/**
|
|
111
|
-
* requestMagicLink Endpoint Config
|
|
112
|
-
*/ const requestMagicLinkEndpoint = {
|
|
113
|
+
/** Endpoint config for requesting a magic link. Mount as POST /emailToken. */ const requestMagicLinkEndpoint = {
|
|
113
114
|
handler: requestMagicLinkHandler,
|
|
114
115
|
method: 'post',
|
|
115
116
|
path: '/emailToken'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/requestMagicLink.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler, PayloadRequest, TypedUser } from 'payload'\n\nimport crypto from 'crypto'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getTokenAndHash } from '../helpers/token.js'\n\nexport type RequestMagicLinkResponse =\n | {\n emailResult: any\n now: string\n }\n | {\n error: string\n now: string\n }\n\n/**\n *
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/requestMagicLink.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler, PayloadRequest, TypedUser } from 'payload'\n\nimport crypto from 'crypto'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getHmacHash, getTokenAndHash } from '../helpers/token.js'\n\nexport type RequestMagicLinkResponse =\n | {\n emailResult: any\n now: string\n }\n | {\n error: string\n now: string\n }\n\n/**\n * Factory that creates the request-magic-link endpoint config and handler.\n * Sends a magic-link email to the given address (creates a pending subscriber if needed).\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)\n * @param options.unsubscribeUrl - The URL to use for unsubscribe links\n * @returns Payload Endpoint config for POST /emailToken\n */\nfunction createEndpointRequestMagicLink({\n subscribersCollectionSlug = defaultCollectionSlug,\n unsubscribeUrl,\n}: {\n subscribersCollectionSlug: CollectionSlug\n unsubscribeUrl?: URL\n}): Endpoint {\n /**\n * Handler for POST /emailToken. Accepts email and verifyUrl, creates/updates a pending\n * subscriber with a verification token, and sends a magic-link email.\n *\n * @param req - Payload request; body must include `email` and `verifyUrl`\n * @returns 200 with `emailResult` and `now` on success; 400 with `error` and `now` on bad data or email failure\n */\n const requestMagicLinkHandler: PayloadHandler = async (req: PayloadRequest) => {\n const data = req?.json ? await req.json() : {}\n const { email, verifyUrl } = data // if by POST data\n // const { email } = req.routeParams // if by path\n\n if (!email || !verifyUrl) {\n return Response.json(\n { error: 'Bad data', now: new Date().toISOString() } as RequestMagicLinkResponse,\n { status: 400 },\n )\n }\n\n const userResults = await req.payload.find({\n collection: subscribersCollectionSlug,\n where: {\n email: { equals: email },\n },\n })\n const user = userResults.docs[0] as TypedUser\n\n if (!user) {\n //\n // Create subscriber with status 'pending',\n // and an invisible unknowable password,\n //\n const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable\n const createResult = await req.payload.create({\n collection: subscribersCollectionSlug,\n data: {\n email,\n password: tokenHash2,\n status: 'pending',\n },\n draft: false,\n })\n if (!createResult) {\n return Response.json(\n { error: 'Bad data', now: new Date().toISOString() } as RequestMagicLinkResponse,\n { status: 400 },\n )\n }\n }\n\n // Update user with verificationToken\n // const token = crypto.randomBytes(32).toString('hex')\n // const tokenHash = crypto.createHash('sha256').update(token).digest('hex')\n // const expiresAt = new Date(Date.now() + 15 * 60 * 1000) // 15 mins\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000)\n await req.payload.update({\n collection: subscribersCollectionSlug,\n data: {\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt?.toISOString(),\n },\n where: {\n email: { equals: user.email },\n },\n })\n const { hashToken: unsubscribeHash } = getHmacHash(email)\n\n // Send email\n const magicLink = `${verifyUrl}${verifyUrl.search ? '&' : '?'}token=${token}&email=${email}`\n const unsubscribeLink = !unsubscribeUrl\n ? undefined\n : `${unsubscribeUrl?.href}${unsubscribeUrl?.search ? '&' : '?'}email=${email}&hash=${unsubscribeHash}`\n const subject = data.subject || 'Your Magic Login Link'\n const message = `\n ${data.message || '<p>You requested a magic link to log in. Click the button below</p>'}\n <p><a href=\"${magicLink}\"><button><b>Login</b></button></a></p>\n ${unsubscribeLink ? `<p>Click here to <a href=\"${unsubscribeLink}\">unsubscribe</a></p>` : ``}\n `\n\n const emailResult = await req.payload.sendEmail({\n html: message,\n subject,\n to: user.email,\n })\n // req.payload.logger.info(`email result: ${JSON.stringify(emailResult)}`)\n // return data; // Return data to allow normal submission if needed\n if (!emailResult) {\n return Response.json(\n {\n error: 'Unknown email result',\n now: new Date().toISOString(),\n } as RequestMagicLinkResponse,\n { status: 400 },\n )\n }\n req.payload.logger.info(`requestMagicLinkHandler email sent \\n ${magicLink}`)\n return Response.json({\n emailResult,\n now: new Date().toISOString(),\n } as RequestMagicLinkResponse)\n }\n\n /** Endpoint config for requesting a magic link. Mount as POST /emailToken. */\n const requestMagicLinkEndpoint: Endpoint = {\n handler: requestMagicLinkHandler,\n method: 'post',\n path: '/emailToken',\n }\n\n return requestMagicLinkEndpoint\n}\n\nexport default createEndpointRequestMagicLink\n"],"names":["defaultCollectionSlug","getHmacHash","getTokenAndHash","createEndpointRequestMagicLink","subscribersCollectionSlug","unsubscribeUrl","requestMagicLinkHandler","req","data","json","email","verifyUrl","Response","error","now","Date","toISOString","status","userResults","payload","find","collection","where","equals","user","docs","tokenHash","tokenHash2","createResult","create","password","draft","expiresAt","token","update","verificationToken","verificationTokenExpires","hashToken","unsubscribeHash","magicLink","search","unsubscribeLink","undefined","href","subject","message","emailResult","sendEmail","html","to","logger","info","requestMagicLinkEndpoint","handler","method","path"],"mappings":"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"}
|