payload-subscribers-plugin 0.0.13 → 0.0.15

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 CHANGED
@@ -38,7 +38,7 @@ export default buildConfig({
38
38
  posts: true,
39
39
  },
40
40
 
41
- // Easily disable the collection logic.
41
+ // Easily disable the plugin logic while keep the collections schema changes.
42
42
  disabled: false,
43
43
 
44
44
  // Specify the collection to use as the subscribers collection
@@ -51,11 +51,11 @@ export default buildConfig({
51
51
  // Provide a custom expiration for magic link tokens. The default is 30 minutes.
52
52
  tokenExpiration: 60 * 60,
53
53
 
54
- // Provide your unsubscribe route. This route should include the Unsubscribe component. If not provided, your payload config must have serverURL defined, and the default will be serverURL+'/unsubscribe'
55
- unsubscribeURL?: string
54
+ // Provide your unsubscribe route. This route should include the Unsubscribe component, or implement your own with the useUnsubscribe hook. If not provided, your payload config must have serverURL defined, and the default will be serverURL+'/unsubscribe'
55
+ unsubscribeURL?: string,
56
56
 
57
- // Provide your verify route. This route should include the Unsubscribe component. If not provided, your payload config must have serverURL defined, and the default will be serverURL+'/verify'
58
- verifyURL?: string
57
+ // Provide your verify route. This route should include the Verify component, or implement your own with the useVerifyMagicLink hook. If not provided, your payload config must have serverURL defined, and the default will be serverURL+'/verify'
58
+ verifyURL?: string,
59
59
  }),
60
60
  ],
61
61
  })
@@ -128,8 +128,22 @@ You can specify collections in the plugin options which will be amended to inclu
128
128
 
129
129
  #### **disabled**
130
130
 
131
+ Easily disable the plugin logic while keep the collections schema changes.
132
+
131
133
  #### **tokenExpiration**
132
134
 
135
+ Provide a custom expiration for magic link tokens. The default is 30 minutes.
136
+
137
+ #### unsubscribeURL
138
+
139
+ Provide your unsubscribe route. This route should include the Unsubscribe component, or implement your own with the useUnsubscribe hook. If not provided, your payload config must have serverURL defined, and the default will be ```serverURL+'/unsubscribe'```
140
+
141
+
142
+ #### **verifyURL**
143
+
144
+ Provide your verify route. This route should include the Verify component, or implement your own with the useVerifyMagicLink hook. If not provided, your payload config must have serverURL defined, and the default will be ```serverURL+'/verify'```
145
+
146
+
133
147
  ### 🔵 Collections
134
148
 
135
149
  #### **optInChannels**
@@ -43,3 +43,4 @@ export type UnsubscribeClasses = {
43
43
  * @returns Loading status, result message, and children
44
44
  */
45
45
  export declare const Unsubscribe: ({ children, classNames, handleUnsubscribe, }: IUnsubscribe) => import("react").JSX.Element;
46
+ export declare const UnsubscribeInSuspense: ({ children, classNames, handleUnsubscribe, }: IUnsubscribe) => import("react").JSX.Element;
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useSearchParams } from 'next/navigation.js';
4
- import { useEffect } from 'react';
4
+ import { Suspense, useEffect } from 'react';
5
5
  import { useUnsubscribe } from '../../hooks/useUnsubscribe.js';
6
6
  import { mergeClassNames } from './helpers.js';
7
7
  import styles from './shared.module.css';
@@ -22,6 +22,26 @@ import styles from './shared.module.css';
22
22
  form: '',
23
23
  loading: '',
24
24
  message: ''
