@contractspec/bundle.marketing 3.7.5 → 3.7.7
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/.turbo/turbo-build.log +84 -84
- package/AGENTS.md +29 -21
- package/CHANGELOG.md +27 -0
- package/README.md +36 -49
- package/dist/browser/components/marketing/ChangelogPage.js +8 -8
- package/dist/browser/components/marketing/CofounderPage.js +167 -523
- package/dist/browser/components/marketing/ContactClient.js +200 -207
- package/dist/browser/components/marketing/ContributePage.js +211 -463
- package/dist/browser/components/marketing/DesignPartnerPage.js +165 -218
- package/dist/browser/components/marketing/LandingPage.js +464 -568
- package/dist/browser/components/marketing/PricingClient.js +213 -839
- package/dist/browser/components/marketing/ProductClientPage.js +265 -463
- package/dist/browser/components/marketing/index.js +2007 -3338
- package/dist/browser/components/marketing/pricing-thinking-modal.js +12 -12
- package/dist/browser/components/marketing/sections/AudienceSection.js +2 -2
- package/dist/browser/components/marketing/sections/CorePositioningSection.js +2 -2
- package/dist/browser/components/marketing/sections/CtaSection.js +3 -3
- package/dist/browser/components/marketing/sections/FearsSection.js +3 -3
- package/dist/browser/components/marketing/sections/HeroMarketingSection.js +6 -6
- package/dist/browser/components/marketing/sections/IconGridSection.js +2 -2
- package/dist/browser/components/marketing/sections/OutputsSection.js +2 -2
- package/dist/browser/components/marketing/sections/ProblemSection.js +2 -2
- package/dist/browser/components/marketing/sections/SolutionSection.js +2 -2
- package/dist/browser/components/marketing/sections/StepsSection.js +4 -4
- package/dist/browser/components/marketing/studio-signup-section.js +25 -41
- package/dist/browser/components/templates/TemplatesClientPage.js +2324 -3578
- package/dist/browser/components/templates/TemplatesPage.js +1 -1
- package/dist/browser/components/templates/TemplatesPreviewModal.js +3 -3
- package/dist/browser/components/templates/index.js +2361 -3615
- package/dist/browser/index.js +2363 -3617
- package/dist/browser/libs/email/client.js +1 -1
- package/dist/browser/libs/email/contact.js +1 -1
- package/dist/browser/libs/email/newsletter.js +1 -1
- package/dist/browser/libs/email/waitlist-application.js +1 -1
- package/dist/browser/libs/email/waitlist.js +1 -1
- package/dist/browser/registry/engine.js +2003 -3334
- package/dist/browser/registry/index.js +2003 -3334
- package/dist/browser/registry/registry-docs.js +2 -2
- package/dist/browser/registry/registry-landing.js +2007 -3338
- package/dist/browser/registry/registry.js +2003 -3334
- package/dist/browser/registry/utils.js +2003 -3334
- package/dist/components/marketing/ChangelogPage.js +8 -8
- package/dist/components/marketing/CofounderPage.js +167 -523
- package/dist/components/marketing/ContactClient.js +200 -207
- package/dist/components/marketing/ContributePage.d.ts +0 -2
- package/dist/components/marketing/ContributePage.js +211 -463
- package/dist/components/marketing/DesignPartnerPage.js +165 -218
- package/dist/components/marketing/LandingPage.js +464 -568
- package/dist/components/marketing/PricingClient.js +213 -839
- package/dist/components/marketing/ProductClientPage.js +265 -463
- package/dist/components/marketing/index.d.ts +5 -5
- package/dist/components/marketing/index.js +2007 -3338
- package/dist/components/marketing/pricing-thinking-modal.js +12 -12
- package/dist/components/marketing/sections/AudienceSection.js +2 -2
- package/dist/components/marketing/sections/CorePositioningSection.js +2 -2
- package/dist/components/marketing/sections/CtaSection.js +3 -3
- package/dist/components/marketing/sections/FearsSection.js +3 -3
- package/dist/components/marketing/sections/HeroMarketingSection.js +6 -6
- package/dist/components/marketing/sections/IconGridSection.d.ts +3 -3
- package/dist/components/marketing/sections/IconGridSection.js +2 -2
- package/dist/components/marketing/sections/OutputsSection.js +2 -2
- package/dist/components/marketing/sections/ProblemSection.js +2 -2
- package/dist/components/marketing/sections/SolutionSection.js +2 -2
- package/dist/components/marketing/sections/StepsSection.js +4 -4
- package/dist/components/marketing/studio-signup-section.js +25 -41
- package/dist/components/templates/TemplatesClientPage.js +2324 -3578
- package/dist/components/templates/TemplatesPage.js +1 -1
- package/dist/components/templates/TemplatesPreviewModal.js +3 -3
- package/dist/components/templates/index.js +2361 -3615
- package/dist/index.js +2363 -3617
- package/dist/libs/email/client.js +1 -1
- package/dist/libs/email/contact.js +1 -1
- package/dist/libs/email/newsletter.js +1 -1
- package/dist/libs/email/waitlist-application.js +1 -1
- package/dist/libs/email/waitlist.js +1 -1
- package/dist/node/components/marketing/ChangelogPage.js +8 -8
- package/dist/node/components/marketing/CofounderPage.js +167 -523
- package/dist/node/components/marketing/ContactClient.js +200 -207
- package/dist/node/components/marketing/ContributePage.js +211 -463
- package/dist/node/components/marketing/DesignPartnerPage.js +165 -218
- package/dist/node/components/marketing/LandingPage.js +464 -568
- package/dist/node/components/marketing/PricingClient.js +213 -839
- package/dist/node/components/marketing/ProductClientPage.js +265 -463
- package/dist/node/components/marketing/index.js +2007 -3338
- package/dist/node/components/marketing/pricing-thinking-modal.js +12 -12
- package/dist/node/components/marketing/sections/AudienceSection.js +2 -2
- package/dist/node/components/marketing/sections/CorePositioningSection.js +2 -2
- package/dist/node/components/marketing/sections/CtaSection.js +3 -3
- package/dist/node/components/marketing/sections/FearsSection.js +3 -3
- package/dist/node/components/marketing/sections/HeroMarketingSection.js +6 -6
- package/dist/node/components/marketing/sections/IconGridSection.js +2 -2
- package/dist/node/components/marketing/sections/OutputsSection.js +2 -2
- package/dist/node/components/marketing/sections/ProblemSection.js +2 -2
- package/dist/node/components/marketing/sections/SolutionSection.js +2 -2
- package/dist/node/components/marketing/sections/StepsSection.js +4 -4
- package/dist/node/components/marketing/studio-signup-section.js +25 -41
- package/dist/node/components/templates/TemplatesClientPage.js +2324 -3578
- package/dist/node/components/templates/TemplatesPage.js +1 -1
- package/dist/node/components/templates/TemplatesPreviewModal.js +3 -3
- package/dist/node/components/templates/index.js +2361 -3615
- package/dist/node/index.js +2363 -3617
- package/dist/node/libs/email/client.js +1 -1
- package/dist/node/libs/email/contact.js +1 -1
- package/dist/node/libs/email/newsletter.js +1 -1
- package/dist/node/libs/email/waitlist-application.js +1 -1
- package/dist/node/libs/email/waitlist.js +1 -1
- package/dist/node/registry/engine.js +2003 -3334
- package/dist/node/registry/index.js +2003 -3334
- package/dist/node/registry/registry-docs.js +2 -2
- package/dist/node/registry/registry-landing.js +2007 -3338
- package/dist/node/registry/registry.js +2003 -3334
- package/dist/node/registry/utils.js +2003 -3334
- package/dist/registry/engine.js +2003 -3334
- package/dist/registry/index.js +2003 -3334
- package/dist/registry/registry-docs.js +2 -2
- package/dist/registry/registry-landing.js +2007 -3338
- package/dist/registry/registry.js +2003 -3334
- package/dist/registry/utils.js +2003 -3334
- package/package.json +29 -29
- package/src/bundles/MarketingBundle.ts +273 -273
- package/src/components/marketing/ChangelogPage.tsx +72 -100
- package/src/components/marketing/CofounderPage.tsx +120 -384
- package/src/components/marketing/ContactClient.tsx +164 -154
- package/src/components/marketing/ContributePage.tsx +139 -313
- package/src/components/marketing/DesignPartnerPage.tsx +133 -171
- package/src/components/marketing/LandingPage.tsx +353 -25
- package/src/components/marketing/PricingClient.tsx +192 -437
- package/src/components/marketing/ProductClientPage.tsx +255 -377
- package/src/components/marketing/index.ts +5 -5
- package/src/components/marketing/pricing-thinking-modal.tsx +197 -197
- package/src/components/marketing/sections/AudienceSection.tsx +55 -56
- package/src/components/marketing/sections/CorePositioningSection.tsx +37 -37
- package/src/components/marketing/sections/CtaSection.tsx +49 -50
- package/src/components/marketing/sections/DevelopersSection.tsx +26 -27
- package/src/components/marketing/sections/FearsSection.tsx +36 -37
- package/src/components/marketing/sections/HeroMarketingSection.tsx +59 -59
- package/src/components/marketing/sections/IconGridSection.tsx +71 -71
- package/src/components/marketing/sections/OutputsSection.tsx +51 -52
- package/src/components/marketing/sections/ProblemSection.tsx +39 -40
- package/src/components/marketing/sections/SolutionSection.tsx +39 -40
- package/src/components/marketing/sections/StepsSection.tsx +47 -48
- package/src/components/marketing/studio-signup-section.tsx +39 -41
- package/src/components/templates/TemplatesClientPage.tsx +727 -685
- package/src/components/templates/TemplatesPage.tsx +110 -110
- package/src/components/templates/TemplatesPreviewModal.tsx +197 -198
- package/src/index.ts +4 -4
- package/src/libs/email/client.test.ts +81 -81
- package/src/libs/email/client.ts +111 -111
- package/src/libs/email/contact.ts +35 -35
- package/src/libs/email/newsletter.ts +46 -46
- package/src/libs/email/types.ts +29 -29
- package/src/libs/email/utils.ts +5 -5
- package/src/libs/email/waitlist-application.ts +72 -72
- package/src/libs/email/waitlist.ts +46 -46
- package/src/libs/pricing-examples.ts +12 -12
- package/src/registry/engine.ts +16 -16
- package/src/registry/factory.ts +57 -57
- package/src/registry/registry-docs.ts +656 -666
- package/src/registry/registry-landing.ts +94 -95
- package/src/registry/registry.ts +36 -37
- package/src/registry/types.ts +2 -2
- package/src/registry/utils.ts +56 -56
- package/tsconfig.json +11 -11
- package/tsdown.config.js +5 -5
package/src/libs/email/client.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
+
import { Logger } from '@contractspec/lib.logger';
|
|
1
2
|
import { createClient, Temv1alpha1 } from '@scaleway/sdk';
|
|
2
3
|
import type { Region } from '@scaleway/sdk-client';
|
|
3
|
-
import { Logger } from '@contractspec/lib.logger';
|
|
4
4
|
import type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
EmailAddress,
|
|
6
|
+
EmailConfigResult,
|
|
7
|
+
EmailSendOutcome,
|
|
8
|
+
EmailServiceConfig,
|
|
9
|
+
SendEmailRequest,
|
|
10
10
|
} from './types';
|
|
11
11
|
|
|
12
12
|
const DEFAULT_FROM: EmailAddress = {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
email: 'noreply@transactional.contractspec.io',
|
|
14
|
+
name: 'ContractSpec',
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const DEFAULT_TEAM_INBOX: EmailAddress = {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
email: 'contact@contractspec.io',
|
|
19
|
+
name: 'ContractSpec Team',
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
const DEFAULT_REGION: Region = 'fr-par';
|
|
@@ -26,121 +26,121 @@ type EmailApi = Pick<Temv1alpha1.API, 'createEmail'>;
|
|
|
26
26
|
let cachedConfig: EmailServiceConfig | null = null;
|
|
27
27
|
let cachedClient: EmailApi | null = null;
|
|
28
28
|
let apiFactory: (client: ReturnType<typeof createClient>) => EmailApi = (
|
|
29
|
-
|
|
29
|
+
client
|
|
30
30
|
) => new Temv1alpha1.API(client);
|
|
31
31
|
|
|
32
32
|
const mapRegion = (value?: string | null): Region => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
const normalized = value?.trim().toLowerCase();
|
|
34
|
+
if (normalized === 'par' || normalized === 'fr-par') return 'fr-par';
|
|
35
|
+
if (normalized === 'ams' || normalized === 'nl-ams') return 'nl-ams';
|
|
36
|
+
if (normalized === 'waw' || normalized === 'pl-waw') return 'pl-waw';
|
|
37
|
+
return DEFAULT_REGION;
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
export const getEmailConfig = (): EmailConfigResult => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
41
|
+
if (cachedConfig) {
|
|
42
|
+
return { ok: true, config: cachedConfig };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const accessKey =
|
|
46
|
+
process.env.SCALEWAY_ACCESS_KEY || process.env.SCALEWAY_ACCESS_KEY_QUEUE;
|
|
47
|
+
const secretKey =
|
|
48
|
+
process.env.SCALEWAY_SECRET_KEY || process.env.SCALEWAY_SECRET_KEY_QUEUE;
|
|
49
|
+
const projectId = process.env.SCALEWAY_PROJECT_ID;
|
|
50
|
+
|
|
51
|
+
if (!accessKey || !secretKey || !projectId) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
errorMessage:
|
|
55
|
+
'Email service is not configured. Please contact us directly at contact@contractspec.io.',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const region = mapRegion(process.env.SCALEWAY_REGION);
|
|
60
|
+
|
|
61
|
+
cachedConfig = {
|
|
62
|
+
accessKey,
|
|
63
|
+
secretKey,
|
|
64
|
+
projectId,
|
|
65
|
+
region,
|
|
66
|
+
defaultZone: `${region}-1`,
|
|
67
|
+
from: {
|
|
68
|
+
email: process.env.SCALEWAY_EMAIL_FROM_EMAIL ?? DEFAULT_FROM.email,
|
|
69
|
+
name: process.env.SCALEWAY_EMAIL_FROM_NAME ?? DEFAULT_FROM.name,
|
|
70
|
+
},
|
|
71
|
+
teamInbox: {
|
|
72
|
+
email: process.env.SCALEWAY_EMAIL_TEAM_EMAIL ?? DEFAULT_TEAM_INBOX.email,
|
|
73
|
+
name: process.env.SCALEWAY_EMAIL_TEAM_NAME ?? DEFAULT_TEAM_INBOX.name,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return { ok: true, config: cachedConfig };
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
const getTemClient = (config: EmailServiceConfig): EmailApi => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
if (cachedClient) {
|
|
82
|
+
return cachedClient;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const client = createClient({
|
|
86
|
+
accessKey: config.accessKey,
|
|
87
|
+
secretKey: config.secretKey,
|
|
88
|
+
defaultProjectId: config.projectId,
|
|
89
|
+
defaultRegion: config.region,
|
|
90
|
+
defaultZone: config.defaultZone,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
cachedClient = apiFactory(client);
|
|
94
|
+
return cachedClient;
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
export const sendEmail = async (
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
config: EmailServiceConfig,
|
|
99
|
+
request: SendEmailRequest
|
|
100
100
|
): Promise<EmailSendOutcome> => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
101
|
+
try {
|
|
102
|
+
const client = getTemClient(config);
|
|
103
|
+
|
|
104
|
+
await client.createEmail({
|
|
105
|
+
region: config.region,
|
|
106
|
+
projectId: config.projectId,
|
|
107
|
+
from: config.from,
|
|
108
|
+
to: request.to,
|
|
109
|
+
subject: request.subject,
|
|
110
|
+
text: request.text,
|
|
111
|
+
html: request.html || request.text,
|
|
112
|
+
additionalHeaders: request.replyTo
|
|
113
|
+
? [{ key: 'Reply-To', value: request.replyTo }]
|
|
114
|
+
: undefined,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return { success: true };
|
|
118
|
+
} catch (error) {
|
|
119
|
+
new Logger().error('scaleway_tem_email_send_failed', {
|
|
120
|
+
context: request.context ?? 'email',
|
|
121
|
+
error: error instanceof Error ? error.message : error,
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
error,
|
|
126
|
+
errorMessage: 'Failed to send email via Scaleway.',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
129
|
};
|
|
130
130
|
|
|
131
131
|
export const __internal = {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
132
|
+
resetCaches() {
|
|
133
|
+
cachedClient = null;
|
|
134
|
+
cachedConfig = null;
|
|
135
|
+
apiFactory = (client: ReturnType<typeof createClient>) =>
|
|
136
|
+
new Temv1alpha1.API(client);
|
|
137
|
+
},
|
|
138
|
+
setApiFactory(
|
|
139
|
+
factory: (client: ReturnType<typeof createClient>) => EmailApi
|
|
140
|
+
) {
|
|
141
|
+
apiFactory = factory;
|
|
142
|
+
},
|
|
143
|
+
setClient(client: EmailApi) {
|
|
144
|
+
cachedClient = client;
|
|
145
|
+
},
|
|
146
146
|
};
|
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
|
|
3
3
|
import { getEmailConfig, sendEmail } from './client';
|
|
4
|
-
import { escapeHtml, formatMultilineHtml } from './utils';
|
|
5
4
|
import type { SubmitContactFormResult } from './types';
|
|
5
|
+
import { escapeHtml, formatMultilineHtml } from './utils';
|
|
6
6
|
|
|
7
7
|
const CONTACT_MISSING_CONFIG =
|
|
8
|
-
|
|
8
|
+
'Email service is not configured. Please contact us directly at contact@contractspec.io.';
|
|
9
9
|
const CONTACT_SEND_ERROR =
|
|
10
|
-
|
|
10
|
+
'Failed to send message. Please contact us directly at contact@contractspec.io.';
|
|
11
11
|
|
|
12
12
|
export const submitContactForm = async (
|
|
13
|
-
|
|
13
|
+
formData: FormData
|
|
14
14
|
): Promise<SubmitContactFormResult> => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const name = (formData.get('name') ?? '').toString().trim();
|
|
16
|
+
const email = (formData.get('email') ?? '').toString().trim();
|
|
17
|
+
const message = (formData.get('message') ?? '').toString().trim();
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
if (!email) {
|
|
20
|
+
return {
|
|
21
|
+
success: false,
|
|
22
|
+
text: 'Please fill in all required fields.',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
const configResult = getEmailConfig();
|
|
27
|
+
if (!configResult.ok || !configResult.config) {
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
text: configResult.errorMessage ?? CONTACT_MISSING_CONFIG,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const senderName = name || email;
|
|
35
|
+
const emailContentText = `
|
|
36
36
|
New contact form submission from ${senderName}
|
|
37
37
|
|
|
38
38
|
Contact Information:
|
|
@@ -46,7 +46,7 @@ ${message || 'No message provided'}
|
|
|
46
46
|
Submitted via ContractSpec contact form
|
|
47
47
|
`.trim();
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
const emailContentHtml = `
|
|
50
50
|
<div style="font-family: sans-serif; max-width: 640px; margin: 0 auto;">
|
|
51
51
|
<h1 style="color: #8b5cf6; margin-bottom: 12px;">New contact form submission</h1>
|
|
52
52
|
<p style="margin: 0 0 12px;">From ${escapeHtml(senderName)}</p>
|
|
@@ -63,18 +63,18 @@ Submitted via ContractSpec contact form
|
|
|
63
63
|
</div>
|
|
64
64
|
`;
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
const sendResult = await sendEmail(configResult.config, {
|
|
67
|
+
to: [configResult.config.teamInbox],
|
|
68
|
+
subject: `New Contact Form Message from ${senderName}`,
|
|
69
|
+
text: emailContentText,
|
|
70
|
+
html: emailContentHtml,
|
|
71
|
+
replyTo: email,
|
|
72
|
+
context: 'contact-form',
|
|
73
|
+
});
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
if (!sendResult.success) {
|
|
76
|
+
return { success: false, text: CONTACT_SEND_ERROR };
|
|
77
|
+
}
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
return { success: true, text: 'Message sent successfully!' };
|
|
80
80
|
};
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
|
|
3
3
|
import { getEmailConfig, sendEmail } from './client';
|
|
4
|
-
import { formatMultilineHtml } from './utils';
|
|
5
4
|
import type { SubmitNewsletterResult } from './types';
|
|
5
|
+
import { formatMultilineHtml } from './utils';
|
|
6
6
|
|
|
7
7
|
const NEWSLETTER_MISSING_CONFIG =
|
|
8
|
-
|
|
8
|
+
'Newsletter service is not configured. Please try again later.';
|
|
9
9
|
const NEWSLETTER_SEND_ERROR =
|
|
10
|
-
|
|
10
|
+
'Failed to subscribe. Please try again later or contact us directly.';
|
|
11
11
|
|
|
12
12
|
export const subscribeToNewsletter = async (
|
|
13
|
-
|
|
13
|
+
formData: FormData
|
|
14
14
|
): Promise<SubmitNewsletterResult> => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
15
|
+
const email = (formData.get('email') ?? '').toString().trim();
|
|
16
|
+
|
|
17
|
+
if (!email || !email.includes('@')) {
|
|
18
|
+
return {
|
|
19
|
+
success: false,
|
|
20
|
+
text: 'Please enter a valid email address.',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const configResult = getEmailConfig();
|
|
25
|
+
if (!configResult.ok || !configResult.config) {
|
|
26
|
+
return {
|
|
27
|
+
success: false,
|
|
28
|
+
text: configResult.errorMessage ?? NEWSLETTER_MISSING_CONFIG,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const welcomeText = `
|
|
33
33
|
Welcome to ContractSpec!
|
|
34
34
|
|
|
35
35
|
Thanks for subscribing to our newsletter. You'll receive updates on:
|
|
@@ -49,7 +49,7 @@ https://contractspec.io
|
|
|
49
49
|
Unsubscribe: Reply to this email with "unsubscribe"
|
|
50
50
|
`.trim();
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
const welcomeHtml = `
|
|
53
53
|
<div style="font-family: sans-serif; max-width: 640px; margin: 0 auto;">
|
|
54
54
|
<h1 style="color: #8b5cf6;">Welcome to ContractSpec!</h1>
|
|
55
55
|
<p>Thanks for subscribing to our newsletter. You'll receive updates on:</p>
|
|
@@ -72,37 +72,37 @@ Unsubscribe: Reply to this email with "unsubscribe"
|
|
|
72
72
|
</div>
|
|
73
73
|
`;
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
const userSend = await sendEmail(configResult.config, {
|
|
76
|
+
to: [{ email }],
|
|
77
|
+
subject: 'Welcome to ContractSpec Newsletter',
|
|
78
|
+
text: welcomeText,
|
|
79
|
+
html: welcomeHtml,
|
|
80
|
+
context: 'newsletter-welcome',
|
|
81
|
+
});
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
if (!userSend.success) {
|
|
84
|
+
return { success: false, text: NEWSLETTER_SEND_ERROR };
|
|
85
|
+
}
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
const teamNotificationText = `New newsletter subscription from: ${email}`;
|
|
88
|
+
const teamNotificationHtml = `
|
|
89
89
|
<div style="font-family: sans-serif; max-width: 640px; margin: 0 auto;">
|
|
90
90
|
<p style="margin: 0 0 8px;">New newsletter subscription</p>
|
|
91
91
|
<p style="margin: 0;"><strong>Email:</strong> ${formatMultilineHtml(email)}</p>
|
|
92
92
|
</div>
|
|
93
93
|
`;
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
const teamSend = await sendEmail(configResult.config, {
|
|
96
|
+
to: [configResult.config.teamInbox],
|
|
97
|
+
subject: `New Newsletter Subscription: ${email}`,
|
|
98
|
+
text: teamNotificationText,
|
|
99
|
+
html: teamNotificationHtml,
|
|
100
|
+
context: 'newsletter-team-notification',
|
|
101
|
+
});
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
if (!teamSend.success) {
|
|
104
|
+
return { success: false, text: NEWSLETTER_SEND_ERROR };
|
|
105
|
+
}
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
return { success: true, text: 'Successfully subscribed!' };
|
|
108
108
|
};
|
package/src/libs/email/types.ts
CHANGED
|
@@ -3,57 +3,57 @@
|
|
|
3
3
|
import type { Region } from '@scaleway/sdk-client';
|
|
4
4
|
|
|
5
5
|
export interface SubmitContactFormResult {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
success: boolean;
|
|
7
|
+
text: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export interface SubmitNewsletterResult {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
success: boolean;
|
|
12
|
+
text: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface SubmitWaitlistResult {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
success: boolean;
|
|
17
|
+
text: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export interface SubmitWaitlistApplicationResult {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
success: boolean;
|
|
22
|
+
text: string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface EmailAddress {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
email: string;
|
|
27
|
+
name?: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface EmailServiceConfig {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
accessKey: string;
|
|
32
|
+
secretKey: string;
|
|
33
|
+
projectId: string;
|
|
34
|
+
region: Region;
|
|
35
|
+
defaultZone: string;
|
|
36
|
+
from: EmailAddress;
|
|
37
|
+
teamInbox: EmailAddress;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export interface EmailConfigResult {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
ok: boolean;
|
|
42
|
+
config?: EmailServiceConfig;
|
|
43
|
+
errorMessage?: string;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export interface SendEmailRequest {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
to: EmailAddress[];
|
|
48
|
+
subject: string;
|
|
49
|
+
text: string;
|
|
50
|
+
html: string;
|
|
51
|
+
replyTo?: string;
|
|
52
|
+
context?: string;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export interface EmailSendOutcome {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
success: boolean;
|
|
57
|
+
error?: unknown;
|
|
58
|
+
errorMessage?: string;
|
|
59
59
|
}
|
package/src/libs/email/utils.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const escapeHtml = (value: string): string =>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
value
|
|
3
|
+
.replaceAll('&', '&')
|
|
4
|
+
.replaceAll('<', '<')
|
|
5
|
+
.replaceAll('>', '>');
|
|
6
6
|
|
|
7
7
|
export const formatMultilineHtml = (value: string): string =>
|
|
8
|
-
|
|
8
|
+
escapeHtml(value).replaceAll('\n', '<br />');
|