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.
Files changed (60) hide show
  1. package/README.md +83 -6
  2. package/dist/components/app/RequestMagicLink.d.ts +12 -1
  3. package/dist/components/app/RequestMagicLink.js +59 -31
  4. package/dist/components/app/RequestMagicLink.js.map +1 -1
  5. package/dist/components/app/RequestOrSubscribe.d.ts +21 -6
  6. package/dist/components/app/RequestOrSubscribe.js +13 -1
  7. package/dist/components/app/RequestOrSubscribe.js.map +1 -1
  8. package/dist/components/app/SelectOptInChannels.d.ts +9 -0
  9. package/dist/components/app/SelectOptInChannels.js +7 -1
  10. package/dist/components/app/SelectOptInChannels.js.map +1 -1
  11. package/dist/components/app/Subscribe.d.ts +11 -1
  12. package/dist/components/app/Subscribe.js +42 -9
  13. package/dist/components/app/Subscribe.js.map +1 -1
  14. package/dist/components/app/SubscriberMenu.d.ts +16 -3
  15. package/dist/components/app/SubscriberMenu.js +26 -17
  16. package/dist/components/app/SubscriberMenu.js.map +1 -1
  17. package/dist/components/app/Unsubscribe.d.ts +31 -0
  18. package/dist/components/app/Unsubscribe.js +155 -0
  19. package/dist/components/app/Unsubscribe.js.map +1 -0
  20. package/dist/components/app/VerifyMagicLink.d.ts +11 -1
  21. package/dist/components/app/VerifyMagicLink.js +19 -7
  22. package/dist/components/app/VerifyMagicLink.js.map +1 -1
  23. package/dist/components/app/helpers.d.ts +8 -0
  24. package/dist/components/app/helpers.js +8 -1
  25. package/dist/components/app/helpers.js.map +1 -1
  26. package/dist/components/app/shared.module.css +10 -1
  27. package/dist/contexts/SubscriberProvider.d.ts +16 -0
  28. package/dist/contexts/SubscriberProvider.js +14 -3
  29. package/dist/contexts/SubscriberProvider.js.map +1 -1
  30. package/dist/endpoints/getOptInChannels.d.ts +6 -2
  31. package/dist/endpoints/getOptInChannels.js +6 -2
  32. package/dist/endpoints/getOptInChannels.js.map +1 -1
  33. package/dist/endpoints/logout.d.ts +5 -5
  34. package/dist/endpoints/logout.js +6 -8
  35. package/dist/endpoints/logout.js.map +1 -1
  36. package/dist/endpoints/requestMagicLink.d.ts +8 -6
  37. package/dist/endpoints/requestMagicLink.js +24 -23
  38. package/dist/endpoints/requestMagicLink.js.map +1 -1
  39. package/dist/endpoints/subscribe.d.ts +6 -5
  40. package/dist/endpoints/subscribe.js +34 -21
  41. package/dist/endpoints/subscribe.js.map +1 -1
  42. package/dist/endpoints/subscriberAuth.d.ts +5 -5
  43. package/dist/endpoints/subscriberAuth.js +11 -12
  44. package/dist/endpoints/subscriberAuth.js.map +1 -1
  45. package/dist/endpoints/unsubscribe.d.ts +21 -0
  46. package/dist/endpoints/unsubscribe.js +118 -0
  47. package/dist/endpoints/unsubscribe.js.map +1 -0
  48. package/dist/endpoints/verifyMagicLink.d.ts +5 -5
  49. package/dist/endpoints/verifyMagicLink.js +14 -15
  50. package/dist/endpoints/verifyMagicLink.js.map +1 -1
  51. package/dist/exports/ui.d.ts +2 -0
  52. package/dist/exports/ui.js +1 -0
  53. package/dist/exports/ui.js.map +1 -1
  54. package/dist/helpers/token.d.ts +3 -0
  55. package/dist/helpers/token.js +12 -2
  56. package/dist/helpers/token.js.map +1 -1
  57. package/dist/index.d.ts +4 -0
  58. package/dist/index.js +11 -1
  59. package/dist/index.js.map +1 -1
  60. 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
