@ainsleydev/payload-helper 0.0.40 → 0.1.0
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 +76 -0
- package/dist/cli/bin.js +20 -0
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/preview-emails.d.ts +5 -0
- package/dist/cli/commands/preview-emails.js +123 -0
- package/dist/cli/commands/preview-emails.js.map +1 -0
- package/dist/email/ForgotPasswordEmail.d.ts +38 -0
- package/dist/email/ForgotPasswordEmail.js +61 -0
- package/dist/email/ForgotPasswordEmail.js.map +1 -0
- package/dist/email/ForgotPasswordEmail.test.d.ts +1 -0
- package/dist/email/ForgotPasswordEmail.test.js +202 -0
- package/dist/email/ForgotPasswordEmail.test.js.map +1 -0
- package/dist/email/VerifyAccountEmail.d.ts +38 -0
- package/dist/email/VerifyAccountEmail.js +61 -0
- package/dist/email/VerifyAccountEmail.js.map +1 -0
- package/dist/email/VerifyAccountEmail.test.d.ts +1 -0
- package/dist/email/VerifyAccountEmail.test.js +212 -0
- package/dist/email/VerifyAccountEmail.test.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin/email.d.ts +10 -0
- package/dist/plugin/email.js +98 -0
- package/dist/plugin/email.js.map +1 -0
- package/dist/plugin/email.test.js +265 -0
- package/dist/plugin/email.test.js.map +1 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.js +3 -1
- package/dist/types.js.map +1 -1
- package/package.json +27 -14
- package/dist/plugin/icon.d.ts +0 -6
- package/dist/plugin/icon.js +0 -26
- package/dist/plugin/icon.js.map +0 -1
- package/dist/plugin/logo.d.ts +0 -6
- package/dist/plugin/logo.js +0 -26
- package/dist/plugin/logo.js.map +0 -1
package/README.md
CHANGED
|
@@ -91,6 +91,82 @@ payloadHelper({
|
|
|
91
91
|
})
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
### Email configuration
|
|
95
|
+
|
|
96
|
+
Configure branded email templates for Payload authentication flows. Automatically applies to all collections with `auth` enabled.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
payloadHelper({
|
|
100
|
+
siteName: 'My Site',
|
|
101
|
+
email: {
|
|
102
|
+
frontEndUrl: 'https://your-site.com', // Optional, defaults to Payload's serverURL
|
|
103
|
+
theme: {
|
|
104
|
+
branding: {
|
|
105
|
+
companyName: 'My Company',
|
|
106
|
+
logoUrl: 'https://your-site.com/logo.png',
|
|
107
|
+
},
|
|
108
|
+
colours: {
|
|
109
|
+
background: {
|
|
110
|
+
accent: '#ff5043',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
forgotPassword: {
|
|
115
|
+
heading: 'Reset your password',
|
|
116
|
+
bodyText: 'Click the button below to reset your password.',
|
|
117
|
+
buttonText: 'Reset Password',
|
|
118
|
+
},
|
|
119
|
+
verifyAccount: {
|
|
120
|
+
heading: 'Welcome aboard',
|
|
121
|
+
bodyText: 'Please verify your email address.',
|
|
122
|
+
buttonText: 'Verify Email',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Previewing emails
|
|
129
|
+
|
|
130
|
+
Preview your emails with your actual branding directly from your Payload configuration:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npx payload-helper preview-emails
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This command will:
|
|
137
|
+
- Read your `payload.config.ts` to extract your email theme configuration
|
|
138
|
+
- Generate preview templates with your actual branding
|
|
139
|
+
- Launch a preview server at http://localhost:3000
|
|
140
|
+
|
|
141
|
+
You can optionally specify a custom port:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npx payload-helper preview-emails --port 3001
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The preview will show both ForgotPassword and VerifyAccount emails using your configured theme, frontEndUrl, and content overrides.
|
|
148
|
+
|
|
149
|
+
## Utilities
|
|
150
|
+
|
|
151
|
+
### Environment Variables
|
|
152
|
+
|
|
153
|
+
The package exports an `env` utility object that provides access to environment variables required by Payload Helper.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { env } from '@ainsleydev/payload-helper'
|
|
157
|
+
|
|
158
|
+
// Access environment variables
|
|
159
|
+
const spaces = {
|
|
160
|
+
key: env.DO_SPACES_KEY,
|
|
161
|
+
secret: env.DO_SPACES_SECRET,
|
|
162
|
+
endpoint: env.DO_SPACES_ENDPOINT,
|
|
163
|
+
region: env.DO_SPACES_REGION,
|
|
164
|
+
bucket: env.DO_SPACES_BUCKET,
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Note:** When using ESM modules, always import utilities from the main package entry point as shown above. Direct imports to subpaths (e.g., `@ainsleydev/payload-helper/dist/util/env`) are not supported.
|
|
169
|
+
|
|
94
170
|
## Open Source
|
|
95
171
|
|
|
96
172
|
ainsley.dev permits the use of any HTML, SCSS and Javascript found within the repository for use
|
package/dist/cli/bin.js
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
2
4
|
import { Command } from 'commander';
|
|
5
|
+
import { getPayload } from 'payload';
|
|
6
|
+
import { previewEmails } from './commands/preview-emails.js';
|
|
3
7
|
const program = new Command();
|
|
8
|
+
const filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const dirname = path.dirname(filename);
|
|
4
10
|
program.command('generate-types').description('Generate JSON schema types for Payload CMS');
|
|
11
|
+
program.command('preview-emails').description('Preview email templates with your Payload configuration').option('-p, --port <number>', 'Port to run preview server on', '3000').action(async (options)=>{
|
|
12
|
+
const payload = await getPayloadInstance();
|
|
13
|
+
await previewEmails({
|
|
14
|
+
payload,
|
|
15
|
+
port: Number.parseInt(options.port, 10)
|
|
16
|
+
});
|
|
17
|
+
});
|
|
5
18
|
export const bin = async ()=>{
|
|
6
19
|
await program.parseAsync(process.argv);
|
|
7
20
|
};
|
|
21
|
+
const getPayloadInstance = async ()=>{
|
|
22
|
+
const configPath = path.join(process.cwd(), 'src/payload.config.ts');
|
|
23
|
+
const config = (await import(configPath)).default;
|
|
24
|
+
return await getPayload({
|
|
25
|
+
config
|
|
26
|
+
});
|
|
27
|
+
};
|
|
8
28
|
|
|
9
29
|
//# sourceMappingURL=bin.js.map
|
package/dist/cli/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/bin.ts"],"sourcesContent":["#!/usr/bin/env node\nimport
|
|
1
|
+
{"version":3,"sources":["../../src/cli/bin.ts"],"sourcesContent":["#!/usr/bin/env node\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Command } from 'commander';\nimport { type Payload, getPayload } from 'payload';\nimport { previewEmails } from './commands/preview-emails.js';\n\nconst program = new Command();\nconst filename = fileURLToPath(import.meta.url);\nconst dirname = path.dirname(filename);\n\nprogram.command('generate-types').description('Generate JSON schema types for Payload CMS');\nprogram\n\t.command('preview-emails')\n\t.description('Preview email templates with your Payload configuration')\n\t.option('-p, --port <number>', 'Port to run preview server on', '3000')\n\t.action(async (options) => {\n\t\tconst payload = await getPayloadInstance();\n\n\t\tawait previewEmails({\n\t\t\tpayload,\n\t\t\tport: Number.parseInt(options.port, 10),\n\t\t});\n\t});\n\nexport const bin = async () => {\n\tawait program.parseAsync(process.argv);\n};\n\nconst getPayloadInstance = async (): Promise<Payload> => {\n\tconst configPath = path.join(process.cwd(), 'src/payload.config.ts');\n\tconst config = (await import(configPath)).default;\n\n\treturn await getPayload({ config });\n};\n"],"names":["path","fileURLToPath","Command","getPayload","previewEmails","program","filename","url","dirname","command","description","option","action","options","payload","getPayloadInstance","port","Number","parseInt","bin","parseAsync","process","argv","configPath","join","cwd","config","default"],"mappings":";AAEA,OAAOA,UAAU,YAAY;AAC7B,SAASC,aAAa,QAAQ,WAAW;AACzC,SAASC,OAAO,QAAQ,YAAY;AACpC,SAAuBC,UAAU,QAAQ,UAAU;AACnD,SAASC,aAAa,QAAQ,+BAA+B;AAE7D,MAAMC,UAAU,IAAIH;AACpB,MAAMI,WAAWL,cAAc,YAAYM,GAAG;AAC9C,MAAMC,UAAUR,KAAKQ,OAAO,CAACF;AAE7BD,QAAQI,OAAO,CAAC,kBAAkBC,WAAW,CAAC;AAC9CL,QACEI,OAAO,CAAC,kBACRC,WAAW,CAAC,2DACZC,MAAM,CAAC,uBAAuB,iCAAiC,QAC/DC,MAAM,CAAC,OAAOC;IACd,MAAMC,UAAU,MAAMC;IAEtB,MAAMX,cAAc;QACnBU;QACAE,MAAMC,OAAOC,QAAQ,CAACL,QAAQG,IAAI,EAAE;IACrC;AACD;AAED,OAAO,MAAMG,MAAM;IAClB,MAAMd,QAAQe,UAAU,CAACC,QAAQC,IAAI;AACtC,EAAE;AAEF,MAAMP,qBAAqB;IAC1B,MAAMQ,aAAavB,KAAKwB,IAAI,CAACH,QAAQI,GAAG,IAAI;IAC5C,MAAMC,SAAS,AAAC,CAAA,MAAM,MAAM,CAACH,WAAU,EAAGI,OAAO;IAEjD,OAAO,MAAMxB,WAAW;QAAEuB;IAAO;AAClC"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
/**
|
|
7
|
+
* Escapes a string for safe use in template literals.
|
|
8
|
+
*/ const escapeForTemplate = (str)=>{
|
|
9
|
+
return str.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Retrieves the email configuration from the Payload config.
|
|
13
|
+
*/ const getEmailConfig = (payload)=>{
|
|
14
|
+
const custom = payload.config.custom;
|
|
15
|
+
const payloadHelperOptions = custom?.payloadHelperOptions;
|
|
16
|
+
return payloadHelperOptions?.email;
|
|
17
|
+
};
|
|
18
|
+
export const previewEmails = async (options)=>{
|
|
19
|
+
const port = options.port || 3000;
|
|
20
|
+
const payload = options.payload;
|
|
21
|
+
console.log(chalk.blue('🔍 Looking for email configuration...'));
|
|
22
|
+
// Get email config from stored plugin options
|
|
23
|
+
const emailConfig = getEmailConfig(payload);
|
|
24
|
+
if (!emailConfig) {
|
|
25
|
+
console.log(chalk.yellow('⚠️ No email configuration found'));
|
|
26
|
+
console.log(chalk.yellow(' Make sure you have configured email in your payloadHelper plugin:\n'));
|
|
27
|
+
console.log(chalk.cyan(` payloadHelper({
|
|
28
|
+
email: {
|
|
29
|
+
theme: { /* ... */ },
|
|
30
|
+
frontEndUrl: 'https://yoursite.com',
|
|
31
|
+
}
|
|
32
|
+
})`));
|
|
33
|
+
console.log(chalk.yellow('\n Using default theme for email previews'));
|
|
34
|
+
} else {
|
|
35
|
+
console.log(chalk.green('✓ Found email configuration'));
|
|
36
|
+
}
|
|
37
|
+
// Create temp directory for preview files
|
|
38
|
+
const tempDir = join(tmpdir(), `payload-helper-preview-${Date.now()}-${process.pid}`);
|
|
39
|
+
mkdirSync(tempDir, {
|
|
40
|
+
recursive: true
|
|
41
|
+
});
|
|
42
|
+
console.log(chalk.blue('📝 Generating preview templates...'));
|
|
43
|
+
// Extract theme and frontEndUrl
|
|
44
|
+
const themeConfig = emailConfig?.theme ? JSON.stringify(emailConfig.theme, null, 2) : '{}';
|
|
45
|
+
const frontEndUrl = emailConfig?.frontEndUrl || 'https://yoursite.com';
|
|
46
|
+
// Safely escape the frontEndUrl for use in template strings
|
|
47
|
+
const escapedFrontEndUrl = escapeForTemplate(frontEndUrl);
|
|
48
|
+
// Generate ForgotPassword preview
|
|
49
|
+
const forgotPasswordContent = emailConfig?.forgotPassword ? JSON.stringify(emailConfig.forgotPassword, null, 3) : 'undefined';
|
|
50
|
+
const forgotPasswordPreview = `import { renderEmail } from '@ainsleydev/email-templates';
|
|
51
|
+
import { ForgotPasswordEmail } from '@ainsleydev/payload-helper';
|
|
52
|
+
|
|
53
|
+
export default async function render() {
|
|
54
|
+
return renderEmail({
|
|
55
|
+
component: ForgotPasswordEmail,
|
|
56
|
+
props: {
|
|
57
|
+
user: { firstName: 'John', email: 'john@example.com' },
|
|
58
|
+
resetUrl: \`${escapedFrontEndUrl}/admin/reset/token123\`,
|
|
59
|
+
content: ${forgotPasswordContent},
|
|
60
|
+
},
|
|
61
|
+
theme: ${themeConfig},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
// Generate VerifyAccount preview
|
|
66
|
+
const verifyAccountContent = emailConfig?.verifyAccount ? JSON.stringify(emailConfig.verifyAccount, null, 3) : 'undefined';
|
|
67
|
+
const verifyAccountPreview = `import { renderEmail } from '@ainsleydev/email-templates';
|
|
68
|
+
import { VerifyAccountEmail } from '@ainsleydev/payload-helper';
|
|
69
|
+
|
|
70
|
+
export default async function render() {
|
|
71
|
+
return renderEmail({
|
|
72
|
+
component: VerifyAccountEmail,
|
|
73
|
+
props: {
|
|
74
|
+
user: { firstName: 'John', email: 'john@example.com' },
|
|
75
|
+
verifyUrl: \`${escapedFrontEndUrl}/admin/verify/token123\`,
|
|
76
|
+
content: ${verifyAccountContent},
|
|
77
|
+
},
|
|
78
|
+
theme: ${themeConfig},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
`;
|
|
82
|
+
// Write preview files
|
|
83
|
+
writeFileSync(join(tempDir, 'forgot-password-email-preview.tsx'), forgotPasswordPreview);
|
|
84
|
+
writeFileSync(join(tempDir, 'verify-account-email-preview.tsx'), verifyAccountPreview);
|
|
85
|
+
console.log(chalk.green('✓ Preview templates generated'));
|
|
86
|
+
console.log(chalk.blue(`🚀 Starting preview server on http://localhost:${port}...`));
|
|
87
|
+
// Launch email-templates preview
|
|
88
|
+
const previewProcess = spawn('npx', [
|
|
89
|
+
'email-templates',
|
|
90
|
+
'preview',
|
|
91
|
+
tempDir,
|
|
92
|
+
`--port=${port}`
|
|
93
|
+
], {
|
|
94
|
+
stdio: 'inherit',
|
|
95
|
+
shell: true
|
|
96
|
+
});
|
|
97
|
+
// Cleanup on exit
|
|
98
|
+
const cleanup = ()=>{
|
|
99
|
+
console.log(chalk.blue('\n🧹 Cleaning up...'));
|
|
100
|
+
try {
|
|
101
|
+
rmSync(tempDir, {
|
|
102
|
+
recursive: true,
|
|
103
|
+
force: true
|
|
104
|
+
});
|
|
105
|
+
console.log(chalk.green('✓ Cleanup complete'));
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(chalk.red('❌ Error during cleanup:'), error);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
previewProcess.on('exit', (code)=>{
|
|
111
|
+
cleanup();
|
|
112
|
+
process.exit(code || 0);
|
|
113
|
+
});
|
|
114
|
+
// Handle Ctrl+C
|
|
115
|
+
process.on('SIGINT', ()=>{
|
|
116
|
+
previewProcess.kill('SIGINT');
|
|
117
|
+
});
|
|
118
|
+
process.on('SIGTERM', ()=>{
|
|
119
|
+
previewProcess.kill('SIGTERM');
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=preview-emails.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/commands/preview-emails.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { mkdirSync, rmSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport chalk from 'chalk';\nimport type { Payload } from 'payload';\nimport type { PayloadHelperPluginConfig } from '../../types.js';\n\n/**\n * Escapes a string for safe use in template literals.\n */\nconst escapeForTemplate = (str: string): string => {\n\treturn str.replace(/\\\\/g, '\\\\\\\\').replace(/`/g, '\\\\`').replace(/\\$/g, '\\\\$');\n};\n\n/**\n * Retrieves the email configuration from the Payload config.\n */\nconst getEmailConfig = (payload: Payload): PayloadHelperPluginConfig['email'] | undefined => {\n\tconst custom = payload.config.custom as Record<string, unknown> | undefined;\n\tconst payloadHelperOptions = custom?.payloadHelperOptions as\n\t\t| PayloadHelperPluginConfig\n\t\t| undefined;\n\treturn payloadHelperOptions?.email;\n};\n\nexport const previewEmails = async (options: { payload: Payload; port?: number }) => {\n\tconst port = options.port || 3000;\n\tconst payload = options.payload;\n\n\tconsole.log(chalk.blue('🔍 Looking for email configuration...'));\n\n\t// Get email config from stored plugin options\n\tconst emailConfig = getEmailConfig(payload);\n\n\tif (!emailConfig) {\n\t\tconsole.log(chalk.yellow('⚠️ No email configuration found'));\n\t\tconsole.log(\n\t\t\tchalk.yellow(' Make sure you have configured email in your payloadHelper plugin:\\n'),\n\t\t);\n\t\tconsole.log(\n\t\t\tchalk.cyan(` payloadHelper({\n email: {\n theme: { /* ... */ },\n frontEndUrl: 'https://yoursite.com',\n }\n })`),\n\t\t);\n\t\tconsole.log(chalk.yellow('\\n Using default theme for email previews'));\n\t} else {\n\t\tconsole.log(chalk.green('✓ Found email configuration'));\n\t}\n\n\t// Create temp directory for preview files\n\tconst tempDir = join(tmpdir(), `payload-helper-preview-${Date.now()}-${process.pid}`);\n\tmkdirSync(tempDir, { recursive: true });\n\n\tconsole.log(chalk.blue('📝 Generating preview templates...'));\n\n\t// Extract theme and frontEndUrl\n\tconst themeConfig = emailConfig?.theme ? JSON.stringify(emailConfig.theme, null, 2) : '{}';\n\tconst frontEndUrl = emailConfig?.frontEndUrl || 'https://yoursite.com';\n\n\t// Safely escape the frontEndUrl for use in template strings\n\tconst escapedFrontEndUrl = escapeForTemplate(frontEndUrl);\n\n\t// Generate ForgotPassword preview\n\tconst forgotPasswordContent = emailConfig?.forgotPassword\n\t\t? JSON.stringify(emailConfig.forgotPassword, null, 3)\n\t\t: 'undefined';\n\n\tconst forgotPasswordPreview = `import { renderEmail } from '@ainsleydev/email-templates';\nimport { ForgotPasswordEmail } from '@ainsleydev/payload-helper';\n\nexport default async function render() {\n\treturn renderEmail({\n\t\tcomponent: ForgotPasswordEmail,\n\t\tprops: {\n\t\t\tuser: { firstName: 'John', email: 'john@example.com' },\n\t\t\tresetUrl: \\`${escapedFrontEndUrl}/admin/reset/token123\\`,\n\t\t\tcontent: ${forgotPasswordContent},\n\t\t},\n\t\ttheme: ${themeConfig},\n\t});\n}\n`;\n\n\t// Generate VerifyAccount preview\n\tconst verifyAccountContent = emailConfig?.verifyAccount\n\t\t? JSON.stringify(emailConfig.verifyAccount, null, 3)\n\t\t: 'undefined';\n\n\tconst verifyAccountPreview = `import { renderEmail } from '@ainsleydev/email-templates';\nimport { VerifyAccountEmail } from '@ainsleydev/payload-helper';\n\nexport default async function render() {\n\treturn renderEmail({\n\t\tcomponent: VerifyAccountEmail,\n\t\tprops: {\n\t\t\tuser: { firstName: 'John', email: 'john@example.com' },\n\t\t\tverifyUrl: \\`${escapedFrontEndUrl}/admin/verify/token123\\`,\n\t\t\tcontent: ${verifyAccountContent},\n\t\t},\n\t\ttheme: ${themeConfig},\n\t});\n}\n`;\n\n\t// Write preview files\n\twriteFileSync(join(tempDir, 'forgot-password-email-preview.tsx'), forgotPasswordPreview);\n\twriteFileSync(join(tempDir, 'verify-account-email-preview.tsx'), verifyAccountPreview);\n\n\tconsole.log(chalk.green('✓ Preview templates generated'));\n\tconsole.log(chalk.blue(`🚀 Starting preview server on http://localhost:${port}...`));\n\n\t// Launch email-templates preview\n\tconst previewProcess = spawn('npx', ['email-templates', 'preview', tempDir, `--port=${port}`], {\n\t\tstdio: 'inherit',\n\t\tshell: true,\n\t});\n\n\t// Cleanup on exit\n\tconst cleanup = () => {\n\t\tconsole.log(chalk.blue('\\n🧹 Cleaning up...'));\n\t\ttry {\n\t\t\trmSync(tempDir, { recursive: true, force: true });\n\t\t\tconsole.log(chalk.green('✓ Cleanup complete'));\n\t\t} catch (error) {\n\t\t\tconsole.error(chalk.red('❌ Error during cleanup:'), error);\n\t\t}\n\t};\n\n\tpreviewProcess.on('exit', (code) => {\n\t\tcleanup();\n\t\tprocess.exit(code || 0);\n\t});\n\n\t// Handle Ctrl+C\n\tprocess.on('SIGINT', () => {\n\t\tpreviewProcess.kill('SIGINT');\n\t});\n\n\tprocess.on('SIGTERM', () => {\n\t\tpreviewProcess.kill('SIGTERM');\n\t});\n};\n"],"names":["spawn","mkdirSync","rmSync","writeFileSync","tmpdir","join","chalk","escapeForTemplate","str","replace","getEmailConfig","payload","custom","config","payloadHelperOptions","email","previewEmails","options","port","console","log","blue","emailConfig","yellow","cyan","green","tempDir","Date","now","process","pid","recursive","themeConfig","theme","JSON","stringify","frontEndUrl","escapedFrontEndUrl","forgotPasswordContent","forgotPassword","forgotPasswordPreview","verifyAccountContent","verifyAccount","verifyAccountPreview","previewProcess","stdio","shell","cleanup","force","error","red","on","code","exit","kill"],"mappings":"AAAA,SAASA,KAAK,QAAQ,qBAAqB;AAC3C,SAASC,SAAS,EAAEC,MAAM,EAAEC,aAAa,QAAQ,UAAU;AAC3D,SAASC,MAAM,QAAQ,UAAU;AACjC,SAASC,IAAI,QAAQ,YAAY;AACjC,OAAOC,WAAW,QAAQ;AAI1B;;CAEC,GACD,MAAMC,oBAAoB,CAACC;IAC1B,OAAOA,IAAIC,OAAO,CAAC,OAAO,QAAQA,OAAO,CAAC,MAAM,OAAOA,OAAO,CAAC,OAAO;AACvE;AAEA;;CAEC,GACD,MAAMC,iBAAiB,CAACC;IACvB,MAAMC,SAASD,QAAQE,MAAM,CAACD,MAAM;IACpC,MAAME,uBAAuBF,QAAQE;IAGrC,OAAOA,sBAAsBC;AAC9B;AAEA,OAAO,MAAMC,gBAAgB,OAAOC;IACnC,MAAMC,OAAOD,QAAQC,IAAI,IAAI;IAC7B,MAAMP,UAAUM,QAAQN,OAAO;IAE/BQ,QAAQC,GAAG,CAACd,MAAMe,IAAI,CAAC;IAEvB,8CAA8C;IAC9C,MAAMC,cAAcZ,eAAeC;IAEnC,IAAI,CAACW,aAAa;QACjBH,QAAQC,GAAG,CAACd,MAAMiB,MAAM,CAAC;QACzBJ,QAAQC,GAAG,CACVd,MAAMiB,MAAM,CAAC;QAEdJ,QAAQC,GAAG,CACVd,MAAMkB,IAAI,CAAC,CAAC;;;;;KAKV,CAAC;QAEJL,QAAQC,GAAG,CAACd,MAAMiB,MAAM,CAAC;IAC1B,OAAO;QACNJ,QAAQC,GAAG,CAACd,MAAMmB,KAAK,CAAC;IACzB;IAEA,0CAA0C;IAC1C,MAAMC,UAAUrB,KAAKD,UAAU,CAAC,uBAAuB,EAAEuB,KAAKC,GAAG,GAAG,CAAC,EAAEC,QAAQC,GAAG,EAAE;IACpF7B,UAAUyB,SAAS;QAAEK,WAAW;IAAK;IAErCZ,QAAQC,GAAG,CAACd,MAAMe,IAAI,CAAC;IAEvB,gCAAgC;IAChC,MAAMW,cAAcV,aAAaW,QAAQC,KAAKC,SAAS,CAACb,YAAYW,KAAK,EAAE,MAAM,KAAK;IACtF,MAAMG,cAAcd,aAAac,eAAe;IAEhD,4DAA4D;IAC5D,MAAMC,qBAAqB9B,kBAAkB6B;IAE7C,kCAAkC;IAClC,MAAME,wBAAwBhB,aAAaiB,iBACxCL,KAAKC,SAAS,CAACb,YAAYiB,cAAc,EAAE,MAAM,KACjD;IAEH,MAAMC,wBAAwB,CAAC;;;;;;;;eAQjB,EAAEH,mBAAmB;YACxB,EAAEC,sBAAsB;;SAE3B,EAAEN,YAAY;;;AAGvB,CAAC;IAEA,iCAAiC;IACjC,MAAMS,uBAAuBnB,aAAaoB,gBACvCR,KAAKC,SAAS,CAACb,YAAYoB,aAAa,EAAE,MAAM,KAChD;IAEH,MAAMC,uBAAuB,CAAC;;;;;;;;gBAQf,EAAEN,mBAAmB;YACzB,EAAEI,qBAAqB;;SAE1B,EAAET,YAAY;;;AAGvB,CAAC;IAEA,sBAAsB;IACtB7B,cAAcE,KAAKqB,SAAS,sCAAsCc;IAClErC,cAAcE,KAAKqB,SAAS,qCAAqCiB;IAEjExB,QAAQC,GAAG,CAACd,MAAMmB,KAAK,CAAC;IACxBN,QAAQC,GAAG,CAACd,MAAMe,IAAI,CAAC,CAAC,+CAA+C,EAAEH,KAAK,GAAG,CAAC;IAElF,iCAAiC;IACjC,MAAM0B,iBAAiB5C,MAAM,OAAO;QAAC;QAAmB;QAAW0B;QAAS,CAAC,OAAO,EAAER,MAAM;KAAC,EAAE;QAC9F2B,OAAO;QACPC,OAAO;IACR;IAEA,kBAAkB;IAClB,MAAMC,UAAU;QACf5B,QAAQC,GAAG,CAACd,MAAMe,IAAI,CAAC;QACvB,IAAI;YACHnB,OAAOwB,SAAS;gBAAEK,WAAW;gBAAMiB,OAAO;YAAK;YAC/C7B,QAAQC,GAAG,CAACd,MAAMmB,KAAK,CAAC;QACzB,EAAE,OAAOwB,OAAO;YACf9B,QAAQ8B,KAAK,CAAC3C,MAAM4C,GAAG,CAAC,4BAA4BD;QACrD;IACD;IAEAL,eAAeO,EAAE,CAAC,QAAQ,CAACC;QAC1BL;QACAlB,QAAQwB,IAAI,CAACD,QAAQ;IACtB;IAEA,gBAAgB;IAChBvB,QAAQsB,EAAE,CAAC,UAAU;QACpBP,eAAeU,IAAI,CAAC;IACrB;IAEAzB,QAAQsB,EAAE,CAAC,WAAW;QACrBP,eAAeU,IAAI,CAAC;IACrB;AACD,EAAE"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { EmailTheme } from '@ainsleydev/email-templates';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Props for the ForgotPasswordEmail component.
|
|
5
|
+
*/
|
|
6
|
+
export interface ForgotPasswordEmailProps {
|
|
7
|
+
/**
|
|
8
|
+
* The email theme (required by renderEmail).
|
|
9
|
+
*/
|
|
10
|
+
theme: EmailTheme;
|
|
11
|
+
/**
|
|
12
|
+
* The user object containing user information.
|
|
13
|
+
*/
|
|
14
|
+
user: {
|
|
15
|
+
firstName?: string;
|
|
16
|
+
email?: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The URL for resetting the password.
|
|
20
|
+
*/
|
|
21
|
+
resetUrl: string;
|
|
22
|
+
/**
|
|
23
|
+
* Optional content overrides.
|
|
24
|
+
*/
|
|
25
|
+
content?: {
|
|
26
|
+
previewText?: string;
|
|
27
|
+
heading?: string;
|
|
28
|
+
bodyText?: string;
|
|
29
|
+
buttonText?: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Email template for password reset requests in Payload CMS.
|
|
34
|
+
*
|
|
35
|
+
* @param props - The component props
|
|
36
|
+
* @returns The rendered email component
|
|
37
|
+
*/
|
|
38
|
+
export declare const ForgotPasswordEmail: ({ theme, user, resetUrl, content, }: ForgotPasswordEmailProps) => React.JSX.Element;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { BaseEmail, Button, Heading, Section, Text } from '@ainsleydev/email-templates';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Email template for password reset requests in Payload CMS.
|
|
6
|
+
*
|
|
7
|
+
* @param props - The component props
|
|
8
|
+
* @returns The rendered email component
|
|
9
|
+
*/ export const ForgotPasswordEmail = ({ theme, user, resetUrl, content })=>{
|
|
10
|
+
const userName = user.firstName || user.email || 'there';
|
|
11
|
+
const previewText = content?.previewText || 'Reset your password';
|
|
12
|
+
const heading = content?.heading || `Hello, ${userName}!`;
|
|
13
|
+
const bodyText = content?.bodyText || 'We received a request to reset your password, please click the button below. If you did not request a password reset, you can safely ignore this email.';
|
|
14
|
+
const buttonText = content?.buttonText || 'Reset Password';
|
|
15
|
+
return /*#__PURE__*/ _jsxs(BaseEmail, {
|
|
16
|
+
theme: theme,
|
|
17
|
+
previewText: previewText,
|
|
18
|
+
children: [
|
|
19
|
+
/*#__PURE__*/ _jsx(Heading, {
|
|
20
|
+
style: {
|
|
21
|
+
color: theme.colours.text.heading,
|
|
22
|
+
fontSize: '24px',
|
|
23
|
+
fontWeight: 'bold',
|
|
24
|
+
marginBottom: '20px'
|
|
25
|
+
},
|
|
26
|
+
children: heading
|
|
27
|
+
}),
|
|
28
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
29
|
+
style: {
|
|
30
|
+
color: theme.colours.text.body,
|
|
31
|
+
fontSize: '16px',
|
|
32
|
+
lineHeight: '24px',
|
|
33
|
+
marginBottom: '30px'
|
|
34
|
+
},
|
|
35
|
+
children: bodyText
|
|
36
|
+
}),
|
|
37
|
+
/*#__PURE__*/ _jsx(Section, {
|
|
38
|
+
style: {
|
|
39
|
+
textAlign: 'center',
|
|
40
|
+
marginBottom: '30px'
|
|
41
|
+
},
|
|
42
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
43
|
+
href: resetUrl,
|
|
44
|
+
style: {
|
|
45
|
+
backgroundColor: theme.colours.background.accent,
|
|
46
|
+
color: theme.colours.text.heading,
|
|
47
|
+
padding: '12px 32px',
|
|
48
|
+
borderRadius: '5px',
|
|
49
|
+
fontSize: '16px',
|
|
50
|
+
fontWeight: 'bold',
|
|
51
|
+
textDecoration: 'none',
|
|
52
|
+
display: 'inline-block'
|
|
53
|
+
},
|
|
54
|
+
children: buttonText
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=ForgotPasswordEmail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/email/ForgotPasswordEmail.tsx"],"sourcesContent":["import { BaseEmail, Button, Heading, Section, Text } from '@ainsleydev/email-templates';\nimport type { EmailTheme } from '@ainsleydev/email-templates';\nimport * as React from 'react';\n\n/**\n * Props for the ForgotPasswordEmail component.\n */\nexport interface ForgotPasswordEmailProps {\n\t/**\n\t * The email theme (required by renderEmail).\n\t */\n\ttheme: EmailTheme;\n\n\t/**\n\t * The user object containing user information.\n\t */\n\tuser: {\n\t\tfirstName?: string;\n\t\temail?: string;\n\t};\n\n\t/**\n\t * The URL for resetting the password.\n\t */\n\tresetUrl: string;\n\n\t/**\n\t * Optional content overrides.\n\t */\n\tcontent?: {\n\t\tpreviewText?: string;\n\t\theading?: string;\n\t\tbodyText?: string;\n\t\tbuttonText?: string;\n\t};\n}\n\n/**\n * Email template for password reset requests in Payload CMS.\n *\n * @param props - The component props\n * @returns The rendered email component\n */\nexport const ForgotPasswordEmail = ({\n\ttheme,\n\tuser,\n\tresetUrl,\n\tcontent,\n}: ForgotPasswordEmailProps) => {\n\tconst userName = user.firstName || user.email || 'there';\n\tconst previewText = content?.previewText || 'Reset your password';\n\tconst heading = content?.heading || `Hello, ${userName}!`;\n\tconst bodyText =\n\t\tcontent?.bodyText ||\n\t\t'We received a request to reset your password, please click the button below. If you did not request a password reset, you can safely ignore this email.';\n\tconst buttonText = content?.buttonText || 'Reset Password';\n\n\treturn (\n\t\t<BaseEmail theme={theme} previewText={previewText}>\n\t\t\t<Heading\n\t\t\t\tstyle={{\n\t\t\t\t\tcolor: theme.colours.text.heading,\n\t\t\t\t\tfontSize: '24px',\n\t\t\t\t\tfontWeight: 'bold',\n\t\t\t\t\tmarginBottom: '20px',\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{heading}\n\t\t\t</Heading>\n\t\t\t<Text\n\t\t\t\tstyle={{\n\t\t\t\t\tcolor: theme.colours.text.body,\n\t\t\t\t\tfontSize: '16px',\n\t\t\t\t\tlineHeight: '24px',\n\t\t\t\t\tmarginBottom: '30px',\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{bodyText}\n\t\t\t</Text>\n\t\t\t<Section style={{ textAlign: 'center', marginBottom: '30px' }}>\n\t\t\t\t<Button\n\t\t\t\t\thref={resetUrl}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tbackgroundColor: theme.colours.background.accent,\n\t\t\t\t\t\tcolor: theme.colours.text.heading,\n\t\t\t\t\t\tpadding: '12px 32px',\n\t\t\t\t\t\tborderRadius: '5px',\n\t\t\t\t\t\tfontSize: '16px',\n\t\t\t\t\t\tfontWeight: 'bold',\n\t\t\t\t\t\ttextDecoration: 'none',\n\t\t\t\t\t\tdisplay: 'inline-block',\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{buttonText}\n\t\t\t\t</Button>\n\t\t\t</Section>\n\t\t</BaseEmail>\n\t);\n};\n"],"names":["BaseEmail","Button","Heading","Section","Text","React","ForgotPasswordEmail","theme","user","resetUrl","content","userName","firstName","email","previewText","heading","bodyText","buttonText","style","color","colours","text","fontSize","fontWeight","marginBottom","body","lineHeight","textAlign","href","backgroundColor","background","accent","padding","borderRadius","textDecoration","display"],"mappings":";AAAA,SAASA,SAAS,EAAEC,MAAM,EAAEC,OAAO,EAAEC,OAAO,EAAEC,IAAI,QAAQ,8BAA8B;AAExF,YAAYC,WAAW,QAAQ;AAmC/B;;;;;CAKC,GACD,OAAO,MAAMC,sBAAsB,CAAC,EACnCC,KAAK,EACLC,IAAI,EACJC,QAAQ,EACRC,OAAO,EACmB;IAC1B,MAAMC,WAAWH,KAAKI,SAAS,IAAIJ,KAAKK,KAAK,IAAI;IACjD,MAAMC,cAAcJ,SAASI,eAAe;IAC5C,MAAMC,UAAUL,SAASK,WAAW,CAAC,OAAO,EAAEJ,SAAS,CAAC,CAAC;IACzD,MAAMK,WACLN,SAASM,YACT;IACD,MAAMC,aAAaP,SAASO,cAAc;IAE1C,qBACC,MAACjB;QAAUO,OAAOA;QAAOO,aAAaA;;0BACrC,KAACZ;gBACAgB,OAAO;oBACNC,OAAOZ,MAAMa,OAAO,CAACC,IAAI,CAACN,OAAO;oBACjCO,UAAU;oBACVC,YAAY;oBACZC,cAAc;gBACf;0BAECT;;0BAEF,KAACX;gBACAc,OAAO;oBACNC,OAAOZ,MAAMa,OAAO,CAACC,IAAI,CAACI,IAAI;oBAC9BH,UAAU;oBACVI,YAAY;oBACZF,cAAc;gBACf;0BAECR;;0BAEF,KAACb;gBAAQe,OAAO;oBAAES,WAAW;oBAAUH,cAAc;gBAAO;0BAC3D,cAAA,KAACvB;oBACA2B,MAAMnB;oBACNS,OAAO;wBACNW,iBAAiBtB,MAAMa,OAAO,CAACU,UAAU,CAACC,MAAM;wBAChDZ,OAAOZ,MAAMa,OAAO,CAACC,IAAI,CAACN,OAAO;wBACjCiB,SAAS;wBACTC,cAAc;wBACdX,UAAU;wBACVC,YAAY;wBACZW,gBAAgB;wBAChBC,SAAS;oBACV;8BAEClB;;;;;AAKN,EAAE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
3
|
+
import { ForgotPasswordEmail } from './ForgotPasswordEmail.js';
|
|
4
|
+
// Mock the email templates module
|
|
5
|
+
vi.mock('@ainsleydev/email-templates', ()=>({
|
|
6
|
+
BaseEmail: ({ children })=>children,
|
|
7
|
+
Button: ({ children, href })=>/*#__PURE__*/ _jsx("a", {
|
|
8
|
+
href: href,
|
|
9
|
+
children: children
|
|
10
|
+
}),
|
|
11
|
+
Heading: ({ children })=>/*#__PURE__*/ _jsx("h1", {
|
|
12
|
+
children: children
|
|
13
|
+
}),
|
|
14
|
+
Section: ({ children })=>/*#__PURE__*/ _jsx("section", {
|
|
15
|
+
children: children
|
|
16
|
+
}),
|
|
17
|
+
Text: ({ children })=>/*#__PURE__*/ _jsx("p", {
|
|
18
|
+
children: children
|
|
19
|
+
})
|
|
20
|
+
}));
|
|
21
|
+
const mockTheme = {
|
|
22
|
+
colours: {
|
|
23
|
+
text: {
|
|
24
|
+
heading: '#000000',
|
|
25
|
+
body: '#333333',
|
|
26
|
+
action: '#007bff',
|
|
27
|
+
negative: '#ffffff'
|
|
28
|
+
},
|
|
29
|
+
background: {
|
|
30
|
+
white: '#ffffff',
|
|
31
|
+
dark: '#000000',
|
|
32
|
+
darker: '#0f0f0f',
|
|
33
|
+
highlight: '#f5f5f5',
|
|
34
|
+
accent: '#007bff'
|
|
35
|
+
},
|
|
36
|
+
border: {
|
|
37
|
+
light: '#e0e0e0',
|
|
38
|
+
medium: '#cccccc',
|
|
39
|
+
dark: '#000000',
|
|
40
|
+
inverse: '#ffffff'
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
branding: {
|
|
44
|
+
companyName: 'Test Company',
|
|
45
|
+
logoUrl: 'https://example.com/logo.png',
|
|
46
|
+
logoWidth: 120,
|
|
47
|
+
websiteUrl: 'https://example.com'
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
describe('ForgotPasswordEmail', ()=>{
|
|
51
|
+
test('should render with default content when no overrides provided', ()=>{
|
|
52
|
+
const props = {
|
|
53
|
+
theme: mockTheme,
|
|
54
|
+
user: {
|
|
55
|
+
firstName: 'John',
|
|
56
|
+
email: 'john@example.com'
|
|
57
|
+
},
|
|
58
|
+
resetUrl: 'https://example.com/reset/token123'
|
|
59
|
+
};
|
|
60
|
+
const result = ForgotPasswordEmail(props);
|
|
61
|
+
// Component should render without errors
|
|
62
|
+
expect(result).toBeDefined();
|
|
63
|
+
});
|
|
64
|
+
test('should use firstName when available', ()=>{
|
|
65
|
+
const props = {
|
|
66
|
+
theme: mockTheme,
|
|
67
|
+
user: {
|
|
68
|
+
firstName: 'Jane',
|
|
69
|
+
email: 'jane@example.com'
|
|
70
|
+
},
|
|
71
|
+
resetUrl: 'https://example.com/reset/token123'
|
|
72
|
+
};
|
|
73
|
+
const result = ForgotPasswordEmail(props);
|
|
74
|
+
// The userName should be set to firstName
|
|
75
|
+
// This is tested through the default heading which uses userName
|
|
76
|
+
expect(result).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
test('should use email when firstName is not available', ()=>{
|
|
79
|
+
const props = {
|
|
80
|
+
theme: mockTheme,
|
|
81
|
+
user: {
|
|
82
|
+
email: 'john@example.com'
|
|
83
|
+
},
|
|
84
|
+
resetUrl: 'https://example.com/reset/token123'
|
|
85
|
+
};
|
|
86
|
+
const result = ForgotPasswordEmail(props);
|
|
87
|
+
// The userName should be set to email
|
|
88
|
+
expect(result).toBeDefined();
|
|
89
|
+
});
|
|
90
|
+
test('should use "there" when neither firstName nor email is available', ()=>{
|
|
91
|
+
const props = {
|
|
92
|
+
theme: mockTheme,
|
|
93
|
+
user: {},
|
|
94
|
+
resetUrl: 'https://example.com/reset/token123'
|
|
95
|
+
};
|
|
96
|
+
const result = ForgotPasswordEmail(props);
|
|
97
|
+
// The userName should default to "there"
|
|
98
|
+
expect(result).toBeDefined();
|
|
99
|
+
});
|
|
100
|
+
test('should use custom content overrides when provided', ()=>{
|
|
101
|
+
const props = {
|
|
102
|
+
theme: mockTheme,
|
|
103
|
+
user: {
|
|
104
|
+
firstName: 'John'
|
|
105
|
+
},
|
|
106
|
+
resetUrl: 'https://example.com/reset/token123',
|
|
107
|
+
content: {
|
|
108
|
+
previewText: 'Custom preview text',
|
|
109
|
+
heading: 'Custom Heading',
|
|
110
|
+
bodyText: 'Custom body text',
|
|
111
|
+
buttonText: 'Custom Button'
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const result = ForgotPasswordEmail(props);
|
|
115
|
+
// Component should use custom content
|
|
116
|
+
expect(result).toBeDefined();
|
|
117
|
+
});
|
|
118
|
+
test('should use partial content overrides with defaults', ()=>{
|
|
119
|
+
const props = {
|
|
120
|
+
theme: mockTheme,
|
|
121
|
+
user: {
|
|
122
|
+
firstName: 'John'
|
|
123
|
+
},
|
|
124
|
+
resetUrl: 'https://example.com/reset/token123',
|
|
125
|
+
content: {
|
|
126
|
+
heading: 'Custom Heading Only'
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const result = ForgotPasswordEmail(props);
|
|
130
|
+
// Component should use custom heading and default for others
|
|
131
|
+
expect(result).toBeDefined();
|
|
132
|
+
});
|
|
133
|
+
test('should pass resetUrl to the button', ()=>{
|
|
134
|
+
const resetUrl = 'https://example.com/reset/abc123xyz';
|
|
135
|
+
const props = {
|
|
136
|
+
theme: mockTheme,
|
|
137
|
+
user: {
|
|
138
|
+
firstName: 'John'
|
|
139
|
+
},
|
|
140
|
+
resetUrl
|
|
141
|
+
};
|
|
142
|
+
const result = ForgotPasswordEmail(props);
|
|
143
|
+
// The resetUrl should be used in the button href
|
|
144
|
+
expect(result).toBeDefined();
|
|
145
|
+
});
|
|
146
|
+
test('should apply theme colors correctly', ()=>{
|
|
147
|
+
const customTheme = {
|
|
148
|
+
colours: {
|
|
149
|
+
text: {
|
|
150
|
+
heading: '#ff0000',
|
|
151
|
+
body: '#00ff00',
|
|
152
|
+
action: '#0000ff',
|
|
153
|
+
negative: '#ffffff'
|
|
154
|
+
},
|
|
155
|
+
background: {
|
|
156
|
+
white: '#ffffff',
|
|
157
|
+
dark: '#000000',
|
|
158
|
+
darker: '#0f0f0f',
|
|
159
|
+
highlight: '#f5f5f5',
|
|
160
|
+
accent: '#0000ff'
|
|
161
|
+
},
|
|
162
|
+
border: {
|
|
163
|
+
light: '#e0e0e0',
|
|
164
|
+
medium: '#cccccc',
|
|
165
|
+
dark: '#000000',
|
|
166
|
+
inverse: '#ffffff'
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
branding: {
|
|
170
|
+
companyName: 'Custom Company',
|
|
171
|
+
logoUrl: 'https://custom.com/logo.png',
|
|
172
|
+
logoWidth: 150,
|
|
173
|
+
websiteUrl: 'https://custom.com'
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
const props = {
|
|
177
|
+
theme: customTheme,
|
|
178
|
+
user: {
|
|
179
|
+
firstName: 'John'
|
|
180
|
+
},
|
|
181
|
+
resetUrl: 'https://example.com/reset/token123'
|
|
182
|
+
};
|
|
183
|
+
const result = ForgotPasswordEmail(props);
|
|
184
|
+
// Component should use custom theme colors
|
|
185
|
+
expect(result).toBeDefined();
|
|
186
|
+
});
|
|
187
|
+
test('should handle empty content object', ()=>{
|
|
188
|
+
const props = {
|
|
189
|
+
theme: mockTheme,
|
|
190
|
+
user: {
|
|
191
|
+
firstName: 'John'
|
|
192
|
+
},
|
|
193
|
+
resetUrl: 'https://example.com/reset/token123',
|
|
194
|
+
content: {}
|
|
195
|
+
};
|
|
196
|
+
const result = ForgotPasswordEmail(props);
|
|
197
|
+
// Should use all defaults when content is empty object
|
|
198
|
+
expect(result).toBeDefined();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
//# sourceMappingURL=ForgotPasswordEmail.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/email/ForgotPasswordEmail.test.tsx"],"sourcesContent":["import type { EmailTheme } from '@ainsleydev/email-templates';\nimport { describe, expect, test, vi } from 'vitest';\nimport { ForgotPasswordEmail } from './ForgotPasswordEmail.js';\nimport type { ForgotPasswordEmailProps } from './ForgotPasswordEmail.js';\n\n// Mock the email templates module\nvi.mock('@ainsleydev/email-templates', () => ({\n\tBaseEmail: ({ children }: { children: React.ReactNode }) => children,\n\tButton: ({ children, href }: { children: React.ReactNode; href: string }) => (\n\t\t<a href={href}>{children}</a>\n\t),\n\tHeading: ({ children }: { children: React.ReactNode }) => <h1>{children}</h1>,\n\tSection: ({ children }: { children: React.ReactNode }) => <section>{children}</section>,\n\tText: ({ children }: { children: React.ReactNode }) => <p>{children}</p>,\n}));\n\nconst mockTheme: EmailTheme = {\n\tcolours: {\n\t\ttext: {\n\t\t\theading: '#000000',\n\t\t\tbody: '#333333',\n\t\t\taction: '#007bff',\n\t\t\tnegative: '#ffffff',\n\t\t},\n\t\tbackground: {\n\t\t\twhite: '#ffffff',\n\t\t\tdark: '#000000',\n\t\t\tdarker: '#0f0f0f',\n\t\t\thighlight: '#f5f5f5',\n\t\t\taccent: '#007bff',\n\t\t},\n\t\tborder: {\n\t\t\tlight: '#e0e0e0',\n\t\t\tmedium: '#cccccc',\n\t\t\tdark: '#000000',\n\t\t\tinverse: '#ffffff',\n\t\t},\n\t},\n\tbranding: {\n\t\tcompanyName: 'Test Company',\n\t\tlogoUrl: 'https://example.com/logo.png',\n\t\tlogoWidth: 120,\n\t\twebsiteUrl: 'https://example.com',\n\t},\n};\n\ndescribe('ForgotPasswordEmail', () => {\n\ttest('should render with default content when no overrides provided', () => {\n\t\tconst props: ForgotPasswordEmailProps = {\n\t\t\ttheme: mockTheme,\n\t\t\tuser: {\n\t\t\t\tfirstName: 'John',\n\t\t\t\temail: 'john@example.com',\n\t\t\t},\n\t\t\tresetUrl: 'https://example.com/reset/token123',\n\t\t};\n\n\t\tconst result = ForgotPasswordEmail(props);\n\n\t\t// Component should render without errors\n\t\texpect(result).toBeDefined();\n\t});\n\n\ttest('should use firstName when available', () => {\n\t\tconst props: ForgotPasswordEmailProps = {\n\t\t\ttheme: mockTheme,\n\t\t\tuser: {\n\t\t\t\tfirstName: 'Jane',\n\t\t\t\temail: 'jane@example.com',\n\t\t\t},\n\t\t\tresetUrl: 'https://example.com/reset/token123',\n\t\t};\n\n\t\tconst result = ForgotPasswordEmail(props);\n\n\t\t// The userName should be set to firstName\n\t\t// This is tested through the default heading which uses userName\n\t\texpect(result).toBeDefined();\n\t});\n\n\ttest('should use email when firstName is not available', () => {\n\t\tconst props: ForgotPasswordEmailProps = {\n\t\t\ttheme: mockTheme,\n\t\t\tuser: {\n\t\t\t\temail: 'john@example.com',\n\t\t\t},\n\t\t\tresetUrl: 'https://example.com/reset/token123',\n\t\t};\n\n\t\tconst result = ForgotPasswordEmail(props);\n\n\t\t// The userName should be set to email\n\t\texpect(result).toBeDefined();\n\t});\n\n\ttest('should use \"there\" when neither firstName nor email is available', () => {\n\t\tconst props: ForgotPasswordEmailProps = {\n\t\t\ttheme: mockTheme,\n\t\t\tuser: {},\n\t\t\tresetUrl: 'https://example.com/reset/token123',\n\t\t};\n\n\t\tconst result = ForgotPasswordEmail(props);\n\n\t\t// The userName should default to \"there\"\n\t\texpect(result).toBeDefined();\n\t});\n\n\ttest('should use custom content overrides when provided', () => {\n\t\tconst props: ForgotPasswordEmailProps = {\n\t\t\ttheme: mockTheme,\n\t\t\tuser: {\n\t\t\t\tfirstName: 'John',\n\t\t\t},\n\t\t\tresetUrl: 'https://example.com/reset/token123',\n\t\t\tcontent: {\n\t\t\t\tpreviewText: 'Custom preview text',\n\t\t\t\theading: 'Custom Heading',\n\t\t\t\tbodyText: 'Custom body text',\n\t\t\t\tbuttonText: 'Custom Button',\n\t\t\t},\n\t\t};\n\n\t\tconst result = ForgotPasswordEmail(props);\n\n\t\t// Component should use custom content\n\t\texpect(result).toBeDefined();\n\t});\n\n\ttest('should use partial content overrides with defaults', () => {\n\t\tconst props: ForgotPasswordEmailProps = {\n\t\t\ttheme: mockTheme,\n\t\t\tuser: {\n\t\t\t\tfirstName: 'John',\n\t\t\t},\n\t\t\tresetUrl: 'https://example.com/reset/token123',\n\t\t\tcontent: {\n\t\t\t\theading: 'Custom Heading Only',\n\t\t\t\t// Other fields should use defaults\n\t\t\t},\n\t\t};\n\n\t\tconst result = ForgotPasswordEmail(props);\n\n\t\t// Component should use custom heading and default for others\n\t\texpect(result).toBeDefined();\n\t});\n\n\ttest('should pass resetUrl to the button', () => {\n\t\tconst resetUrl = 'https://example.com/reset/abc123xyz';\n\t\tconst props: ForgotPasswordEmailProps = {\n\t\t\ttheme: mockTheme,\n\t\t\tuser: {\n\t\t\t\tfirstName: 'John',\n\t\t\t},\n\t\t\tresetUrl,\n\t\t};\n\n\t\tconst result = ForgotPasswordEmail(props);\n\n\t\t// The resetUrl should be used in the button href\n\t\texpect(result).toBeDefined();\n\t});\n\n\ttest('should apply theme colors correctly', () => {\n\t\tconst customTheme: EmailTheme = {\n\t\t\tcolours: {\n\t\t\t\ttext: {\n\t\t\t\t\theading: '#ff0000',\n\t\t\t\t\tbody: '#00ff00',\n\t\t\t\t\taction: '#0000ff',\n\t\t\t\t\tnegative: '#ffffff',\n\t\t\t\t},\n\t\t\t\tbackground: {\n\t\t\t\t\twhite: '#ffffff',\n\t\t\t\t\tdark: '#000000',\n\t\t\t\t\tdarker: '#0f0f0f',\n\t\t\t\t\thighlight: '#f5f5f5',\n\t\t\t\t\taccent: '#0000ff',\n\t\t\t\t},\n\t\t\t\tborder: {\n\t\t\t\t\tlight: '#e0e0e0',\n\t\t\t\t\tmedium: '#cccccc',\n\t\t\t\t\tdark: '#000000',\n\t\t\t\t\tinverse: '#ffffff',\n\t\t\t\t},\n\t\t\t},\n\t\t\tbranding: {\n\t\t\t\tcompanyName: 'Custom Company',\n\t\t\t\tlogoUrl: 'https://custom.com/logo.png',\n\t\t\t\tlogoWidth: 150,\n\t\t\t\twebsiteUrl: 'https://custom.com',\n\t\t\t},\n\t\t};\n\n\t\tconst props: ForgotPasswordEmailProps = {\n\t\t\ttheme: customTheme,\n\t\t\tuser: {\n\t\t\t\tfirstName: 'John',\n\t\t\t},\n\t\t\tresetUrl: 'https://example.com/reset/token123',\n\t\t};\n\n\t\tconst result = ForgotPasswordEmail(props);\n\n\t\t// Component should use custom theme colors\n\t\texpect(result).toBeDefined();\n\t});\n\n\ttest('should handle empty content object', () => {\n\t\tconst props: ForgotPasswordEmailProps = {\n\t\t\ttheme: mockTheme,\n\t\t\tuser: {\n\t\t\t\tfirstName: 'John',\n\t\t\t},\n\t\t\tresetUrl: 'https://example.com/reset/token123',\n\t\t\tcontent: {},\n\t\t};\n\n\t\tconst result = ForgotPasswordEmail(props);\n\n\t\t// Should use all defaults when content is empty object\n\t\texpect(result).toBeDefined();\n\t});\n});\n"],"names":["describe","expect","test","vi","ForgotPasswordEmail","mock","BaseEmail","children","Button","href","a","Heading","h1","Section","section","Text","p","mockTheme","colours","text","heading","body","action","negative","background","white","dark","darker","highlight","accent","border","light","medium","inverse","branding","companyName","logoUrl","logoWidth","websiteUrl","props","theme","user","firstName","email","resetUrl","result","toBeDefined","content","previewText","bodyText","buttonText","customTheme"],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { EmailTheme } from '@ainsleydev/email-templates';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Props for the VerifyAccountEmail component.
|
|
5
|
+
*/
|
|
6
|
+
export interface VerifyAccountEmailProps {
|
|
7
|
+
/**
|
|
8
|
+
* The email theme (required by renderEmail).
|
|
9
|
+
*/
|
|
10
|
+
theme: EmailTheme;
|
|
11
|
+
/**
|
|
12
|
+
* The user object containing user information.
|
|
13
|
+
*/
|
|
14
|
+
user: {
|
|
15
|
+
firstName?: string;
|
|
16
|
+
email?: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The URL for verifying the account.
|
|
20
|
+
*/
|
|
21
|
+
verifyUrl: string;
|
|
22
|
+
/**
|
|
23
|
+
* Optional content overrides.
|
|
24
|
+
*/
|
|
25
|
+
content?: {
|
|
26
|
+
previewText?: string;
|
|
27
|
+
heading?: string;
|
|
28
|
+
bodyText?: string;
|
|
29
|
+
buttonText?: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Email template for account verification in Payload CMS.
|
|
34
|
+
*
|
|
35
|
+
* @param props - The component props
|
|
36
|
+
* @returns The rendered email component
|
|
37
|
+
*/
|
|
38
|
+
export declare const VerifyAccountEmail: ({ theme, user, verifyUrl, content, }: VerifyAccountEmailProps) => React.JSX.Element;
|