@contractspec/bundle.marketing 3.7.6 → 3.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/.turbo/turbo-build.log +87 -87
  2. package/AGENTS.md +29 -21
  3. package/CHANGELOG.md +59 -0
  4. package/README.md +36 -49
  5. package/dist/browser/components/marketing/ChangelogPage.js +8 -8
  6. package/dist/browser/components/marketing/CofounderPage.js +167 -523
  7. package/dist/browser/components/marketing/ContactClient.js +200 -207
  8. package/dist/browser/components/marketing/ContributePage.js +211 -463
  9. package/dist/browser/components/marketing/DesignPartnerPage.js +165 -218
  10. package/dist/browser/components/marketing/LandingPage.js +464 -568
  11. package/dist/browser/components/marketing/PricingClient.js +213 -839
  12. package/dist/browser/components/marketing/ProductClientPage.js +265 -463
  13. package/dist/browser/components/marketing/index.js +2007 -3338
  14. package/dist/browser/components/marketing/pricing-thinking-modal.js +12 -12
  15. package/dist/browser/components/marketing/sections/AudienceSection.js +2 -2
  16. package/dist/browser/components/marketing/sections/CorePositioningSection.js +2 -2
  17. package/dist/browser/components/marketing/sections/CtaSection.js +3 -3
  18. package/dist/browser/components/marketing/sections/FearsSection.js +3 -3
  19. package/dist/browser/components/marketing/sections/HeroMarketingSection.js +6 -6
  20. package/dist/browser/components/marketing/sections/IconGridSection.js +2 -2
  21. package/dist/browser/components/marketing/sections/OutputsSection.js +2 -2
  22. package/dist/browser/components/marketing/sections/ProblemSection.js +2 -2
  23. package/dist/browser/components/marketing/sections/SolutionSection.js +2 -2
  24. package/dist/browser/components/marketing/sections/StepsSection.js +4 -4
  25. package/dist/browser/components/marketing/studio-signup-section.js +25 -41
  26. package/dist/browser/components/templates/TemplatesClientPage.js +2374 -3571
  27. package/dist/browser/components/templates/TemplatesPage.js +1 -1
  28. package/dist/browser/components/templates/TemplatesPreviewModal.js +27 -3
  29. package/dist/browser/components/templates/index.js +2412 -3609
  30. package/dist/browser/index.js +2415 -3612
  31. package/dist/browser/libs/email/client.js +1 -1
  32. package/dist/browser/libs/email/contact.js +1 -1
  33. package/dist/browser/libs/email/newsletter.js +1 -1
  34. package/dist/browser/libs/email/waitlist-application.js +1 -1
  35. package/dist/browser/libs/email/waitlist.js +1 -1
  36. package/dist/browser/registry/engine.js +2003 -3334
  37. package/dist/browser/registry/index.js +2003 -3334
  38. package/dist/browser/registry/registry-docs.js +2 -2
  39. package/dist/browser/registry/registry-landing.js +2007 -3338
  40. package/dist/browser/registry/registry.js +2003 -3334
  41. package/dist/browser/registry/utils.js +2003 -3334
  42. package/dist/components/marketing/ChangelogPage.js +8 -8
  43. package/dist/components/marketing/CofounderPage.js +167 -523
  44. package/dist/components/marketing/ContactClient.js +200 -207
  45. package/dist/components/marketing/ContributePage.d.ts +0 -2
  46. package/dist/components/marketing/ContributePage.js +211 -463
  47. package/dist/components/marketing/DesignPartnerPage.js +165 -218
  48. package/dist/components/marketing/LandingPage.js +464 -568
  49. package/dist/components/marketing/PricingClient.js +213 -839
  50. package/dist/components/marketing/ProductClientPage.js +265 -463
  51. package/dist/components/marketing/index.d.ts +5 -5
  52. package/dist/components/marketing/index.js +2007 -3338
  53. package/dist/components/marketing/pricing-thinking-modal.js +12 -12
  54. package/dist/components/marketing/sections/AudienceSection.js +2 -2
  55. package/dist/components/marketing/sections/CorePositioningSection.js +2 -2
  56. package/dist/components/marketing/sections/CtaSection.js +3 -3
  57. package/dist/components/marketing/sections/FearsSection.js +3 -3
  58. package/dist/components/marketing/sections/HeroMarketingSection.js +6 -6
  59. package/dist/components/marketing/sections/IconGridSection.d.ts +3 -3
  60. package/dist/components/marketing/sections/IconGridSection.js +2 -2
  61. package/dist/components/marketing/sections/OutputsSection.js +2 -2
  62. package/dist/components/marketing/sections/ProblemSection.js +2 -2
  63. package/dist/components/marketing/sections/SolutionSection.js +2 -2
  64. package/dist/components/marketing/sections/StepsSection.js +4 -4
  65. package/dist/components/marketing/studio-signup-section.js +25 -41
  66. package/dist/components/templates/TemplatesClientPage.js +2374 -3571
  67. package/dist/components/templates/TemplatesPage.js +1 -1
  68. package/dist/components/templates/TemplatesPreviewModal.js +27 -3
  69. package/dist/components/templates/index.js +2412 -3609
  70. package/dist/index.js +2415 -3612
  71. package/dist/libs/email/client.js +1 -1
  72. package/dist/libs/email/contact.js +1 -1
  73. package/dist/libs/email/newsletter.js +1 -1
  74. package/dist/libs/email/waitlist-application.js +1 -1
  75. package/dist/libs/email/waitlist.js +1 -1
  76. package/dist/node/components/marketing/ChangelogPage.js +8 -8
  77. package/dist/node/components/marketing/CofounderPage.js +167 -523
  78. package/dist/node/components/marketing/ContactClient.js +200 -207
  79. package/dist/node/components/marketing/ContributePage.js +211 -463
  80. package/dist/node/components/marketing/DesignPartnerPage.js +165 -218
  81. package/dist/node/components/marketing/LandingPage.js +464 -568
  82. package/dist/node/components/marketing/PricingClient.js +213 -839
  83. package/dist/node/components/marketing/ProductClientPage.js +265 -463
  84. package/dist/node/components/marketing/index.js +2007 -3338
  85. package/dist/node/components/marketing/pricing-thinking-modal.js +12 -12
  86. package/dist/node/components/marketing/sections/AudienceSection.js +2 -2
  87. package/dist/node/components/marketing/sections/CorePositioningSection.js +2 -2
  88. package/dist/node/components/marketing/sections/CtaSection.js +3 -3
  89. package/dist/node/components/marketing/sections/FearsSection.js +3 -3
  90. package/dist/node/components/marketing/sections/HeroMarketingSection.js +6 -6
  91. package/dist/node/components/marketing/sections/IconGridSection.js +2 -2
  92. package/dist/node/components/marketing/sections/OutputsSection.js +2 -2
  93. package/dist/node/components/marketing/sections/ProblemSection.js +2 -2
  94. package/dist/node/components/marketing/sections/SolutionSection.js +2 -2
  95. package/dist/node/components/marketing/sections/StepsSection.js +4 -4
  96. package/dist/node/components/marketing/studio-signup-section.js +25 -41
  97. package/dist/node/components/templates/TemplatesClientPage.js +2374 -3571
  98. package/dist/node/components/templates/TemplatesPage.js +1 -1
  99. package/dist/node/components/templates/TemplatesPreviewModal.js +27 -3
  100. package/dist/node/components/templates/index.js +2412 -3609
  101. package/dist/node/index.js +2415 -3612
  102. package/dist/node/libs/email/client.js +1 -1
  103. package/dist/node/libs/email/contact.js +1 -1
  104. package/dist/node/libs/email/newsletter.js +1 -1
  105. package/dist/node/libs/email/waitlist-application.js +1 -1
  106. package/dist/node/libs/email/waitlist.js +1 -1
  107. package/dist/node/registry/engine.js +2003 -3334
  108. package/dist/node/registry/index.js +2003 -3334
  109. package/dist/node/registry/registry-docs.js +2 -2
  110. package/dist/node/registry/registry-landing.js +2007 -3338
  111. package/dist/node/registry/registry.js +2003 -3334
  112. package/dist/node/registry/utils.js +2003 -3334
  113. package/dist/registry/engine.js +2003 -3334
  114. package/dist/registry/index.js +2003 -3334
  115. package/dist/registry/registry-docs.js +2 -2
  116. package/dist/registry/registry-landing.js +2007 -3338
  117. package/dist/registry/registry.js +2003 -3334
  118. package/dist/registry/utils.js +2003 -3334
  119. package/package.json +31 -29
  120. package/src/bundles/MarketingBundle.ts +273 -273
  121. package/src/components/marketing/ChangelogPage.tsx +72 -100
  122. package/src/components/marketing/CofounderPage.tsx +120 -384
  123. package/src/components/marketing/ContactClient.tsx +164 -154
  124. package/src/components/marketing/ContributePage.tsx +139 -313
  125. package/src/components/marketing/DesignPartnerPage.tsx +133 -171
  126. package/src/components/marketing/LandingPage.tsx +353 -25
  127. package/src/components/marketing/PricingClient.tsx +192 -437
  128. package/src/components/marketing/ProductClientPage.tsx +255 -377
  129. package/src/components/marketing/index.ts +5 -5
  130. package/src/components/marketing/pricing-thinking-modal.tsx +197 -197
  131. package/src/components/marketing/sections/AudienceSection.tsx +55 -56
  132. package/src/components/marketing/sections/CorePositioningSection.tsx +37 -37
  133. package/src/components/marketing/sections/CtaSection.tsx +49 -50
  134. package/src/components/marketing/sections/DevelopersSection.tsx +26 -27
  135. package/src/components/marketing/sections/FearsSection.tsx +36 -37
  136. package/src/components/marketing/sections/HeroMarketingSection.tsx +59 -59
  137. package/src/components/marketing/sections/IconGridSection.tsx +71 -71
  138. package/src/components/marketing/sections/OutputsSection.tsx +51 -52
  139. package/src/components/marketing/sections/ProblemSection.tsx +39 -40
  140. package/src/components/marketing/sections/SolutionSection.tsx +39 -40
  141. package/src/components/marketing/sections/StepsSection.tsx +47 -48
  142. package/src/components/marketing/studio-signup-section.tsx +39 -41
  143. package/src/components/templates/TemplatesClientPage.tsx +763 -685
  144. package/src/components/templates/TemplatesPage.tsx +110 -110
  145. package/src/components/templates/TemplatesPreviewModal.tsx +251 -198
  146. package/src/index.ts +4 -4
  147. package/src/libs/email/client.test.ts +81 -81
  148. package/src/libs/email/client.ts +111 -111
  149. package/src/libs/email/contact.ts +35 -35
  150. package/src/libs/email/newsletter.ts +46 -46
  151. package/src/libs/email/types.ts +29 -29
  152. package/src/libs/email/utils.ts +5 -5
  153. package/src/libs/email/waitlist-application.ts +72 -72
  154. package/src/libs/email/waitlist.ts +46 -46
  155. package/src/libs/pricing-examples.ts +12 -12
  156. package/src/registry/engine.ts +16 -16
  157. package/src/registry/factory.ts +57 -57
  158. package/src/registry/registry-docs.ts +656 -666
  159. package/src/registry/registry-landing.ts +94 -95
  160. package/src/registry/registry.ts +36 -37
  161. package/src/registry/types.ts +2 -2
  162. package/src/registry/utils.ts +56 -56
  163. package/tsconfig.json +11 -11
  164. package/tsdown.config.js +5 -5