25
+ }, handleUnsubscribe })=>{
26
+ return /*#__PURE__*/ _jsx(Suspense, {
27
+ fallback: /*#__PURE__*/ _jsx("div", {
28
+ children: "Unsubscribing..."
29
+ }),
30
+ children: /*#__PURE__*/ _jsx(UnsubscribeInSuspense, {
31
+ classNames: classNames,
32
+ handleUnsubscribe: handleUnsubscribe,
33
+ children: children
34
+ })
35
+ });
36
+ };
37
+ export const UnsubscribeInSuspense = ({ children, classNames = {
38
+ button: '',
39
+ container: '',
40
+ emailInput: '',
41
+ error: '',
42
+ form: '',
43
+ loading: '',
44
+ message: ''
25
45
  }, handleUnsubscribe })=>{
26
46
  const { isError, isLoading, result, unsubscribe } = useUnsubscribe({
27
47
  handleUnsubscribe
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/app/Unsubscribe.tsx"],"sourcesContent":["'use client'\n\nimport { useSearchParams } from 'next/navigation.js'\nimport { useEffect } from 'react'\n\nimport type { UnsubscribeResponse } from '../../endpoints/unsubscribe.js'\n\nexport { UnsubscribeResponse }\nimport { useSubscriber } from '../../exports/ui.js'\nimport { useUnsubscribe } from '../../hooks/useUnsubscribe.js'\nimport { mergeClassNames } from './helpers.js'\nimport styles from './shared.module.css'\n\n// const payload = await getPayload({\n// config: configPromise,\n// })\n\n/**\n * Props for the Unsubscribe component.\n *\n * @property children - Optional React nodes rendered after unsubscribe is attempted\n * @property classNames - Optional CSS class overrides for the component elements\n * @property handleUnsubscribe - Callback when unsubscribe is attempted (success or error)\n */\nexport interface IUnsubscribe {\n children?: React.ReactNode\n classNames?: UnsubscribeClasses\n handleUnsubscribe?: (result: UnsubscribeResponse) => void\n}\n\n/**\n * Optional CSS class overrides for Unsubscribe elements.\n *\n * @property button - Class for buttons\n * @property container - Class for the main container\n * @property emailInput - Class for the email input field\n * @property error - Class for error messages\n * @property form - Class for the form\n * @property loading - Class for loading state\n * @property message - Class for result message text\n */\nexport type UnsubscribeClasses = {\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 unsubscribe action, for use with unsubscribe URLs in emails, etc.\n * Uses URL params email and hash to call POST /api/unsubscribe. Displays children after attempt.\n *\n * @param props - Component props (see IUnsubscribe)\n * @param props.children - Optional React nodes rendered after unsubscribe is attempted\n * @param props.classNames - Optional class overrides for the component elements\n * @param props.handleUnsubscribe - Callback when unsubscribe is attempted (success or error)\n * @returns Loading status, result message, and children\n */\nexport const Unsubscribe = ({\n children,\n classNames = {\n button: '',\n container: '',\n emailInput: '',\n error: '',\n form: '',\n loading: '',\n message: '',\n },\n handleUnsubscribe,\n}: IUnsubscribe) => {\n const { isError, isLoading, result, unsubscribe } = useUnsubscribe({ handleUnsubscribe })\n\n const searchParams = useSearchParams()\n const email = searchParams.get('email')\n const hash = searchParams.get('hash')\n\n useEffect(() => {\n async function callUnsubscribe() {\n if (email && hash) {\n await unsubscribe({ email, hash })\n }\n }\n void callUnsubscribe()\n }, [email, hash, unsubscribe])\n\n return (\n <div\n className={mergeClassNames([\n 'subscribers-callUnsubscribe subscribers-container',\n styles.container,\n classNames.container,\n ])}\n >\n {isLoading && (\n <p className={mergeClassNames(['subscribers-loading', styles.loading, classNames.loading])}>\n unsubscribing...\n </p>\n )}\n {!isLoading && (\n <>\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 <div className={mergeClassNames(['subscribers-form', styles.form, classNames.form])}>\n {children}\n </div>\n </>\n )}\n </div>\n )\n}\n"],"names":["useSearchParams","useEffect","useUnsubscribe","mergeClassNames","styles","Unsubscribe","children","classNames","button","container","emailInput","error","form","loading","message","handleUnsubscribe","isError","isLoading","result","unsubscribe","searchParams","email","get","hash","callUnsubscribe","div","className","p"],"mappings":"AAAA;;AAEA,SAASA,eAAe,QAAQ,qBAAoB;AACpD,SAASC,SAAS,QAAQ,QAAO;AAMjC,SAASC,cAAc,QAAQ,gCAA+B;AAC9D,SAASC,eAAe,QAAQ,eAAc;AAC9C,OAAOC,YAAY,sBAAqB;AAwCxC;;;;;;;;;CASC,GACD,OAAO,MAAMC,cAAc,CAAC,EAC1BC,QAAQ,EACRC,aAAa;IACXC,QAAQ;IACRC,WAAW;IACXC,YAAY;IACZC,OAAO;IACPC,MAAM;IACNC,SAAS;IACTC,SAAS;AACX,CAAC,EACDC,iBAAiB,EACJ;IACb,MAAM,EAAEC,OAAO,EAAEC,SAAS,EAAEC,MAAM,EAAEC,WAAW,EAAE,GAAGjB,eAAe;QAAEa;IAAkB;IAEvF,MAAMK,eAAepB;IACrB,MAAMqB,QAAQD,aAAaE,GAAG,CAAC;IAC/B,MAAMC,OAAOH,aAAaE,GAAG,CAAC;IAE9BrB,UAAU;QACR,eAAeuB;YACb,IAAIH,SAASE,MAAM;gBACjB,MAAMJ,YAAY;oBAAEE;oBAAOE;gBAAK;YAClC;QACF;QACA,KAAKC;IACP,GAAG;QAACH;QAAOE;QAAMJ;KAAY;IAE7B,qBACE,MAACM;QACCC,WAAWvB,gBAAgB;YACzB;YACAC,OAAOK,SAAS;YAChBF,WAAWE,SAAS;SACrB;;YAEAQ,2BACC,KAACU;gBAAED,WAAWvB,gBAAgB;oBAAC;oBAAuBC,OAAOS,OAAO;oBAAEN,WAAWM,OAAO;iBAAC;0BAAG;;YAI7F,CAACI,2BACA;;kCACE,KAACU;wBACCD,WAAWvB,gBAAgB;4BACzB;4BACAC,OAAOU,OAAO;4BACdP,WAAWO,OAAO;4BAClBE,UAAU;gCAAC;gCAAqBZ,OAAOO,KAAK;gCAAEJ,WAAWI,KAAK;6BAAC,GAAG,EAAE;yBACrE;kCAEAO;;kCAEH,KAACO;wBAAIC,WAAWvB,gBAAgB;4BAAC;4BAAoBC,OAAOQ,IAAI;4BAAEL,WAAWK,IAAI;yBAAC;kCAC/EN;;;;;;AAMb,EAAC"}
1
+ {"version":3,"sources":["../../../src/components/app/Unsubscribe.tsx"],"sourcesContent":["'use client'\n\nimport { useSearchParams } from 'next/navigation.js'\nimport { Suspense, useEffect } from 'react'\n\nimport type { UnsubscribeResponse } from '../../endpoints/unsubscribe.js'\n\nexport { UnsubscribeResponse }\nimport { useSubscriber } from '../../exports/ui.js'\nimport { useUnsubscribe } from '../../hooks/useUnsubscribe.js'\nimport { mergeClassNames } from './helpers.js'\nimport styles from './shared.module.css'\n\n// const payload = await getPayload({\n// config: configPromise,\n// })\n\n/**\n * Props for the Unsubscribe component.\n *\n * @property children - Optional React nodes rendered after unsubscribe is attempted\n * @property classNames - Optional CSS class overrides for the component elements\n * @property handleUnsubscribe - Callback when unsubscribe is attempted (success or error)\n */\nexport interface IUnsubscribe {\n children?: React.ReactNode\n classNames?: UnsubscribeClasses\n handleUnsubscribe?: (result: UnsubscribeResponse) => void\n}\n\n/**\n * Optional CSS class overrides for Unsubscribe elements.\n *\n * @property button - Class for buttons\n * @property container - Class for the main container\n * @property emailInput - Class for the email input field\n * @property error - Class for error messages\n * @property form - Class for the form\n * @property loading - Class for loading state\n * @property message - Class for result message text\n */\nexport type UnsubscribeClasses = {\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 unsubscribe action, for use with unsubscribe URLs in emails, etc.\n * Uses URL params email and hash to call POST /api/unsubscribe. Displays children after attempt.\n *\n * @param props - Component props (see IUnsubscribe)\n * @param props.children - Optional React nodes rendered after unsubscribe is attempted\n * @param props.classNames - Optional class overrides for the component elements\n * @param props.handleUnsubscribe - Callback when unsubscribe is attempted (success or error)\n * @returns Loading status, result message, and children\n */\nexport const Unsubscribe = ({\n children,\n classNames = {\n button: '',\n container: '',\n emailInput: '',\n error: '',\n form: '',\n loading: '',\n message: '',\n },\n handleUnsubscribe,\n}: IUnsubscribe) => {\n return (\n <Suspense fallback={<div>Unsubscribing...</div>}>\n <UnsubscribeInSuspense classNames={classNames} handleUnsubscribe={handleUnsubscribe}>\n {children}\n </UnsubscribeInSuspense>\n </Suspense>\n )\n}\n\nexport const UnsubscribeInSuspense = ({\n children,\n classNames = {\n button: '',\n container: '',\n emailInput: '',\n error: '',\n form: '',\n loading: '',\n message: '',\n },\n handleUnsubscribe,\n}: IUnsubscribe) => {\n const { isError, isLoading, result, unsubscribe } = useUnsubscribe({ handleUnsubscribe })\n\n const searchParams = useSearchParams()\n const email = searchParams.get('email')\n const hash = searchParams.get('hash')\n\n useEffect(() => {\n async function callUnsubscribe() {\n if (email && hash) {\n await unsubscribe({ email, hash })\n }\n }\n void callUnsubscribe()\n }, [email, hash, unsubscribe])\n\n return (\n <div\n className={mergeClassNames([\n 'subscribers-callUnsubscribe subscribers-container',\n styles.container,\n classNames.container,\n ])}\n >\n {isLoading && (\n <p className={mergeClassNames(['subscribers-loading', styles.loading, classNames.loading])}>\n unsubscribing...\n </p>\n )}\n {!isLoading && (\n <>\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 <div className={mergeClassNames(['subscribers-form', styles.form, classNames.form])}>\n {children}\n </div>\n </>\n )}\n </div>\n )\n}\n"],"names":["useSearchParams","Suspense","useEffect","useUnsubscribe","mergeClassNames","styles","Unsubscribe","children","classNames","button","container","emailInput","error","form","loading","message","handleUnsubscribe","fallback","div","UnsubscribeInSuspense","isError","isLoading","result","unsubscribe","searchParams","email","get","hash","callUnsubscribe","className","p"],"mappings":"AAAA;;AAEA,SAASA,eAAe,QAAQ,qBAAoB;AACpD,SAASC,QAAQ,EAAEC,SAAS,QAAQ,QAAO;AAM3C,SAASC,cAAc,QAAQ,gCAA+B;AAC9D,SAASC,eAAe,QAAQ,eAAc;AAC9C,OAAOC,YAAY,sBAAqB;AAwCxC;;;;;;;;;CASC,GACD,OAAO,MAAMC,cAAc,CAAC,EAC1BC,QAAQ,EACRC,aAAa;IACXC,QAAQ;IACRC,WAAW;IACXC,YAAY;IACZC,OAAO;IACPC,MAAM;IACNC,SAAS;IACTC,SAAS;AACX,CAAC,EACDC,iBAAiB,EACJ;IACb,qBACE,KAACf;QAASgB,wBAAU,KAACC;sBAAI;;kBACvB,cAAA,KAACC;YAAsBX,YAAYA;YAAYQ,mBAAmBA;sBAC/DT;;;AAIT,EAAC;AAED,OAAO,MAAMY,wBAAwB,CAAC,EACpCZ,QAAQ,EACRC,aAAa;IACXC,QAAQ;IACRC,WAAW;IACXC,YAAY;IACZC,OAAO;IACPC,MAAM;IACNC,SAAS;IACTC,SAAS;AACX,CAAC,EACDC,iBAAiB,EACJ;IACb,MAAM,EAAEI,OAAO,EAAEC,SAAS,EAAEC,MAAM,EAAEC,WAAW,EAAE,GAAGpB,eAAe;QAAEa;IAAkB;IAEvF,MAAMQ,eAAexB;IACrB,MAAMyB,QAAQD,aAAaE,GAAG,CAAC;IAC/B,MAAMC,OAAOH,aAAaE,GAAG,CAAC;IAE9BxB,UAAU;QACR,eAAe0B;YACb,IAAIH,SAASE,MAAM;gBACjB,MAAMJ,YAAY;oBAAEE;oBAAOE;gBAAK;YAClC;QACF;QACA,KAAKC;IACP,GAAG;QAACH;QAAOE;QAAMJ;KAAY;IAE7B,qBACE,MAACL;QACCW,WAAWzB,gBAAgB;YACzB;YACAC,OAAOK,SAAS;YAChBF,WAAWE,SAAS;SACrB;;YAEAW,2BACC,KAACS;gBAAED,WAAWzB,gBAAgB;oBAAC;oBAAuBC,OAAOS,OAAO;oBAAEN,WAAWM,OAAO;iBAAC;0BAAG;;YAI7F,CAACO,2BACA;;kCACE,KAACS;wBACCD,WAAWzB,gBAAgB;4BACzB;4BACAC,OAAOU,OAAO;4BACdP,WAAWO,OAAO;4BAClBK,UAAU;gCAAC;gCAAqBf,OAAOO,KAAK;gCAAEJ,WAAWI,KAAK;6BAAC,GAAG,EAAE;yBACrE;kCAEAU;;kCAEH,KAACJ;wBAAIW,WAAWzB,gBAAgB;4BAAC;4BAAoBC,OAAOQ,IAAI;4BAAEL,WAAWK,IAAI;yBAAC;kCAC/EN;;;;;;AAMb,EAAC"}
@@ -51,3 +51,4 @@ export type VerifyMagicLinkClasses = {
51
51
  * @returns Loading status, error/result message, and children. Shows RequestMagicLink when no token/email.
52
52
  */
53
53
  export declare const VerifyMagicLink: ({ children, classNames, handleMagicLinkRequested, handleMagicLinkVerified, verifyData, }: IVerifyMagicLink) => import("react").JSX.Element;
54
+ export declare const VerifyMagicLinkInSuspense: ({ children, classNames, handleMagicLinkRequested, handleMagicLinkVerified, verifyData, }: IVerifyMagicLink) => import("react").JSX.Element;
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useSearchParams } from 'next/navigation.js';
4
- import { useEffect, useState } from 'react';
5
- import { RequestMagicLink, useSubscriber } from '../../exports/ui.js';
4
+ import { Suspense, useEffect, useState } from 'react';
5
+ import { RequestMagicLink } from '../../exports/ui.js';
6
6
  import { useRequestMagicLink } from '../../hooks/useRequestMagicLink.js';
7
7
  import { useVerifyMagicLink } from '../../hooks/useVerifyMagicLink.js';
8
8
  import { mergeClassNames } from './helpers.js';
@@ -27,24 +27,39 @@ import styles from './shared.module.css';
27
27
  form: '',
28
28
  loading: '',
29
29
  message: ''
30
+ }, handleMagicLinkRequested, handleMagicLinkVerified, verifyData })=>{
31
+ return /*#__PURE__*/ _jsx(Suspense, {
32
+ fallback: /*#__PURE__*/ _jsx("div", {
33
+ children: "Verifying..."
34
+ }),
35
+ children: /*#__PURE__*/ _jsx(VerifyMagicLinkInSuspense, {
36
+ classNames: classNames,
37
+ handleMagicLinkRequested: handleMagicLinkRequested,
38
+ handleMagicLinkVerified: handleMagicLinkVerified,
39
+ verifyData: verifyData,
40
+ children: children
41
+ })
42
+ });
43
+ };
44
+ export const VerifyMagicLinkInSuspense = ({ children, classNames = {
45
+ button: '',
46
+ container: '',
47
+ emailInput: '',
48
+ error: '',
49
+ form: '',
50
+ loading: '',
51
+ message: ''
30
52
  }, handleMagicLinkRequested, handleMagicLinkVerified, verifyData })=>{
31
53
  const searchParams = useSearchParams();
32
54
  const email = searchParams.get('email');
33
55
  const token = searchParams.get('token');
34
- const { subscriber } = useSubscriber();
35
56
  const { isError: verifyIsError, isLoading: verifyIsLoading, result: verifyResult, verify } = useVerifyMagicLink();
36
57
  useEffect(()=>{
37
- async function asyncVerify() {
58
+ const asyncVerify = async ()=>{
38
59
  await verify();
39
- }
40
- if (!subscriber) {
41
- void asyncVerify();
42
- } else {
43
- setIsError(false);
44
- setResult('Already logged in');
45
- }
60
+ };
61
+ void asyncVerify();
46
62
  }, [
47
- subscriber,
48
63
  verify
49
64
  ]);
50
65
  useEffect(()=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/app/VerifyMagicLink.tsx"],"sourcesContent":["'use client'\n\nimport { useSearchParams } from 'next/navigation.js'\nimport { useEffect, useState } from 'react'\n\nimport type { RequestMagicLinkResponse } from '../../endpoints/requestMagicLink.js'\nimport type { VerifyMagicLinkResponse } from '../../endpoints/verifyMagicLink.js'\n\nexport { VerifyMagicLinkResponse }\nimport { RequestMagicLink, useSubscriber } from '../../exports/ui.js'\nimport { useRequestMagicLink } from '../../hooks/useRequestMagicLink.js'\nimport { useVerifyMagicLink } from '../../hooks/useVerifyMagicLink.js'\nimport { mergeClassNames } from './helpers.js'\nimport styles from './shared.module.css'\n\n// const payload = await getPayload({\n// config: configPromise,\n// })\n\n/**\n * Props for the VerifyMagicLink component.\n *\n * @property children - Optional React nodes rendered after the verify action (e.g. when error)\n * @property classNames - Optional CSS class overrides for the component elements\n * @property handleMagicLinkRequested - Callback when a new magic link is requested\n * @property handleMagicLinkVerified - Callback when the magic link is verified\n * @property verifyData - Optional data for verification (e.g. email/token from URL)\n */\nexport interface IVerifyMagicLink {\n children?: React.ReactNode\n classNames?: VerifyMagicLinkClasses\n handleMagicLinkRequested?: (result: RequestMagicLinkResponse) => void\n handleMagicLinkVerified?: (result: string) => void\n verifyData?: string\n}\n\n/**\n * Optional CSS class overrides for VerifyMagicLink elements.\n *\n * @property button - Class for buttons\n * @property container - Class for the main container\n * @property emailInput - Class for the email input field\n * @property error - Class for error messages\n * @property form - Class for the form\n * @property loading - Class for loading state\n * @property message - Class for result message text\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\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\" and optional callbacks.\n *\n * @param props - Component props (see IVerifyMagicLink)\n * @param props.children - Optional React nodes rendered after the verify action (e.g. when error)\n * @param props.classNames - Optional class overrides for the component elements\n * @param props.handleMagicLinkRequested - Callback when a new magic link is requested\n * @param props.handleMagicLinkVerified - Callback when the magic link is verified\n * @param props.verifyData - Optional data for verification (e.g. email/token from URL)\n * @returns Loading status, error/result message, and children. Shows RequestMagicLink when no token/email.\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 verifyData,\n}: IVerifyMagicLink) => {\n const searchParams = useSearchParams()\n const email = searchParams.get('email')\n const token = searchParams.get('token')\n\n const { subscriber } = useSubscriber()\n\n const {\n isError: verifyIsError,\n isLoading: verifyIsLoading,\n result: verifyResult,\n verify,\n } = useVerifyMagicLink()\n\n useEffect(() => {\n async function asyncVerify() {\n await verify()\n }\n if (!subscriber) {\n void asyncVerify()\n } else {\n setIsError(false)\n setResult('Already logged in')\n }\n }, [subscriber, verify])\n\n useEffect(() => {\n setResult(verifyResult)\n setIsError(verifyIsError)\n setIsLoading(verifyIsLoading)\n if (!verifyIsError && handleMagicLinkVerified) {\n handleMagicLinkVerified(verifyResult)\n }\n }, [handleMagicLinkVerified, verifyResult, verifyIsError, verifyIsLoading])\n\n const [result, setResult] = useState<string>()\n const [isError, setIsError] = useState<boolean>(false)\n const [isLoading, setIsLoading] = useState<boolean>(false)\n\n const {\n result: requestResult,\n sendMagicLink,\n status: requestStatus,\n } = useRequestMagicLink({\n handleMagicLinkRequested,\n verifyData,\n })\n\n useEffect(() => {\n setIsError(requestStatus == 'error')\n setResult(requestResult)\n setIsLoading(false)\n }, [requestResult, requestStatus])\n\n const handleRequestAnother = () => {\n if (email) {\n void sendMagicLink(email)\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 {isLoading && (\n <p\n className={mergeClassNames([\n 'subscribers-loading',\n styles.loading,\n classNames.loading,\n ])}\n >\n verifying...\n </p>\n )}\n {!isLoading && 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 && isError && (\n <button\n className={mergeClassNames([\n 'subscribers-button',\n styles.button,\n classNames.button,\n ])}\n name={'request'}\n onClick={handleRequestAnother}\n type=\"button\"\n >\n {'Request another magic link'}\n </button>\n )}\n {result && children}\n </div>\n </div>\n )}\n </>\n )\n}\n"],"names":["useSearchParams","useEffect","useState","RequestMagicLink","useSubscriber","useRequestMagicLink","useVerifyMagicLink","mergeClassNames","styles","VerifyMagicLink","children","classNames","button","container","emailInput","error","form","loading","message","handleMagicLinkRequested","handleMagicLinkVerified","verifyData","searchParams","email","get","token","subscriber","isError","verifyIsError","isLoading","verifyIsLoading","result","verifyResult","verify","asyncVerify","setIsError","setResult","setIsLoading","requestResult","sendMagicLink","status","requestStatus","handleRequestAnother","div","className","p","name","onClick","type"],"mappings":"AAAA;;AAEA,SAASA,eAAe,QAAQ,qBAAoB;AACpD,SAASC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAM3C,SAASC,gBAAgB,EAAEC,aAAa,QAAQ,sBAAqB;AACrE,SAASC,mBAAmB,QAAQ,qCAAoC;AACxE,SAASC,kBAAkB,QAAQ,oCAAmC;AACtE,SAASC,eAAe,QAAQ,eAAc;AAC9C,OAAOC,YAAY,sBAAqB;AA4CxC;;;;;;;;;;;;CAYC,GACD,OAAO,MAAMC,kBAAkB,CAAC,EAC9BC,QAAQ,EACRC,aAAa;IACXC,QAAQ;IACRC,WAAW;IACXC,YAAY;IACZC,OAAO;IACPC,MAAM;IACNC,SAAS;IACTC,SAAS;AACX,CAAC,EACDC,wBAAwB,EACxBC,uBAAuB,EACvBC,UAAU,EACO;IACjB,MAAMC,eAAetB;IACrB,MAAMuB,QAAQD,aAAaE,GAAG,CAAC;IAC/B,MAAMC,QAAQH,aAAaE,GAAG,CAAC;IAE/B,MAAM,EAAEE,UAAU,EAAE,GAAGtB;IAEvB,MAAM,EACJuB,SAASC,aAAa,EACtBC,WAAWC,eAAe,EAC1BC,QAAQC,YAAY,EACpBC,MAAM,EACP,GAAG3B;IAEJL,UAAU;QACR,eAAeiC;YACb,MAAMD;QACR;QACA,IAAI,CAACP,YAAY;YACf,KAAKQ;QACP,OAAO;YACLC,WAAW;YACXC,UAAU;QACZ;IACF,GAAG;QAACV;QAAYO;KAAO;IAEvBhC,UAAU;QACRmC,UAAUJ;QACVG,WAAWP;QACXS,aAAaP;QACb,IAAI,CAACF,iBAAiBR,yBAAyB;YAC7CA,wBAAwBY;QAC1B;IACF,GAAG;QAACZ;QAAyBY;QAAcJ;QAAeE;KAAgB;IAE1E,MAAM,CAACC,QAAQK,UAAU,GAAGlC;IAC5B,MAAM,CAACyB,SAASQ,WAAW,GAAGjC,SAAkB;IAChD,MAAM,CAAC2B,WAAWQ,aAAa,GAAGnC,SAAkB;IAEpD,MAAM,EACJ6B,QAAQO,aAAa,EACrBC,aAAa,EACbC,QAAQC,aAAa,EACtB,GAAGpC,oBAAoB;QACtBc;QACAE;IACF;IAEApB,UAAU;QACRkC,WAAWM,iBAAiB;QAC5BL,UAAUE;QACVD,aAAa;IACf,GAAG;QAACC;QAAeG;KAAc;IAEjC,MAAMC,uBAAuB;QAC3B,IAAInB,OAAO;YACT,KAAKgB,cAAchB;QACrB;IACF;IAEA,qBACE;;YACI,CAAA,CAACA,SAAS,CAACE,KAAI,mBAAM,KAACtB;gBAAiBQ,YAAYA;;YACpDY,SAASE,uBACR,MAACkB;gBACCC,WAAWrC,gBAAgB;oBACzB;oBACAC,OAAOK,SAAS;oBAChBF,WAAWE,SAAS;iBACrB;;oBAEAgB,2BACC,KAACgB;wBACCD,WAAWrC,gBAAgB;4BACzB;4BACAC,OAAOS,OAAO;4BACdN,WAAWM,OAAO;yBACnB;kCACF;;oBAIF,CAACY,aAAaE,wBACb,KAACc;wBACCD,WAAWrC,gBAAgB;4BACzB;4BACAC,OAAOU,OAAO;4BACdP,WAAWO,OAAO;4BAClBS,UAAU;gCAAC;gCAAqBnB,OAAOO,KAAK;gCAAEJ,WAAWI,KAAK;6BAAC,GAAG,EAAE;yBACrE;kCAEAgB;;kCAGL,MAACY;wBAAIC,WAAWrC,gBAAgB;4BAAC;4BAAoBC,OAAOQ,IAAI;4BAAEL,WAAWK,IAAI;yBAAC;;4BAC/Ee,UAAUJ,yBACT,KAACf;gCACCgC,WAAWrC,gBAAgB;oCACzB;oCACAC,OAAOI,MAAM;oCACbD,WAAWC,MAAM;iCAClB;gCACDkC,MAAM;gCACNC,SAASL;gCACTM,MAAK;0CAEJ;;4BAGJjB,UAAUrB;;;;;;;AAMvB,EAAC"}
1
+ {"version":3,"sources":["../../../src/components/app/VerifyMagicLink.tsx"],"sourcesContent":["'use client'\n\nimport { useSearchParams } from 'next/navigation.js'\nimport { Suspense, useEffect, useState } from 'react'\n\nimport type { RequestMagicLinkResponse } from '../../endpoints/requestMagicLink.js'\nimport type { VerifyMagicLinkResponse } from '../../endpoints/verifyMagicLink.js'\n\nexport { VerifyMagicLinkResponse }\nimport { RequestMagicLink, useSubscriber } from '../../exports/ui.js'\nimport { useRequestMagicLink } from '../../hooks/useRequestMagicLink.js'\nimport { useVerifyMagicLink } from '../../hooks/useVerifyMagicLink.js'\nimport { mergeClassNames } from './helpers.js'\nimport styles from './shared.module.css'\n\n// const payload = await getPayload({\n// config: configPromise,\n// })\n\n/**\n * Props for the VerifyMagicLink component.\n *\n * @property children - Optional React nodes rendered after the verify action (e.g. when error)\n * @property classNames - Optional CSS class overrides for the component elements\n * @property handleMagicLinkRequested - Callback when a new magic link is requested\n * @property handleMagicLinkVerified - Callback when the magic link is verified\n * @property verifyData - Optional data for verification (e.g. email/token from URL)\n */\nexport interface IVerifyMagicLink {\n children?: React.ReactNode\n classNames?: VerifyMagicLinkClasses\n handleMagicLinkRequested?: (result: RequestMagicLinkResponse) => void\n handleMagicLinkVerified?: (result: string) => void\n verifyData?: string\n}\n\n/**\n * Optional CSS class overrides for VerifyMagicLink elements.\n *\n * @property button - Class for buttons\n * @property container - Class for the main container\n * @property emailInput - Class for the email input field\n * @property error - Class for error messages\n * @property form - Class for the form\n * @property loading - Class for loading state\n * @property message - Class for result message text\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\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\" and optional callbacks.\n *\n * @param props - Component props (see IVerifyMagicLink)\n * @param props.children - Optional React nodes rendered after the verify action (e.g. when error)\n * @param props.classNames - Optional class overrides for the component elements\n * @param props.handleMagicLinkRequested - Callback when a new magic link is requested\n * @param props.handleMagicLinkVerified - Callback when the magic link is verified\n * @param props.verifyData - Optional data for verification (e.g. email/token from URL)\n * @returns Loading status, error/result message, and children. Shows RequestMagicLink when no token/email.\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 verifyData,\n}: IVerifyMagicLink) => {\n return (\n <Suspense fallback={<div>Verifying...</div>}>\n <VerifyMagicLinkInSuspense\n classNames={classNames}\n handleMagicLinkRequested={handleMagicLinkRequested}\n handleMagicLinkVerified={handleMagicLinkVerified}\n verifyData={verifyData}\n >\n {children}\n </VerifyMagicLinkInSuspense>\n </Suspense>\n )\n}\n\nexport const VerifyMagicLinkInSuspense = ({\n children,\n classNames = {\n button: '',\n container: '',\n emailInput: '',\n error: '',\n form: '',\n loading: '',\n message: '',\n },\n handleMagicLinkRequested,\n handleMagicLinkVerified,\n verifyData,\n}: IVerifyMagicLink) => {\n const searchParams = useSearchParams()\n const email = searchParams.get('email')\n const token = searchParams.get('token')\n\n const {\n isError: verifyIsError,\n isLoading: verifyIsLoading,\n result: verifyResult,\n verify,\n } = useVerifyMagicLink()\n\n useEffect(() => {\n const asyncVerify = async () => {\n await verify()\n }\n void asyncVerify()\n }, [verify])\n\n useEffect(() => {\n setResult(verifyResult)\n setIsError(verifyIsError)\n setIsLoading(verifyIsLoading)\n if (!verifyIsError && handleMagicLinkVerified) {\n handleMagicLinkVerified(verifyResult)\n }\n }, [handleMagicLinkVerified, verifyResult, verifyIsError, verifyIsLoading])\n\n const [result, setResult] = useState<string>()\n const [isError, setIsError] = useState<boolean>(false)\n const [isLoading, setIsLoading] = useState<boolean>(false)\n\n const {\n result: requestResult,\n sendMagicLink,\n status: requestStatus,\n } = useRequestMagicLink({\n handleMagicLinkRequested,\n verifyData,\n })\n\n useEffect(() => {\n setIsError(requestStatus == 'error')\n setResult(requestResult)\n setIsLoading(false)\n }, [requestResult, requestStatus])\n\n const handleRequestAnother = () => {\n if (email) {\n void sendMagicLink(email)\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 {isLoading && (\n <p\n className={mergeClassNames([\n 'subscribers-loading',\n styles.loading,\n classNames.loading,\n ])}\n >\n verifying...\n </p>\n )}\n {!isLoading && 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 && isError && (\n <button\n className={mergeClassNames([\n 'subscribers-button',\n styles.button,\n classNames.button,\n ])}\n name={'request'}\n onClick={handleRequestAnother}\n type=\"button\"\n >\n {'Request another magic link'}\n </button>\n )}\n {result && children}\n </div>\n </div>\n )}\n </>\n )\n}\n"],"names":["useSearchParams","Suspense","useEffect","useState","RequestMagicLink","useRequestMagicLink","useVerifyMagicLink","mergeClassNames","styles","VerifyMagicLink","children","classNames","button","container","emailInput","error","form","loading","message","handleMagicLinkRequested","handleMagicLinkVerified","verifyData","fallback","div","VerifyMagicLinkInSuspense","searchParams","email","get","token","isError","verifyIsError","isLoading","verifyIsLoading","result","verifyResult","verify","asyncVerify","setResult","setIsError","setIsLoading","requestResult","sendMagicLink","status","requestStatus","handleRequestAnother","className","p","name","onClick","type"],"mappings":"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"}
@@ -18,32 +18,33 @@ const SubscriberContext = /*#__PURE__*/ createContext(undefined);
18
18
  // Keep track of if the selection content is loaded yet
19
19
  const [isLoaded, setIsLoaded] = useState(false);
20
20
  const [permissions, setPermissions] = useState();
21
- const refreshSubscriber = useCallback(async ()=>{
22
- const initSubscriber = async ()=>{
23
- setIsLoaded(false);
24
- try {
25
- const authResponse = await fetch('/api/subscriberAuth', {
26
- // body: JSON.stringify({}),
27
- method: 'POST'
28
- });
29
- if (authResponse.ok) {
30
- // Call the server function to get the user data
31
- const authResponseJson = await authResponse.json();
32
- // console.log('authResponseJson', JSON.stringify(authResponseJson, undefined, 2))
33
- const { permissions, subscriber } = authResponseJson;
34
- // console.log(`subscriber = `, subscriber)
35
- // console.log(`permissions = `, permissions)
36
- setPermissions(permissions);
37
- setSubscriber(subscriber);
38
- } else {
39
- setPermissions(null);
40
- setSubscriber(null);
41
- }
42
- } catch (error) {
43
- console.log(`authResponse error`, error);
21
+ const initSubscriber = async ()=>{
22
+ console.log('initSubscriber');
23
+ setIsLoaded(false);
24
+ try {
25
+ const authResponse = await fetch('/api/subscriberAuth', {
26
+ // body: JSON.stringify({}),
27
+ method: 'POST'
28
+ });
29
+ if (authResponse.ok) {
30
+ // Call the server function to get the user data
31
+ const authResponseJson = await authResponse.json();
32
+ // console.log('authResponseJson', JSON.stringify(authResponseJson, undefined, 2))
33
+ const { permissions, subscriber } = authResponseJson;
34
+ // console.log(`subscriber = `, subscriber)
35
+ // console.log(`permissions = `, permissions)
36
+ setPermissions(permissions);
37
+ setSubscriber(subscriber);
38
+ } else {
39
+ setPermissions(null);
40
+ setSubscriber(null);
44
41
  }
45
- setIsLoaded(true);
46
- };
42
+ } catch (error) {
43
+ console.log(`authResponse error`, error);
44
+ }
45
+ setIsLoaded(true);
46
+ };
47
+ const refreshSubscriber = useCallback(async ()=>{
47
48
  await initSubscriber();
48
49
  }, [
49
50
  serverURL
@@ -74,10 +75,8 @@ const SubscriberContext = /*#__PURE__*/ createContext(undefined);
74
75
  setIsLoaded(true);
75
76
  }, []);
76
77
  useEffect(()=>{
77
- void refreshSubscriber();
78
- }, [
79
- refreshSubscriber
80
- ]);
78
+ void initSubscriber();
79
+ }, []);
81
80
  // Memoize the value to prevent unnecessary re-renders in consumers
82
81
  const contextValue = useMemo(()=>({
83
82
  isLoaded,
@@ -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, OptInChannel, 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: ({ optIns?: null | OptInChannel[] } & Omit<Subscriber, 'optIns'>) | null\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: OptInChannel[] })>(\n null,\n )\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 authResponseJson = await authResponse.json()\n // console.log('authResponseJson', JSON.stringify(authResponseJson, undefined, 2))\n const { permissions, subscriber } = authResponseJson\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])\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","authResponseJson","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,SAClC;IAGF,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,MAAMC,mBAAmB,MAAMJ,aAAaK,IAAI;oBAChD,kFAAkF;oBAClF,MAAM,EAAET,WAAW,EAAEL,UAAU,EAAE,GAAGa;oBACpC,2CAA2C;oBAC3C,6CAA6C;oBAC7CP,eAAeD;oBACfJ,cAAcD;gBAChB,OAAO;oBACLM,eAAe;oBACfL,cAAc;gBAChB;YACF,EAAE,OAAOc,OAAgB;gBACvBC,QAAQC,GAAG,CAAC,CAAC,kBAAkB,CAAC,EAAEF;YACpC;YACAX,YAAY;QACd;QACA,MAAMI;IACR,GAAG;QAACN;KAAU;IAEd,MAAMgB,SAAS7B,YAAY;QACzBe,YAAY;QACZ,IAAI;YACF,uCAAuC;YACvC,8BAA8B;YAC9B,KAAK;YACL,6CAA6C;YAC7C,cAAc;YACd,oBAAoB;YACpB,yBAAyB;YACzB,KAAK;YACL,oCAAoC;YACpC,MAAMe,iBAAiB,MAAMT,MAAM,eAAe;gBAChDC,QAAQ;YACV;YAEA,gDAAgD;YAEhD,IAAIQ,eAAeP,EAAE,EAAE;gBACrBX,cAAc;gBACdK,eAAe;YACjB;QACF,EAAE,OAAOS,OAAgB;YACvBC,QAAQC,GAAG,CAAC,CAAC,oBAAoB,CAAC,EAAEF;QACtC;QACAX,YAAY;IACd,GAAG,EAAE;IAELd,UAAU;QACR,KAAKiB;IACP,GAAG;QAACA;KAAkB;IAEtB,mEAAmE;IACnE,MAAMa,eAAsC3B,QAC1C,IAAO,CAAA;YACLU;YACAe;YACAb;YACAE;YACAP;QACF,CAAA,GACA;QAACG;QAAUe;QAAQb;QAAaE;QAAmBP;KAAW;IAGhE,qBAAO,KAACJ,kBAAkByB,QAAQ;QAACC,OAAOF;kBAAerB;;AAC3D;AAEA;;;;;CAKC,GACD,OAAO,SAASwB;IACd,MAAMC,UAAUhC,WAAWI;IAC3B,IAAI4B,YAAY3B,WAAW;QACzB,MAAM,IAAI4B,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, OptInChannel, 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: ({ optIns?: null | OptInChannel[] } & Omit<Subscriber, 'optIns'>) | null\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: OptInChannel[] })>(\n null,\n )\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 initSubscriber = async () => {\n console.log('initSubscriber')\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 authResponseJson = await authResponse.json()\n // console.log('authResponseJson', JSON.stringify(authResponseJson, undefined, 2))\n const { permissions, subscriber } = authResponseJson\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\n const refreshSubscriber = useCallback(async () => {\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 initSubscriber()\n }, [])\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","initSubscriber","console","log","authResponse","fetch","method","ok","authResponseJson","json","error","refreshSubscriber","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,SAClC;IAGF,MAAM,EAAEQ,SAAS,EAAE,GAAGP;IAEtB,uDAAuD;IACvD,MAAM,CAACQ,UAAUC,YAAY,GAAGV,SAAS;IAEzC,MAAM,CAACW,aAAaC,eAAe,GAAGZ;IAEtC,MAAMa,iBAAiB;QACrBC,QAAQC,GAAG,CAAC;QACZL,YAAY;QACZ,IAAI;YACF,MAAMM,eAAe,MAAMC,MAAM,uBAAuB;gBACtD,4BAA4B;gBAC5BC,QAAQ;YACV;YAEA,IAAIF,aAAaG,EAAE,EAAE;gBACnB,gDAAgD;gBAChD,MAAMC,mBAAmB,MAAMJ,aAAaK,IAAI;gBAChD,kFAAkF;gBAClF,MAAM,EAAEV,WAAW,EAAEL,UAAU,EAAE,GAAGc;gBACpC,2CAA2C;gBAC3C,6CAA6C;gBAC7CR,eAAeD;gBACfJ,cAAcD;YAChB,OAAO;gBACLM,eAAe;gBACfL,cAAc;YAChB;QACF,EAAE,OAAOe,OAAgB;YACvBR,QAAQC,GAAG,CAAC,CAAC,kBAAkB,CAAC,EAAEO;QACpC;QACAZ,YAAY;IACd;IAEA,MAAMa,oBAAoB5B,YAAY;QACpC,MAAMkB;IACR,GAAG;QAACL;KAAU;IAEd,MAAMgB,SAAS7B,YAAY;QACzBe,YAAY;QACZ,IAAI;YACF,uCAAuC;YACvC,8BAA8B;YAC9B,KAAK;YACL,6CAA6C;YAC7C,cAAc;YACd,oBAAoB;YACpB,yBAAyB;YACzB,KAAK;YACL,oCAAoC;YACpC,MAAMe,iBAAiB,MAAMR,MAAM,eAAe;gBAChDC,QAAQ;YACV;YAEA,gDAAgD;YAEhD,IAAIO,eAAeN,EAAE,EAAE;gBACrBZ,cAAc;gBACdK,eAAe;YACjB;QACF,EAAE,OAAOU,OAAgB;YACvBR,QAAQC,GAAG,CAAC,CAAC,oBAAoB,CAAC,EAAEO;QACtC;QACAZ,YAAY;IACd,GAAG,EAAE;IAELd,UAAU;QACR,KAAKiB;IACP,GAAG,EAAE;IAEL,mEAAmE;IACnE,MAAMa,eAAsC3B,QAC1C,IAAO,CAAA;YACLU;YACAe;YACAb;YACAY;YACAjB;QACF,CAAA,GACA;QAACG;QAAUe;QAAQb;QAAaY;QAAmBjB;KAAW;IAGhE,qBAAO,KAACJ,kBAAkByB,QAAQ;QAACC,OAAOF;kBAAerB;;AAC3D;AAEA;;;;;CAKC,GACD,OAAO,SAASwB;IACd,MAAMC,UAAUhC,WAAWI;IAC3B,IAAI4B,YAAY3B,WAAW;QACzB,MAAM,IAAI4B,MAAM;IAClB;IACA,OAAOD;AACT"}
@@ -31,23 +31,28 @@ import { useServerUrl } from '../react-hooks/useServerUrl.js';
31
31
  const { emailResult, error } = emailTokenResponseJson;
32
32
  if (error) {
33
33
  setStatus('error');
34
- setResult(`An error occured. Please try again. \n ${error?.error ? error.error : error}`);
34
+ setResult(`An error occured. Please try again. \n ${JSON.stringify(error?.error ? error.error : error, undefined, 2)}`);
35
+ console.log('emailToken error result: ', result);
35
36
  } else if (emailResult) {
36
37
  setStatus('sent');
37
38
  setResult('An email has been sent containing your magic link.');
39
+ console.log('emailToken error result: ', result);
38
40
  } else {
39
41
  setStatus('error');
40
42
  setResult(`An error occured. Please try again. \nResult unknown`);
43
+ console.log('emailToken error result: ', result);
41
44
  }
42
45
  } else {
43
46
  try {
44
47
  const emailTokenResponseJson = await emailTokenResponse.json();
45
48
  setStatus('error');
46
- setResult(`An error occured. Please try again. \n${emailTokenResponseJson?.error ? emailTokenResponseJson.error : emailTokenResponseJson}`);
49
+ setResult(`An error occured. Please try again. \n${JSON.stringify(emailTokenResponseJson?.error ? emailTokenResponseJson.error : emailTokenResponseJson, undefined, 2)}`);
50
+ console.log('emailToken error result: ', result);
47
51
  } catch (ignore) {
48
52
  const emailTokenResponseText = await emailTokenResponse.text();
49
53
  setStatus('error');
50
54
  setResult(`An error occured. Please try again. \n${emailTokenResponseText}`);
55
+ console.log('emailToken error result: ', result);
51
56
  }
52
57
  }
53
58
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/useRequestMagicLink.tsx"],"sourcesContent":["'use client'\n\nimport { useState } from 'react'\n\nimport type { RequestMagicLinkResponse } from '../endpoints/requestMagicLink.js'\n\nimport { useServerUrl } from '../react-hooks/useServerUrl.js'\n\nexport { RequestMagicLinkResponse }\n\n/**\n * Options for the useRequestMagicLink hook.\n *\n * @property handleMagicLinkRequested - Callback when a magic link is successfully requested\n * @property verifyData - Optional data sent with the request (e.g. for verification redirect)\n */\nexport interface IUseRequestMagicLinkOptions {\n handleMagicLinkRequested?: (result: RequestMagicLinkResponse) => void\n verifyData?: string\n}\n\n/**\n * Return value of useRequestMagicLink.\n *\n * @property result - Success or error message from the last request\n * @property sendMagicLink - Sends a magic link email for the given address\n * @property status - Current status: 'default' | 'sending' | 'sent' | 'error'\n */\nexport interface IUseRequestMagicLink {\n result?: string\n sendMagicLink: (email: string) => Promise<void>\n status?: RequestMagicLinkStatusValue\n}\n\nexport type RequestMagicLinkStatusValue = 'default' | 'error' | 'sending' | 'sent'\n\n/**\n * Hook to request a magic-login link by email. Calls POST /api/emailToken and exposes\n * sendMagicLink, plus result message and status for UI.\n *\n * @param options - Hook options (see IUseRequestMagicLinkOptions)\n * @param options.handleMagicLinkRequested - Callback when a magic link is successfully requested\n * @param options.verifyData - Optional data sent with the request (e.g. for verification redirect)\n * @returns sendMagicLink function, result message, and status (see IUseRequestMagicLink)\n */\nexport const useRequestMagicLink = ({\n handleMagicLinkRequested,\n verifyData,\n}: IUseRequestMagicLinkOptions): IUseRequestMagicLink => {\n const { serverURL } = useServerUrl()\n\n const [status, setStatus] = useState<RequestMagicLinkStatusValue>('default')\n const [result, setResult] = useState<string>()\n\n const sendMagicLink = async (email: string): Promise<void> => {\n setStatus('sending')\n const emailTokenResponse = await fetch(`${serverURL ? serverURL : ''}/api/emailToken`, {\n body: JSON.stringify({\n email,\n verifyData,\n }),\n method: 'POST',\n })\n if (emailTokenResponse.ok) {\n const emailTokenResponseJson: RequestMagicLinkResponse = await emailTokenResponse.json()\n if (handleMagicLinkRequested) {\n handleMagicLinkRequested(emailTokenResponseJson)\n }\n // @ts-expect-error One or the other exists\n const { emailResult, error } = emailTokenResponseJson\n if (error) {\n setStatus('error')\n setResult(`An error occured. Please try again. \\n ${error?.error ? error.error : error}`)\n } else if (emailResult) {\n setStatus('sent')\n setResult('An email has been sent containing your magic link.')\n } else {\n setStatus('error')\n setResult(`An error occured. Please try again. \\nResult unknown`)\n }\n } else {\n try {\n const emailTokenResponseJson = await emailTokenResponse.json()\n setStatus('error')\n setResult(\n `An error occured. Please try again. \\n${emailTokenResponseJson?.error ? emailTokenResponseJson.error : emailTokenResponseJson}`,\n )\n } catch (ignore) {\n const emailTokenResponseText = await emailTokenResponse.text()\n setStatus('error')\n setResult(`An error occured. Please try again. \\n${emailTokenResponseText}`)\n }\n }\n }\n return {\n result,\n sendMagicLink,\n status,\n }\n}\n"],"names":["useState","useServerUrl","useRequestMagicLink","handleMagicLinkRequested","verifyData","serverURL","status","setStatus","result","setResult","sendMagicLink","email","emailTokenResponse","fetch","body","JSON","stringify","method","ok","emailTokenResponseJson","json","emailResult","error","ignore","emailTokenResponseText","text"],"mappings":"AAAA;AAEA,SAASA,QAAQ,QAAQ,QAAO;AAIhC,SAASC,YAAY,QAAQ,iCAAgC;AA8B7D;;;;;;;;CAQC,GACD,OAAO,MAAMC,sBAAsB,CAAC,EAClCC,wBAAwB,EACxBC,UAAU,EACkB;IAC5B,MAAM,EAAEC,SAAS,EAAE,GAAGJ;IAEtB,MAAM,CAACK,QAAQC,UAAU,GAAGP,SAAsC;IAClE,MAAM,CAACQ,QAAQC,UAAU,GAAGT;IAE5B,MAAMU,gBAAgB,OAAOC;QAC3BJ,UAAU;QACV,MAAMK,qBAAqB,MAAMC,MAAM,GAAGR,YAAYA,YAAY,GAAG,eAAe,CAAC,EAAE;YACrFS,MAAMC,KAAKC,SAAS,CAAC;gBACnBL;gBACAP;YACF;YACAa,QAAQ;QACV;QACA,IAAIL,mBAAmBM,EAAE,EAAE;YACzB,MAAMC,yBAAmD,MAAMP,mBAAmBQ,IAAI;YACtF,IAAIjB,0BAA0B;gBAC5BA,yBAAyBgB;YAC3B;YACA,2CAA2C;YAC3C,MAAM,EAAEE,WAAW,EAAEC,KAAK,EAAE,GAAGH;YAC/B,IAAIG,OAAO;gBACTf,UAAU;gBACVE,UAAU,CAAC,uCAAuC,EAAEa,OAAOA,QAAQA,MAAMA,KAAK,GAAGA,OAAO;YAC1F,OAAO,IAAID,aAAa;gBACtBd,UAAU;gBACVE,UAAU;YACZ,OAAO;gBACLF,UAAU;gBACVE,UAAU,CAAC,oDAAoD,CAAC;YAClE;QACF,OAAO;YACL,IAAI;gBACF,MAAMU,yBAAyB,MAAMP,mBAAmBQ,IAAI;gBAC5Db,UAAU;gBACVE,UACE,CAAC,sCAAsC,EAAEU,wBAAwBG,QAAQH,uBAAuBG,KAAK,GAAGH,wBAAwB;YAEpI,EAAE,OAAOI,QAAQ;gBACf,MAAMC,yBAAyB,MAAMZ,mBAAmBa,IAAI;gBAC5DlB,UAAU;gBACVE,UAAU,CAAC,sCAAsC,EAAEe,wBAAwB;YAC7E;QACF;IACF;IACA,OAAO;QACLhB;QACAE;QACAJ;IACF;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/hooks/useRequestMagicLink.tsx"],"sourcesContent":["'use client'\n\nimport { useState } from 'react'\n\nimport type { RequestMagicLinkResponse } from '../endpoints/requestMagicLink.js'\n\nimport { useServerUrl } from '../react-hooks/useServerUrl.js'\n\nexport { RequestMagicLinkResponse }\n\n/**\n * Options for the useRequestMagicLink hook.\n *\n * @property handleMagicLinkRequested - Callback when a magic link is successfully requested\n * @property verifyData - Optional data sent with the request (e.g. for verification redirect)\n */\nexport interface IUseRequestMagicLinkOptions {\n handleMagicLinkRequested?: (result: RequestMagicLinkResponse) => void\n verifyData?: string\n}\n\n/**\n * Return value of useRequestMagicLink.\n *\n * @property result - Success or error message from the last request\n * @property sendMagicLink - Sends a magic link email for the given address\n * @property status - Current status: 'default' | 'sending' | 'sent' | 'error'\n */\nexport interface IUseRequestMagicLink {\n result?: string\n sendMagicLink: (email: string) => Promise<void>\n status?: RequestMagicLinkStatusValue\n}\n\nexport type RequestMagicLinkStatusValue = 'default' | 'error' | 'sending' | 'sent'\n\n/**\n * Hook to request a magic-login link by email. Calls POST /api/emailToken and exposes\n * sendMagicLink, plus result message and status for UI.\n *\n * @param options - Hook options (see IUseRequestMagicLinkOptions)\n * @param options.handleMagicLinkRequested - Callback when a magic link is successfully requested\n * @param options.verifyData - Optional data sent with the request (e.g. for verification redirect)\n * @returns sendMagicLink function, result message, and status (see IUseRequestMagicLink)\n */\nexport const useRequestMagicLink = ({\n handleMagicLinkRequested,\n verifyData,\n}: IUseRequestMagicLinkOptions): IUseRequestMagicLink => {\n const { serverURL } = useServerUrl()\n\n const [status, setStatus] = useState<RequestMagicLinkStatusValue>('default')\n const [result, setResult] = useState<string>()\n\n const sendMagicLink = async (email: string): Promise<void> => {\n setStatus('sending')\n const emailTokenResponse = await fetch(`${serverURL ? serverURL : ''}/api/emailToken`, {\n body: JSON.stringify({\n email,\n verifyData,\n }),\n method: 'POST',\n })\n if (emailTokenResponse.ok) {\n const emailTokenResponseJson: RequestMagicLinkResponse = await emailTokenResponse.json()\n if (handleMagicLinkRequested) {\n handleMagicLinkRequested(emailTokenResponseJson)\n }\n // @ts-expect-error One or the other exists\n const { emailResult, error } = emailTokenResponseJson\n if (error) {\n setStatus('error')\n setResult(\n `An error occured. Please try again. \\n ${JSON.stringify(error?.error ? error.error : error, undefined, 2)}`,\n )\n console.log('emailToken error result: ', result)\n } else if (emailResult) {\n setStatus('sent')\n setResult('An email has been sent containing your magic link.')\n console.log('emailToken error result: ', result)\n } else {\n setStatus('error')\n setResult(`An error occured. Please try again. \\nResult unknown`)\n console.log('emailToken error result: ', result)\n }\n } else {\n try {\n const emailTokenResponseJson = await emailTokenResponse.json()\n setStatus('error')\n setResult(\n `An error occured. Please try again. \\n${JSON.stringify(emailTokenResponseJson?.error ? emailTokenResponseJson.error : emailTokenResponseJson, undefined, 2)}`,\n )\n console.log('emailToken error result: ', result)\n } catch (ignore) {\n const emailTokenResponseText = await emailTokenResponse.text()\n setStatus('error')\n setResult(`An error occured. Please try again. \\n${emailTokenResponseText}`)\n console.log('emailToken error result: ', result)\n }\n }\n }\n return {\n result,\n sendMagicLink,\n status,\n }\n}\n"],"names":["useState","useServerUrl","useRequestMagicLink","handleMagicLinkRequested","verifyData","serverURL","status","setStatus","result","setResult","sendMagicLink","email","emailTokenResponse","fetch","body","JSON","stringify","method","ok","emailTokenResponseJson","json","emailResult","error","undefined","console","log","ignore","emailTokenResponseText","text"],"mappings":"AAAA;AAEA,SAASA,QAAQ,QAAQ,QAAO;AAIhC,SAASC,YAAY,QAAQ,iCAAgC;AA8B7D;;;;;;;;CAQC,GACD,OAAO,MAAMC,sBAAsB,CAAC,EAClCC,wBAAwB,EACxBC,UAAU,EACkB;IAC5B,MAAM,EAAEC,SAAS,EAAE,GAAGJ;IAEtB,MAAM,CAACK,QAAQC,UAAU,GAAGP,SAAsC;IAClE,MAAM,CAACQ,QAAQC,UAAU,GAAGT;IAE5B,MAAMU,gBAAgB,OAAOC;QAC3BJ,UAAU;QACV,MAAMK,qBAAqB,MAAMC,MAAM,GAAGR,YAAYA,YAAY,GAAG,eAAe,CAAC,EAAE;YACrFS,MAAMC,KAAKC,SAAS,CAAC;gBACnBL;gBACAP;YACF;YACAa,QAAQ;QACV;QACA,IAAIL,mBAAmBM,EAAE,EAAE;YACzB,MAAMC,yBAAmD,MAAMP,mBAAmBQ,IAAI;YACtF,IAAIjB,0BAA0B;gBAC5BA,yBAAyBgB;YAC3B;YACA,2CAA2C;YAC3C,MAAM,EAAEE,WAAW,EAAEC,KAAK,EAAE,GAAGH;YAC/B,IAAIG,OAAO;gBACTf,UAAU;gBACVE,UACE,CAAC,uCAAuC,EAAEM,KAAKC,SAAS,CAACM,OAAOA,QAAQA,MAAMA,KAAK,GAAGA,OAAOC,WAAW,IAAI;gBAE9GC,QAAQC,GAAG,CAAC,6BAA6BjB;YAC3C,OAAO,IAAIa,aAAa;gBACtBd,UAAU;gBACVE,UAAU;gBACVe,QAAQC,GAAG,CAAC,6BAA6BjB;YAC3C,OAAO;gBACLD,UAAU;gBACVE,UAAU,CAAC,oDAAoD,CAAC;gBAChEe,QAAQC,GAAG,CAAC,6BAA6BjB;YAC3C;QACF,OAAO;YACL,IAAI;gBACF,MAAMW,yBAAyB,MAAMP,mBAAmBQ,IAAI;gBAC5Db,UAAU;gBACVE,UACE,CAAC,sCAAsC,EAAEM,KAAKC,SAAS,CAACG,wBAAwBG,QAAQH,uBAAuBG,KAAK,GAAGH,wBAAwBI,WAAW,IAAI;gBAEhKC,QAAQC,GAAG,CAAC,6BAA6BjB;YAC3C,EAAE,OAAOkB,QAAQ;gBACf,MAAMC,yBAAyB,MAAMf,mBAAmBgB,IAAI;gBAC5DrB,UAAU;gBACVE,UAAU,CAAC,sCAAsC,EAAEkB,wBAAwB;gBAC3EH,QAAQC,GAAG,CAAC,6BAA6BjB;YAC3C;QACF;IACF;IACA,OAAO;QACLA;QACAE;QACAJ;IACF;AACF,EAAC"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CollectionSlug, Config } from 'payload';
1
+ import type { CollectionSlug, Plugin } from 'payload';
2
2
  export type PayloadSubscribersConfig = {
3
3
  /**
4
4
  * List of collections to add a custom field
@@ -44,4 +44,4 @@ export type PayloadSubscribersConfig = {
44
44
  * @param pluginOptions.verifyURL - (optional) The route or full URL for verify links
45
45
  * @returns Payload config modified to include the plugin
46
46
  */
47
- export declare const payloadSubscribersPlugin: (pluginOptions: PayloadSubscribersConfig) => (config: Config) => Config;
47
+ export declare const payloadSubscribersPlugin: (pluginOptions: PayloadSubscribersConfig) => Plugin;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { BasePayload, CollectionSlug, Config } from 'payload'\n\nimport { OptedInChannels } from './collections/fields/OptedInChannels.js'\nimport OptInChannels from './collections/OptInChannels.js'\nimport {\n defaultTokenExpiration,\n SubscribersCollectionFactory,\n subscribersCollectionFields,\n} from './collections/Subscribers.js'\nimport getOptInChannelsEndpoint from './endpoints/getOptInChannels.js'\nimport createEndpointLogout from './endpoints/logout.js'\nimport createEndpointRequestMagicLink from './endpoints/requestMagicLink.js'\nimport createEndpointSubscribe from './endpoints/subscribe.js'\nimport createEndpointSubscriberAuth from './endpoints/subscriberAuth.js'\nimport createEndpointUnsubscribe from './endpoints/unsubscribe.js'\nimport createEndpointVerifyMagicLink from './endpoints/verifyMagicLink.js'\nimport { getTestEmail } from './helpers/testData.js'\nimport { getTokenAndHash } from './helpers/token.js'\nimport { isAbsoluteURL } from './helpers/utilities.js'\n\nexport type PayloadSubscribersConfig = {\n /**\n * List of collections to add a custom field\n */\n collections?: Partial<Record<CollectionSlug, true>>\n /**\n * Defaults to false-y. When true:\n * - Database schema changes are still made and seeded\n * - APIs return null or undefined success\n * - Admin components are not added\n * - App components return nothing\n */\n disabled?: boolean\n /**\n * The collection to use as the subscribers collection\n * - Optional. If not specified, the plugin will add a 'subscribers' collection.\n * - Sets the collection auth if not already.\n * - Adds (or overrides) fields: email, firstName, status, optIns, verificationToken, verificationTokenExpires.\n */\n subscribersCollectionSlug?: CollectionSlug\n /**\n * Defaults to 30 minutes\n */\n tokenExpiration?: number\n /**\n * The route or full URL for unsubscribe links\n */\n unsubscribeURL?: string\n /**\n * The route or full URL for verify links\n */\n verifyURL?: string\n}\n\n/**\n * Adds the payload-subscribers-plugin to your payload config\n *\n * @param pluginOptions - Plugin options\n * @param pluginOptions.collections - (optional) An array of existing collection slugs to add an optIns relationship field to\n * @param pluginOptions.disabled - (optional) A convenience option to disable the plugin\n * @param pluginOptions.subscribersCollectionSlug - (optional) The slug of an existing collection to use for subscribers. If omitted, the plugin will create the 'subscribers' collection\n * @param pluginOptions.tokenExpiration - (optional) The expiration time for a token, in milliseconds. Defaults to 30 minutes\n * @param pluginOptions.unsubscribeURL - (optional) The route or full URL for unsubscribe links\n * @param pluginOptions.verifyURL - (optional) The route or full URL for verify links\n * @returns Payload config modified to include the plugin\n */\nexport const payloadSubscribersPlugin =\n (pluginOptions: PayloadSubscribersConfig) =>\n (config: Config): Config => {\n if (!config.serverURL && !(pluginOptions.unsubscribeURL && pluginOptions.verifyURL)) {\n throw new Error(\n 'payloadSubscribersPlugin requires config.serverURL OR valid values for all URL options: unsubscribeURL, verifyURL',\n )\n }\n\n if (!config.collections) {\n config.collections = []\n }\n\n config.collections.push(OptInChannels)\n\n const unsubscribeURL = !pluginOptions.unsubscribeURL\n ? new URL('/unsubscribe', config.serverURL)\n : isAbsoluteURL(pluginOptions.unsubscribeURL)\n ? new URL(pluginOptions.unsubscribeURL)\n : new URL(pluginOptions.unsubscribeURL, config.serverURL)\n\n // Get a URL object from the verifyURL option\n const verifyURL = !pluginOptions.verifyURL\n ? new URL('/verify', config.serverURL)\n : isAbsoluteURL(pluginOptions.verifyURL)\n ? new URL(pluginOptions.verifyURL)\n : new URL(pluginOptions.verifyURL, config.serverURL)\n\n let subscribersCollection = pluginOptions.subscribersCollectionSlug\n ? config.collections.find(\n (collection) => collection.slug == pluginOptions.subscribersCollectionSlug,\n )\n : undefined\n\n if (subscribersCollection) {\n // Configure the input collection to be the subscribers collection\n config.collections = config.collections.filter(\n (collection) => collection.slug != subscribersCollection?.slug,\n )\n subscribersCollection.fields.push(...subscribersCollectionFields)\n if (!subscribersCollection.auth) {\n subscribersCollection = {\n ...subscribersCollection,\n auth: { tokenExpiration: defaultTokenExpiration },\n }\n }\n if (!subscribersCollection.admin?.useAsTitle) {\n if (!subscribersCollection.admin) {\n subscribersCollection.admin = { useAsTitle: 'email' }\n } else {\n // Throw error? Or override?\n subscribersCollection.admin.useAsTitle = 'email'\n }\n }\n config.collections.push(subscribersCollection)\n } else {\n // Configure the default built-in subscribers collection\n subscribersCollection = SubscribersCollectionFactory({\n slug: pluginOptions.subscribersCollectionSlug,\n tokenExpiration: pluginOptions.tokenExpiration,\n })\n config.collections.push(subscribersCollection)\n }\n\n if (pluginOptions.collections) {\n for (const collectionSlug in pluginOptions.collections) {\n const collection = config.collections.find(\n (collection) => collection.slug === collectionSlug,\n )\n\n if (collection) {\n collection.fields.push(OptedInChannels)\n }\n }\n }\n\n /**\n * If the plugin is disabled, we still want to keep added collections/fields so the database schema is consistent which is important for migrations.\n * If your plugin heavily modifies the database schema, you may want to remove this property.\n */\n if (pluginOptions.disabled) {\n return config\n }\n\n if (!config.admin) {\n config.admin = {}\n }\n\n if (!config.admin.components) {\n config.admin.components = {}\n }\n\n if (!config.admin.components.beforeDashboard) {\n config.admin.components.beforeDashboard = []\n }\n\n if (!config.endpoints) {\n config.endpoints = []\n }\n\n config.endpoints.push(\n getOptInChannelsEndpoint,\n createEndpointLogout({\n subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,\n }),\n createEndpointRequestMagicLink({\n subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,\n unsubscribeURL,\n verifyURL,\n }),\n createEndpointSubscribe({\n subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,\n unsubscribeURL,\n verifyURL,\n }),\n createEndpointSubscriberAuth({\n subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,\n }),\n createEndpointUnsubscribe({\n subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,\n }),\n createEndpointVerifyMagicLink({\n subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,\n }),\n )\n\n const incomingOnInit = config.onInit\n\n const genInit = (testData: { testEmail: string }) => async (payload: BasePayload) => {\n // Ensure we are executing any existing onInit functions before running our own.\n if (incomingOnInit) {\n await incomingOnInit(payload)\n }\n\n // console.log('Object.keys(payload.collections)', Object.keys(payload.collections))\n const { totalDocs: totalOptIns } = await payload.count({\n collection: 'opt-in-channels',\n where: {\n title: {\n equals: 'seeded-by-plugin',\n },\n },\n })\n\n if (totalOptIns === 0) {\n await payload.create({\n collection: 'opt-in-channels',\n data: {\n active: true,\n title: 'seeded-by-plugin',\n },\n })\n }\n\n // const { seededChannel } = await payload.find({\n // collection: 'opt-in-channels',\n // where: {\n // title: {\n // equals: 'seeded-by-plugin',\n // },\n // },\n // })\n\n const { totalDocs: totalSubscribers } = await payload.count({\n collection: subscribersCollection.slug as CollectionSlug,\n where: {\n email: {\n equals: testData.testEmail,\n },\n },\n })\n\n const { tokenHash } = getTokenAndHash() // Unknowable\n // payload.logger.info(`testData.testEmail == '${testData.testEmail}'`)\n if (totalSubscribers === 0) {\n await payload.create({\n collection: subscribersCollection.slug as CollectionSlug,\n data: {\n email: testData.testEmail,\n password: tokenHash,\n status: 'pending',\n },\n })\n }\n }\n\n // console.log(`getTestEmail == '${getTestEmail()}'`)\n config.onInit = genInit({ testEmail: getTestEmail() })\n\n return config\n }\n"],"names":["OptedInChannels","OptInChannels","defaultTokenExpiration","SubscribersCollectionFactory","subscribersCollectionFields","getOptInChannelsEndpoint","createEndpointLogout","createEndpointRequestMagicLink","createEndpointSubscribe","createEndpointSubscriberAuth","createEndpointUnsubscribe","createEndpointVerifyMagicLink","getTestEmail","getTokenAndHash","isAbsoluteURL","payloadSubscribersPlugin","pluginOptions","config","serverURL","unsubscribeURL","verifyURL","Error","collections","push","URL","subscribersCollection","subscribersCollectionSlug","find","collection","slug","undefined","filter","fields","auth","tokenExpiration","admin","useAsTitle","collectionSlug","disabled","components","beforeDashboard","endpoints","incomingOnInit","onInit","genInit","testData","payload","totalDocs","totalOptIns","count","where","title","equals","create","data","active","totalSubscribers","email","testEmail","tokenHash","password","status"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { BasePayload, CollectionSlug, Config, Plugin } from 'payload'\n\nimport { OptedInChannels } from './collections/fields/OptedInChannels.js'\nimport OptInChannels from './collections/OptInChannels.js'\nimport {\n defaultTokenExpiration,\n SubscribersCollectionFactory,\n subscribersCollectionFields,\n} from './collections/Subscribers.js'\nimport getOptInChannelsEndpoint from './endpoints/getOptInChannels.js'\nimport createEndpointLogout from './endpoints/logout.js'\nimport createEndpointRequestMagicLink from './endpoints/requestMagicLink.js'\nimport createEndpointSubscribe from './endpoints/subscribe.js'\nimport createEndpointSubscriberAuth from './endpoints/subscriberAuth.js'\nimport createEndpointUnsubscribe from './endpoints/unsubscribe.js'\nimport createEndpointVerifyMagicLink from './endpoints/verifyMagicLink.js'\nimport { getTestEmail } from './helpers/testData.js'\nimport { getTokenAndHash } from './helpers/token.js'\nimport { isAbsoluteURL } from './helpers/utilities.js'\n\nexport type PayloadSubscribersConfig = {\n /**\n * List of collections to add a custom field\n */\n collections?: Partial<Record<CollectionSlug, true>>\n /**\n * Defaults to false-y. When true:\n * - Database schema changes are still made and seeded\n * - APIs return null or undefined success\n * - Admin components are not added\n * - App components return nothing\n */\n disabled?: boolean\n /**\n * The collection to use as the subscribers collection\n * - Optional. If not specified, the plugin will add a 'subscribers' collection.\n * - Sets the collection auth if not already.\n * - Adds (or overrides) fields: email, firstName, status, optIns, verificationToken, verificationTokenExpires.\n */\n subscribersCollectionSlug?: CollectionSlug\n /**\n * Defaults to 30 minutes\n */\n tokenExpiration?: number\n /**\n * The route or full URL for unsubscribe links\n */\n unsubscribeURL?: string\n /**\n * The route or full URL for verify links\n */\n verifyURL?: string\n}\n\n/**\n * Adds the payload-subscribers-plugin to your payload config\n *\n * @param pluginOptions - Plugin options\n * @param pluginOptions.collections - (optional) An array of existing collection slugs to add an optIns relationship field to\n * @param pluginOptions.disabled - (optional) A convenience option to disable the plugin\n * @param pluginOptions.subscribersCollectionSlug - (optional) The slug of an existing collection to use for subscribers. If omitted, the plugin will create the 'subscribers' collection\n * @param pluginOptions.tokenExpiration - (optional) The expiration time for a token, in milliseconds. Defaults to 30 minutes\n * @param pluginOptions.unsubscribeURL - (optional) The route or full URL for unsubscribe links\n * @param pluginOptions.verifyURL - (optional) The route or full URL for verify links\n * @returns Payload config modified to include the plugin\n */\nexport const payloadSubscribersPlugin =\n (pluginOptions: PayloadSubscribersConfig): Plugin =>\n (config: Config): Config => {\n if (!config.serverURL && !(pluginOptions.unsubscribeURL && pluginOptions.verifyURL)) {\n throw new Error(\n 'payloadSubscribersPlugin requires config.serverURL OR valid values for all URL options: unsubscribeURL, verifyURL',\n )\n }\n\n if (!config.collections) {\n config.collections = []\n }\n\n config.collections.push(OptInChannels)\n\n const unsubscribeURL = !pluginOptions.unsubscribeURL\n ? new URL('/unsubscribe', config.serverURL)\n : isAbsoluteURL(pluginOptions.unsubscribeURL)\n ? new URL(pluginOptions.unsubscribeURL)\n : new URL(pluginOptions.unsubscribeURL, config.serverURL)\n\n // Get a URL object from the verifyURL option\n const verifyURL = !pluginOptions.verifyURL\n ? new URL('/verify', config.serverURL)\n : isAbsoluteURL(pluginOptions.verifyURL)\n ? new URL(pluginOptions.verifyURL)\n : new URL(pluginOptions.verifyURL, config.serverURL)\n\n let subscribersCollection = pluginOptions.subscribersCollectionSlug\n ? config.collections.find(\n (collection) => collection.slug == pluginOptions.subscribersCollectionSlug,\n )\n : undefined\n\n if (subscribersCollection) {\n // Configure the input collection to be the subscribers collection\n config.collections = config.collections.filter(\n (collection) => collection.slug != subscribersCollection?.slug,\n )\n subscribersCollection.fields.push(...subscribersCollectionFields)\n if (!subscribersCollection.auth) {\n subscribersCollection = {\n ...subscribersCollection,\n auth: { tokenExpiration: defaultTokenExpiration },\n }\n }\n if (!subscribersCollection.admin?.useAsTitle) {\n if (!subscribersCollection.admin) {\n subscribersCollection.admin = { useAsTitle: 'email' }\n } else {\n // Throw error? Or override?\n subscribersCollection.admin.useAsTitle = 'email'\n }\n }\n config.collections.push(subscribersCollection)\n } else {\n // Configure the default built-in subscribers collection\n subscribersCollection = SubscribersCollectionFactory({\n slug: pluginOptions.subscribersCollectionSlug,\n tokenExpiration: pluginOptions.tokenExpiration,\n })\n config.collections.push(subscribersCollection)\n }\n\n if (pluginOptions.collections) {\n for (const collectionSlug in pluginOptions.collections) {\n const collection = config.collections.find(\n (collection) => collection.slug === collectionSlug,\n )\n\n if (collection) {\n collection.fields.push(OptedInChannels)\n }\n }\n }\n\n /**\n * If the plugin is disabled, we still want to keep added collections/fields so the database schema is consistent which is important for migrations.\n * If your plugin heavily modifies the database schema, you may want to remove this property.\n */\n if (pluginOptions.disabled) {\n return config\n }\n\n if (!config.admin) {\n config.admin = {}\n }\n\n if (!config.admin.components) {\n config.admin.components = {}\n }\n\n if (!config.admin.components.beforeDashboard) {\n config.admin.components.beforeDashboard = []\n }\n\n if (!config.endpoints) {\n config.endpoints = []\n }\n\n config.endpoints.push(\n getOptInChannelsEndpoint,\n createEndpointLogout({\n subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,\n }),\n createEndpointRequestMagicLink({\n subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,\n unsubscribeURL,\n verifyURL,\n }),\n createEndpointSubscribe({\n subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,\n unsubscribeURL,\n verifyURL,\n }),\n createEndpointSubscriberAuth({\n subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,\n }),\n createEndpointUnsubscribe({\n subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,\n }),\n createEndpointVerifyMagicLink({\n subscribersCollectionSlug: subscribersCollection.slug as CollectionSlug,\n }),\n )\n\n const incomingOnInit = config.onInit\n\n const genInit = (testData: { testEmail: string }) => async (payload: BasePayload) => {\n // Ensure we are executing any existing onInit functions before running our own.\n if (incomingOnInit) {\n await incomingOnInit(payload)\n }\n\n // console.log('Object.keys(payload.collections)', Object.keys(payload.collections))\n const { totalDocs: totalOptIns } = await payload.count({\n collection: 'opt-in-channels',\n where: {\n title: {\n equals: 'seeded-by-plugin',\n },\n },\n })\n\n if (totalOptIns === 0) {\n await payload.create({\n collection: 'opt-in-channels',\n data: {\n active: true,\n title: 'seeded-by-plugin',\n },\n })\n }\n\n // const { seededChannel } = await payload.find({\n // collection: 'opt-in-channels',\n // where: {\n // title: {\n // equals: 'seeded-by-plugin',\n // },\n // },\n // })\n\n const { totalDocs: totalSubscribers } = await payload.count({\n collection: subscribersCollection.slug as CollectionSlug,\n where: {\n email: {\n equals: testData.testEmail,\n },\n },\n })\n\n const { tokenHash } = getTokenAndHash() // Unknowable\n // payload.logger.info(`testData.testEmail == '${testData.testEmail}'`)\n if (totalSubscribers === 0) {\n await payload.create({\n collection: subscribersCollection.slug as CollectionSlug,\n data: {\n email: testData.testEmail,\n password: tokenHash,\n status: 'pending',\n },\n })\n }\n }\n\n // console.log(`getTestEmail == '${getTestEmail()}'`)\n config.onInit = genInit({ testEmail: getTestEmail() })\n\n return config\n }\n"],"names":["OptedInChannels","OptInChannels","defaultTokenExpiration","SubscribersCollectionFactory","subscribersCollectionFields","getOptInChannelsEndpoint","createEndpointLogout","createEndpointRequestMagicLink","createEndpointSubscribe","createEndpointSubscriberAuth","createEndpointUnsubscribe","createEndpointVerifyMagicLink","getTestEmail","getTokenAndHash","isAbsoluteURL","payloadSubscribersPlugin","pluginOptions","config","serverURL","unsubscribeURL","verifyURL","Error","collections","push","URL","subscribersCollection","subscribersCollectionSlug","find","collection","slug","undefined","filter","fields","auth","tokenExpiration","admin","useAsTitle","collectionSlug","disabled","components","beforeDashboard","endpoints","incomingOnInit","onInit","genInit","testData","payload","totalDocs","totalOptIns","count","where","title","equals","create","data","active","totalSubscribers","email","testEmail","tokenHash","password","status"],"mappings":"AAEA,SAASA,eAAe,QAAQ,0CAAyC;AACzE,OAAOC,mBAAmB,iCAAgC;AAC1D,SACEC,sBAAsB,EACtBC,4BAA4B,EAC5BC,2BAA2B,QACtB,+BAA8B;AACrC,OAAOC,8BAA8B,kCAAiC;AACtE,OAAOC,0BAA0B,wBAAuB;AACxD,OAAOC,oCAAoC,kCAAiC;AAC5E,OAAOC,6BAA6B,2BAA0B;AAC9D,OAAOC,kCAAkC,gCAA+B;AACxE,OAAOC,+BAA+B,6BAA4B;AAClE,OAAOC,mCAAmC,iCAAgC;AAC1E,SAASC,YAAY,QAAQ,wBAAuB;AACpD,SAASC,eAAe,QAAQ,qBAAoB;AACpD,SAASC,aAAa,QAAQ,yBAAwB;AAoCtD;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,2BACX,CAACC,gBACD,CAACC;QACC,IAAI,CAACA,OAAOC,SAAS,IAAI,CAAEF,CAAAA,cAAcG,cAAc,IAAIH,cAAcI,SAAS,AAAD,GAAI;YACnF,MAAM,IAAIC,MACR;QAEJ;QAEA,IAAI,CAACJ,OAAOK,WAAW,EAAE;YACvBL,OAAOK,WAAW,GAAG,EAAE;QACzB;QAEAL,OAAOK,WAAW,CAACC,IAAI,CAACtB;QAExB,MAAMkB,iBAAiB,CAACH,cAAcG,cAAc,GAChD,IAAIK,IAAI,gBAAgBP,OAAOC,SAAS,IACxCJ,cAAcE,cAAcG,cAAc,IACxC,IAAIK,IAAIR,cAAcG,cAAc,IACpC,IAAIK,IAAIR,cAAcG,cAAc,EAAEF,OAAOC,SAAS;QAE5D,6CAA6C;QAC7C,MAAME,YAAY,CAACJ,cAAcI,SAAS,GACtC,IAAII,IAAI,WAAWP,OAAOC,SAAS,IACnCJ,cAAcE,cAAcI,SAAS,IACnC,IAAII,IAAIR,cAAcI,SAAS,IAC/B,IAAII,IAAIR,cAAcI,SAAS,EAAEH,OAAOC,SAAS;QAEvD,IAAIO,wBAAwBT,cAAcU,yBAAyB,GAC/DT,OAAOK,WAAW,CAACK,IAAI,CACrB,CAACC,aAAeA,WAAWC,IAAI,IAAIb,cAAcU,yBAAyB,IAE5EI;QAEJ,IAAIL,uBAAuB;YACzB,kEAAkE;YAClER,OAAOK,WAAW,GAAGL,OAAOK,WAAW,CAACS,MAAM,CAC5C,CAACH,aAAeA,WAAWC,IAAI,IAAIJ,uBAAuBI;YAE5DJ,sBAAsBO,MAAM,CAACT,IAAI,IAAInB;YACrC,IAAI,CAACqB,sBAAsBQ,IAAI,EAAE;gBAC/BR,wBAAwB;oBACtB,GAAGA,qBAAqB;oBACxBQ,MAAM;wBAAEC,iBAAiBhC;oBAAuB;gBAClD;YACF;YACA,IAAI,CAACuB,sBAAsBU,KAAK,EAAEC,YAAY;gBAC5C,IAAI,CAACX,sBAAsBU,KAAK,EAAE;oBAChCV,sBAAsBU,KAAK,GAAG;wBAAEC,YAAY;oBAAQ;gBACtD,OAAO;oBACL,4BAA4B;oBAC5BX,sBAAsBU,KAAK,CAACC,UAAU,GAAG;gBAC3C;YACF;YACAnB,OAAOK,WAAW,CAACC,IAAI,CAACE;QAC1B,OAAO;YACL,wDAAwD;YACxDA,wBAAwBtB,6BAA6B;gBACnD0B,MAAMb,cAAcU,yBAAyB;gBAC7CQ,iBAAiBlB,cAAckB,eAAe;YAChD;YACAjB,OAAOK,WAAW,CAACC,IAAI,CAACE;QAC1B;QAEA,IAAIT,cAAcM,WAAW,EAAE;YAC7B,IAAK,MAAMe,kBAAkBrB,cAAcM,WAAW,CAAE;gBACtD,MAAMM,aAAaX,OAAOK,WAAW,CAACK,IAAI,CACxC,CAACC,aAAeA,WAAWC,IAAI,KAAKQ;gBAGtC,IAAIT,YAAY;oBACdA,WAAWI,MAAM,CAACT,IAAI,CAACvB;gBACzB;YACF;QACF;QAEA;;;KAGC,GACD,IAAIgB,cAAcsB,QAAQ,EAAE;YAC1B,OAAOrB;QACT;QAEA,IAAI,CAACA,OAAOkB,KAAK,EAAE;YACjBlB,OAAOkB,KAAK,GAAG,CAAC;QAClB;QAEA,IAAI,CAAClB,OAAOkB,KAAK,CAACI,UAAU,EAAE;YAC5BtB,OAAOkB,KAAK,CAACI,UAAU,GAAG,CAAC;QAC7B;QAEA,IAAI,CAACtB,OAAOkB,KAAK,CAACI,UAAU,CAACC,eAAe,EAAE;YAC5CvB,OAAOkB,KAAK,CAACI,UAAU,CAACC,eAAe,GAAG,EAAE;QAC9C;QAEA,IAAI,CAACvB,OAAOwB,SAAS,EAAE;YACrBxB,OAAOwB,SAAS,GAAG,EAAE;QACvB;QAEAxB,OAAOwB,SAAS,CAAClB,IAAI,CACnBlB,0BACAC,qBAAqB;YACnBoB,2BAA2BD,sBAAsBI,IAAI;QACvD,IACAtB,+BAA+B;YAC7BmB,2BAA2BD,sBAAsBI,IAAI;YACrDV;YACAC;QACF,IACAZ,wBAAwB;YACtBkB,2BAA2BD,sBAAsBI,IAAI;YACrDV;YACAC;QACF,IACAX,6BAA6B;YAC3BiB,2BAA2BD,sBAAsBI,IAAI;QACvD,IACAnB,0BAA0B;YACxBgB,2BAA2BD,sBAAsBI,IAAI;QACvD,IACAlB,8BAA8B;YAC5Be,2BAA2BD,sBAAsBI,IAAI;QACvD;QAGF,MAAMa,iBAAiBzB,OAAO0B,MAAM;QAEpC,MAAMC,UAAU,CAACC,WAAoC,OAAOC;gBAC1D,gFAAgF;gBAChF,IAAIJ,gBAAgB;oBAClB,MAAMA,eAAeI;gBACvB;gBAEA,oFAAoF;gBACpF,MAAM,EAAEC,WAAWC,WAAW,EAAE,GAAG,MAAMF,QAAQG,KAAK,CAAC;oBACrDrB,YAAY;oBACZsB,OAAO;wBACLC,OAAO;4BACLC,QAAQ;wBACV;oBACF;gBACF;gBAEA,IAAIJ,gBAAgB,GAAG;oBACrB,MAAMF,QAAQO,MAAM,CAAC;wBACnBzB,YAAY;wBACZ0B,MAAM;4BACJC,QAAQ;4BACRJ,OAAO;wBACT;oBACF;gBACF;gBAEA,iDAAiD;gBACjD,mCAAmC;gBACnC,aAAa;gBACb,eAAe;gBACf,oCAAoC;gBACpC,SAAS;gBACT,OAAO;gBACP,KAAK;gBAEL,MAAM,EAAEJ,WAAWS,gBAAgB,EAAE,GAAG,MAAMV,QAAQG,KAAK,CAAC;oBAC1DrB,YAAYH,sBAAsBI,IAAI;oBACtCqB,OAAO;wBACLO,OAAO;4BACLL,QAAQP,SAASa,SAAS;wBAC5B;oBACF;gBACF;gBAEA,MAAM,EAAEC,SAAS,EAAE,GAAG9C,kBAAkB,aAAa;;gBACrD,uEAAuE;gBACvE,IAAI2C,qBAAqB,GAAG;oBAC1B,MAAMV,QAAQO,MAAM,CAAC;wBACnBzB,YAAYH,sBAAsBI,IAAI;wBACtCyB,MAAM;4BACJG,OAAOZ,SAASa,SAAS;4BACzBE,UAAUD;4BACVE,QAAQ;wBACV;oBACF;gBACF;YACF;QAEA,qDAAqD;QACrD5C,OAAO0B,MAAM,GAAGC,QAAQ;YAAEc,WAAW9C;QAAe;QAEpD,OAAOK;IACT,EAAC"}
package/package.json CHANGED
@@ -69,7 +69,7 @@
69
69
  },
70
70
  "registry": "https://registry.npmjs.org/",
71
71
  "dependencies": {},
72
- "version": "0.0.13",
72
+ "version": "0.0.15",
73
73
  "scripts": {
74
74
  "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
75
75
  "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",