- export const VerifyMagicLink = ({ children, classNames = {
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 + '/api/verifyToken', {
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
- export const mergeClassNames = (classNames)=>{
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":["export 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,OAAO,MAAMA,kBAAkB,CAACC;IAC9B,OAAOA,WACJC,IAAI,CAACC,UACLC,MAAM,CAAC,CAACC,YAAc,CAAC,CAACA,WACxBC,IAAI,CAAC;AACV,EAAC"}
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"}
@@ -11,4 +11,13 @@
11
11
 
12
12
  .error {
13
13
  color: red;
14
- }
14
+ }
15
+
16
+ .group {
17
+ display: flex
18
+ }
19
+
20
+ .group > div {
21
+ flex-grow: 1;
22
+ }
23
+
@@ -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
- export function SubscriberProvider({ children }) {
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
- // Custom hook to easily consume the context and add error handling
92
- export function useSubscriber() {
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 // Recommended type for children\n}\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// Custom hook to easily consume the context and add error handling\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;AAU7D,MAAMC,kCAAoBL,cAAiDM;AAM3E,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,mEAAmE;AACnE,OAAO,SAASuB;IACd,MAAMC,UAAU/B,WAAWI;IAC3B,IAAI2B,YAAY1B,WAAW;QACzB,MAAM,IAAI2B,MAAM;IAClB;IACA,OAAOD;AACT"}
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
- * @returns
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
- * getOptInChannels Endpoint Config
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
- * @returns
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
- * getOptInChannels Endpoint Config
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 * getOptInChannels Endpoint Config\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;;;CAGC,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;;CAEC,GACD,MAAMC,2BAAqC;IACzCC,SAASrB;IACTsB,QAAQ;IACRC,MAAM;AACR;AAEA,eAAeH,yBAAwB"}
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
- * createEndpointLogout
11
- * @param options
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;
@@ -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
- * createEndpointLogout
5
- * @param options
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 * createEndpointLogout\n * @param options\n * @returns\n *\n * Factory to generate the endpoint config with handler based on input option for subscribersCollectionSlug\n *\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 /**\n * logout Endpoint Config\n */\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;;GAEC,GACD,MAAMS,iBAA2B;QAC/BC,SAAS1B;QACTO,QAAQ;QACRoB,MAAM;IACR;IAEA,OAAOF;AACT;AAEA,eAAe3B,qBAAoB"}
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
- * createEndpointRequestMagicLink
11
- * @param options
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
- * createEndpointRequestMagicLink
6
- * @param options
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
- * Factory to generate the endpoint config with handler based on input option for subscribersCollectionSlug
10
- *
11
- */ function createEndpointRequestMagicLink({ subscribersCollectionSlug = defaultCollectionSlug }) {
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
- * requestMagicLink Endpoint Handler
14
- * @param req
15
- * @data { email, verifyUrl }
16
- * @returns { status: 200, json: {message: string, now: date} }
17
- * @returns { status: 400, json: {error: ('Bad data' | 'Unknown email result'), now: date} }
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.toISOString()
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>Use this link to log in:</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 * createEndpointRequestMagicLink\n * @param options\n * @returns\n *\n * Factory to generate the endpoint config with handler based on input option for subscribersCollectionSlug\n *\n */\nfunction createEndpointRequestMagicLink({\n subscribersCollectionSlug = defaultCollectionSlug,\n}: {\n subscribersCollectionSlug: CollectionSlug\n}): Endpoint {\n /**\n * requestMagicLink Endpoint Handler\n * @param req\n * @data { email, verifyUrl }\n * @returns { status: 200, json: {message: string, now: date} }\n * @returns { status: 400, json: {error: ('Bad data' | 'Unknown email result'), now: date} }\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 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\n // Send email\n const magicLink = `${verifyUrl}${verifyUrl.search ? '&' : '?'}token=${token}&email=${email}`\n const subject = data.subject || 'Your Magic Login Link'\n const message = `\n ${data.message || '<p>Use this link to log in:</p>'}\n <p><a href=\"${magicLink}\"><b>Login</b></a></p>\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 /**\n * requestMagicLink Endpoint Config\n */\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":["crypto","defaultCollectionSlug","getTokenAndHash","createEndpointRequestMagicLink","subscribersCollectionSlug","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","token","randomBytes","toString","createHash","update","digest","expiresAt","verificationToken","verificationTokenExpires","magicLink","search","subject","message","emailResult","sendEmail","html","to","logger","info","requestMagicLinkEndpoint","handler","method","path"],"mappings":"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"}
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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"}