@@ -5,103 +5,103 @@ import { beforeEach, describe, expect, it } from 'bun:test';
5
5
  import { __internal, getEmailConfig, sendEmail } from './client';
6
6
 
7
7
  const ENV_KEYS = [
8
- 'SCALEWAY_ACCESS_KEY',
9
- 'SCALEWAY_SECRET_KEY',
10
- 'SCALEWAY_PROJECT_ID',
11
- 'SCALEWAY_ACCESS_KEY_QUEUE',
12
- 'SCALEWAY_SECRET_KEY_QUEUE',
13
- 'SCALEWAY_REGION',
14
- 'SCALEWAY_EMAIL_FROM_EMAIL',
15
- 'SCALEWAY_EMAIL_FROM_NAME',
16
- 'SCALEWAY_EMAIL_TEAM_EMAIL',
17
- 'SCALEWAY_EMAIL_TEAM_NAME',
8
+ 'SCALEWAY_ACCESS_KEY',
9
+ 'SCALEWAY_SECRET_KEY',
10
+ 'SCALEWAY_PROJECT_ID',
11
+ 'SCALEWAY_ACCESS_KEY_QUEUE',
12
+ 'SCALEWAY_SECRET_KEY_QUEUE',
13
+ 'SCALEWAY_REGION',
14
+ 'SCALEWAY_EMAIL_FROM_EMAIL',
15
+ 'SCALEWAY_EMAIL_FROM_NAME',
16
+ 'SCALEWAY_EMAIL_TEAM_EMAIL',
17
+ 'SCALEWAY_EMAIL_TEAM_NAME',
18
18
  ];
19
19
 
20
20
  const clearEnv = () => {
21
- ENV_KEYS.forEach((key) => {
22
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- Pragmatic use for environment cleanup in tests
23
- delete process.env[key];
24
- });
21
+ ENV_KEYS.forEach((key) => {
22
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- Pragmatic use for environment cleanup in tests
23
+ delete process.env[key];
24
+ });
25
25
  };
26
26
 
27
27
  const setEnv = (values: Partial<Record<string, string>>) => {
28
- Object.entries(values).forEach(([key, value]) => {
29
- process.env[key] = value;
30
- });
28
+ Object.entries(values).forEach(([key, value]) => {
29
+ process.env[key] = value;
30
+ });
31
31
  };
32
32
 
33
33
  describe('email client config', () => {
34
- beforeEach(() => {
35
- __internal.resetCaches();
36
- clearEnv();
37
- });
34
+ beforeEach(() => {
35
+ __internal.resetCaches();
36
+ clearEnv();
37
+ });
38
38
 
39
- it('returns a failure result when credentials are missing', () => {
40
- const result = getEmailConfig();
41
- expect(result.ok).toBeFalse();
42
- expect(result.config).toBeUndefined();
43
- });
39
+ it('returns a failure result when credentials are missing', () => {
40
+ const result = getEmailConfig();
41
+ expect(result.ok).toBeFalse();
42
+ expect(result.config).toBeUndefined();
43
+ });
44
44
 
45
- it('prefers Scaleway env variables for config and region mapping', () => {
46
- setEnv({
47
- SCALEWAY_ACCESS_KEY: 'access',
48
- SCALEWAY_SECRET_KEY: 'secret',
49
- SCALEWAY_PROJECT_ID: 'project-123',
50
- SCALEWAY_EMAIL_FROM_EMAIL: 'from@example.com',
51
- SCALEWAY_EMAIL_FROM_NAME: 'From Name',
52
- SCALEWAY_EMAIL_TEAM_EMAIL: 'team@example.com',
53
- SCALEWAY_EMAIL_TEAM_NAME: 'Team Name',
54
- });
45
+ it('prefers Scaleway env variables for config and region mapping', () => {
46
+ setEnv({
47
+ SCALEWAY_ACCESS_KEY: 'access',
48
+ SCALEWAY_SECRET_KEY: 'secret',
49
+ SCALEWAY_PROJECT_ID: 'project-123',
50
+ SCALEWAY_EMAIL_FROM_EMAIL: 'from@example.com',
51
+ SCALEWAY_EMAIL_FROM_NAME: 'From Name',
52
+ SCALEWAY_EMAIL_TEAM_EMAIL: 'team@example.com',
53
+ SCALEWAY_EMAIL_TEAM_NAME: 'Team Name',
54
+ });
55
55
 
56
- const result = getEmailConfig();
57
- expect(result.ok).toBeTrue();
58
- expect(result.config?.accessKey).toBe('access');
59
- expect(result.config?.secretKey).toBe('secret');
60
- expect(result.config?.projectId).toBe('project-123');
61
- expect(result.config?.region).toBe('fr-par');
62
- expect(result.config?.from.email).toBe('from@example.com');
63
- expect(result.config?.from.name).toBe('From Name');
64
- expect(result.config?.teamInbox.email).toBe('team@example.com');
65
- expect(result.config?.teamInbox.name).toBe('Team Name');
66
- });
56
+ const result = getEmailConfig();
57
+ expect(result.ok).toBeTrue();
58
+ expect(result.config?.accessKey).toBe('access');
59
+ expect(result.config?.secretKey).toBe('secret');
60
+ expect(result.config?.projectId).toBe('project-123');
61
+ expect(result.config?.region).toBe('fr-par');
62
+ expect(result.config?.from.email).toBe('from@example.com');
63
+ expect(result.config?.from.name).toBe('From Name');
64
+ expect(result.config?.teamInbox.email).toBe('team@example.com');
65
+ expect(result.config?.teamInbox.name).toBe('Team Name');
66
+ });
67
67
 
68
- it('uses provided API factory to send email with reply-to header', async () => {
69
- setEnv({
70
- SCALEWAY_ACCESS_KEY: 'access',
71
- SCALEWAY_SECRET_KEY: 'secret',
72
- SCALEWAY_PROJECT_ID: 'project-123',
73
- });
68
+ it('uses provided API factory to send email with reply-to header', async () => {
69
+ setEnv({
70
+ SCALEWAY_ACCESS_KEY: 'access',
71
+ SCALEWAY_SECRET_KEY: 'secret',
72
+ SCALEWAY_PROJECT_ID: 'project-123',
73
+ });
74
74
 
75
- let captured: unknown = null;
75
+ let captured: unknown = null;
76
76
 
77
- __internal.setClient({
78
- async createEmail(request: unknown) {
79
- captured = request;
80
- return { emails: [] };
81
- },
82
- });
77
+ __internal.setClient({
78
+ async createEmail(request: unknown) {
79
+ captured = request;
80
+ return { emails: [] };
81
+ },
82
+ });
83
83
 
84
- const config = getEmailConfig().config!;
84
+ const config = getEmailConfig().config!;
85
85
 
86
- const response = await sendEmail(config, {
87
- to: [{ email: 'user@example.com' }],
88
- subject: 'Subject',
89
- text: 'Plain text',
90
- html: '<p>html</p>',
91
- replyTo: 'reply@example.com',
92
- context: 'test',
93
- });
86
+ const response = await sendEmail(config, {
87
+ to: [{ email: 'user@example.com' }],
88
+ subject: 'Subject',
89
+ text: 'Plain text',
90
+ html: '<p>html</p>',
91
+ replyTo: 'reply@example.com',
92
+ context: 'test',
93
+ });
94
94
 
95
- expect(response.success).toBeTrue();
96
- const createEmailRequest = captured as Record<string, unknown>;
97
- expect(createEmailRequest.region).toBe(config.region);
98
- expect(createEmailRequest.projectId).toBe(config.projectId);
99
- expect(createEmailRequest.from).toStrictEqual(config.from);
100
- expect(createEmailRequest.to).toStrictEqual([
101
- { email: 'user@example.com' },
102
- ]);
103
- expect(createEmailRequest.additionalHeaders).toStrictEqual([
104
- { key: 'Reply-To', value: 'reply@example.com' },
105
- ]);
106
- });
95
+ expect(response.success).toBeTrue();
96
+ const createEmailRequest = captured as Record<string, unknown>;
97
+ expect(createEmailRequest.region).toBe(config.region);
98
+ expect(createEmailRequest.projectId).toBe(config.projectId);
99
+ expect(createEmailRequest.from).toStrictEqual(config.from);
100
+ expect(createEmailRequest.to).toStrictEqual([
101
+ { email: 'user@example.com' },
102
+ ]);
103
+ expect(createEmailRequest.additionalHeaders).toStrictEqual([
104
+ { key: 'Reply-To', value: 'reply@example.com' },
105
+ ]);
106
+ });
107
107
  });
@@ -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
- EmailAddress,
6
- EmailConfigResult,
7
- EmailSendOutcome,
8
- EmailServiceConfig,
9
- SendEmailRequest,
5
+ EmailAddress,
6
+ EmailConfigResult,
7
+ EmailSendOutcome,
8
+ EmailServiceConfig,
9
+ SendEmailRequest,
10
10
  } from './types';
11
11
 
12
12
  const DEFAULT_FROM: EmailAddress = {
13
- email: 'noreply@transactional.contractspec.io',
14
- name: 'ContractSpec',
13
+ email: 'noreply@transactional.contractspec.io',
14
+ name: 'ContractSpec',
15
15
  };
16
16
 
17
17
  const DEFAULT_TEAM_INBOX: EmailAddress = {
18
- email: 'contact@contractspec.io',
19
- name: 'ContractSpec Team',
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
- client
29
+ client
30
30
  ) => new Temv1alpha1.API(client);
31
31
 
32
32
  const mapRegion = (value?: string | null): Region => {
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;
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
- 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 };
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
- 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;
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
- config: EmailServiceConfig,
99
- request: SendEmailRequest
98
+ config: EmailServiceConfig,
99
+ request: SendEmailRequest
100
100
  ): Promise<EmailSendOutcome> => {
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
- }
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
- 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
- },
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
- 'Email service is not configured. Please contact us directly at contact@contractspec.io.';
8
+ 'Email service is not configured. Please contact us directly at contact@contractspec.io.';
9
9
  const CONTACT_SEND_ERROR =
10
- 'Failed to send message. Please contact us directly at contact@contractspec.io.';
10
+ 'Failed to send message. Please contact us directly at contact@contractspec.io.';
11
11
 
12
12
  export const submitContactForm = async (
13
- formData: FormData
13
+ formData: FormData
14
14
  ): Promise<SubmitContactFormResult> => {
15
- const name = (formData.get('name') ?? '').toString().trim();
16
- const email = (formData.get('email') ?? '').toString().trim();
17
- const message = (formData.get('message') ?? '').toString().trim();
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
- if (!email) {
20
- return {
21
- success: false,
22
- text: 'Please fill in all required fields.',
23
- };
24
- }
19
+ if (!email) {
20
+ return {
21
+ success: false,
22
+ text: 'Please fill in all required fields.',
23
+ };
24
+ }
25
25
 
26
- const configResult = getEmailConfig();
27
- if (!configResult.ok || !configResult.config) {
28
- return {
29
- success: false,
30
- text: configResult.errorMessage ?? CONTACT_MISSING_CONFIG,
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
- const senderName = name || email;
35
- const emailContentText = `
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
- const emailContentHtml = `
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
- 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
- });
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
- if (!sendResult.success) {
76
- return { success: false, text: CONTACT_SEND_ERROR };
77
- }
75
+ if (!sendResult.success) {
76
+ return { success: false, text: CONTACT_SEND_ERROR };
77
+ }
78
78
 
79
- return { success: true, text: 'Message sent successfully!' };
79
+ return { success: true, text: 'Message sent successfully!' };
80
80
  };