@contractspec/bundle.marketing 1.12.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.
Files changed (216) hide show
  1. package/.turbo/turbo-build$colon$types.log +1 -0
  2. package/.turbo/turbo-build.log +175 -0
  3. package/.turbo/turbo-lint.log +3 -0
  4. package/AGENTS.md +36 -0
  5. package/CHANGELOG.md +416 -0
  6. package/README.md +57 -0
  7. package/dist/components/marketing/ChangelogPage.d.ts +21 -0
  8. package/dist/components/marketing/ChangelogPage.d.ts.map +1 -0
  9. package/dist/components/marketing/ChangelogPage.js +65 -0
  10. package/dist/components/marketing/ChangelogPage.js.map +1 -0
  11. package/dist/components/marketing/CofounderPage.d.ts +7 -0
  12. package/dist/components/marketing/CofounderPage.d.ts.map +1 -0
  13. package/dist/components/marketing/CofounderPage.js +468 -0
  14. package/dist/components/marketing/CofounderPage.js.map +1 -0
  15. package/dist/components/marketing/ContactClient.d.ts +7 -0
  16. package/dist/components/marketing/ContactClient.d.ts.map +1 -0
  17. package/dist/components/marketing/ContactClient.js +158 -0
  18. package/dist/components/marketing/ContactClient.js.map +1 -0
  19. package/dist/components/marketing/ContributePage.d.ts +9 -0
  20. package/dist/components/marketing/ContributePage.d.ts.map +1 -0
  21. package/dist/components/marketing/ContributePage.js +362 -0
  22. package/dist/components/marketing/ContributePage.js.map +1 -0
  23. package/dist/components/marketing/DesignPartnerPage.d.ts +9 -0
  24. package/dist/components/marketing/DesignPartnerPage.d.ts.map +1 -0
  25. package/dist/components/marketing/DesignPartnerPage.js +215 -0
  26. package/dist/components/marketing/DesignPartnerPage.js.map +1 -0
  27. package/dist/components/marketing/LandingPage.d.ts +7 -0
  28. package/dist/components/marketing/LandingPage.d.ts.map +1 -0
  29. package/dist/components/marketing/LandingPage.js +38 -0
  30. package/dist/components/marketing/LandingPage.js.map +1 -0
  31. package/dist/components/marketing/PricingClient.d.ts +7 -0
  32. package/dist/components/marketing/PricingClient.d.ts.map +1 -0
  33. package/dist/components/marketing/PricingClient.js +521 -0
  34. package/dist/components/marketing/PricingClient.js.map +1 -0
  35. package/dist/components/marketing/ProductClientPage.d.ts +7 -0
  36. package/dist/components/marketing/ProductClientPage.d.ts.map +1 -0
  37. package/dist/components/marketing/ProductClientPage.js +460 -0
  38. package/dist/components/marketing/ProductClientPage.js.map +1 -0
  39. package/dist/components/marketing/index.d.ts +11 -0
  40. package/dist/components/marketing/index.js +12 -0
  41. package/dist/components/marketing/pricing-thinking-modal.d.ts +16 -0
  42. package/dist/components/marketing/pricing-thinking-modal.d.ts.map +1 -0
  43. package/dist/components/marketing/pricing-thinking-modal.js +202 -0
  44. package/dist/components/marketing/pricing-thinking-modal.js.map +1 -0
  45. package/dist/components/marketing/sections/AudienceSection.d.ts +7 -0
  46. package/dist/components/marketing/sections/AudienceSection.d.ts.map +1 -0
  47. package/dist/components/marketing/sections/AudienceSection.js +68 -0
  48. package/dist/components/marketing/sections/AudienceSection.js.map +1 -0
  49. package/dist/components/marketing/sections/CorePositioningSection.d.ts +7 -0
  50. package/dist/components/marketing/sections/CorePositioningSection.d.ts.map +1 -0
  51. package/dist/components/marketing/sections/CorePositioningSection.js +59 -0
  52. package/dist/components/marketing/sections/CorePositioningSection.js.map +1 -0
  53. package/dist/components/marketing/sections/CtaSection.d.ts +7 -0
  54. package/dist/components/marketing/sections/CtaSection.d.ts.map +1 -0
  55. package/dist/components/marketing/sections/CtaSection.js +54 -0
  56. package/dist/components/marketing/sections/CtaSection.js.map +1 -0
  57. package/dist/components/marketing/sections/DevelopersSection.d.ts +7 -0
  58. package/dist/components/marketing/sections/DevelopersSection.d.ts.map +1 -0
  59. package/dist/components/marketing/sections/DevelopersSection.js +45 -0
  60. package/dist/components/marketing/sections/DevelopersSection.js.map +1 -0
  61. package/dist/components/marketing/sections/FearsSection.d.ts +7 -0
  62. package/dist/components/marketing/sections/FearsSection.d.ts.map +1 -0
  63. package/dist/components/marketing/sections/FearsSection.js +48 -0
  64. package/dist/components/marketing/sections/FearsSection.js.map +1 -0
  65. package/dist/components/marketing/sections/HeroMarketingSection.d.ts +7 -0
  66. package/dist/components/marketing/sections/HeroMarketingSection.d.ts.map +1 -0
  67. package/dist/components/marketing/sections/HeroMarketingSection.js +77 -0
  68. package/dist/components/marketing/sections/HeroMarketingSection.js.map +1 -0
  69. package/dist/components/marketing/sections/IconGridSection.d.ts +45 -0
  70. package/dist/components/marketing/sections/IconGridSection.d.ts.map +1 -0
  71. package/dist/components/marketing/sections/IconGridSection.js +44 -0
  72. package/dist/components/marketing/sections/IconGridSection.js.map +1 -0
  73. package/dist/components/marketing/sections/OutputsSection.d.ts +7 -0
  74. package/dist/components/marketing/sections/OutputsSection.d.ts.map +1 -0
  75. package/dist/components/marketing/sections/OutputsSection.js +59 -0
  76. package/dist/components/marketing/sections/OutputsSection.js.map +1 -0
  77. package/dist/components/marketing/sections/ProblemSection.d.ts +7 -0
  78. package/dist/components/marketing/sections/ProblemSection.d.ts.map +1 -0
  79. package/dist/components/marketing/sections/ProblemSection.js +46 -0
  80. package/dist/components/marketing/sections/ProblemSection.js.map +1 -0
  81. package/dist/components/marketing/sections/SolutionSection.d.ts +7 -0
  82. package/dist/components/marketing/sections/SolutionSection.d.ts.map +1 -0
  83. package/dist/components/marketing/sections/SolutionSection.js +46 -0
  84. package/dist/components/marketing/sections/SolutionSection.js.map +1 -0
  85. package/dist/components/marketing/sections/StepsSection.d.ts +7 -0
  86. package/dist/components/marketing/sections/StepsSection.d.ts.map +1 -0
  87. package/dist/components/marketing/sections/StepsSection.js +52 -0
  88. package/dist/components/marketing/sections/StepsSection.js.map +1 -0
  89. package/dist/components/marketing/waitlist-section.d.ts +15 -0
  90. package/dist/components/marketing/waitlist-section.d.ts.map +1 -0
  91. package/dist/components/marketing/waitlist-section.js +578 -0
  92. package/dist/components/marketing/waitlist-section.js.map +1 -0
  93. package/dist/components/templates/TemplatesClientPage.d.ts +7 -0
  94. package/dist/components/templates/TemplatesClientPage.d.ts.map +1 -0
  95. package/dist/components/templates/TemplatesClientPage.js +625 -0
  96. package/dist/components/templates/TemplatesClientPage.js.map +1 -0
  97. package/dist/components/templates/TemplatesPage.d.ts +7 -0
  98. package/dist/components/templates/TemplatesPage.d.ts.map +1 -0
  99. package/dist/components/templates/TemplatesPage.js +125 -0
  100. package/dist/components/templates/TemplatesPage.js.map +1 -0
  101. package/dist/components/templates/TemplatesPreviewModal.d.ts +15 -0
  102. package/dist/components/templates/TemplatesPreviewModal.d.ts.map +1 -0
  103. package/dist/components/templates/TemplatesPreviewModal.js +137 -0
  104. package/dist/components/templates/TemplatesPreviewModal.js.map +1 -0
  105. package/dist/components/templates/index.d.ts +4 -0
  106. package/dist/components/templates/index.js +5 -0
  107. package/dist/index.d.ts +29 -0
  108. package/dist/index.js +28 -0
  109. package/dist/libs/email/client.d.ts +15 -0
  110. package/dist/libs/email/client.d.ts.map +1 -0
  111. package/dist/libs/email/client.js +113 -0
  112. package/dist/libs/email/client.js.map +1 -0
  113. package/dist/libs/email/contact.d.ts +7 -0
  114. package/dist/libs/email/contact.d.ts.map +1 -0
  115. package/dist/libs/email/contact.js +71 -0
  116. package/dist/libs/email/contact.js.map +1 -0
  117. package/dist/libs/email/newsletter.d.ts +7 -0
  118. package/dist/libs/email/newsletter.d.ts.map +1 -0
  119. package/dist/libs/email/newsletter.js +95 -0
  120. package/dist/libs/email/newsletter.js.map +1 -0
  121. package/dist/libs/email/types.d.ts +53 -0
  122. package/dist/libs/email/types.d.ts.map +1 -0
  123. package/dist/libs/email/types.js +1 -0
  124. package/dist/libs/email/utils.d.ts +6 -0
  125. package/dist/libs/email/utils.d.ts.map +1 -0
  126. package/dist/libs/email/utils.js +7 -0
  127. package/dist/libs/email/utils.js.map +1 -0
  128. package/dist/libs/email/waitlist-application.d.ts +7 -0
  129. package/dist/libs/email/waitlist-application.d.ts.map +1 -0
  130. package/dist/libs/email/waitlist-application.js +170 -0
  131. package/dist/libs/email/waitlist-application.js.map +1 -0
  132. package/dist/libs/email/waitlist.d.ts +7 -0
  133. package/dist/libs/email/waitlist.d.ts.map +1 -0
  134. package/dist/libs/email/waitlist.js +105 -0
  135. package/dist/libs/email/waitlist.js.map +1 -0
  136. package/dist/libs/pricing-examples.d.ts +22 -0
  137. package/dist/libs/pricing-examples.d.ts.map +1 -0
  138. package/dist/libs/pricing-examples.js +21 -0
  139. package/dist/libs/pricing-examples.js.map +1 -0
  140. package/dist/registry/engine.d.ts +17 -0
  141. package/dist/registry/engine.d.ts.map +1 -0
  142. package/dist/registry/engine.js +24 -0
  143. package/dist/registry/engine.js.map +1 -0
  144. package/dist/registry/factory.d.ts +64 -0
  145. package/dist/registry/factory.d.ts.map +1 -0
  146. package/dist/registry/factory.js +61 -0
  147. package/dist/registry/factory.js.map +1 -0
  148. package/dist/registry/index.d.ts +8 -0
  149. package/dist/registry/index.js +8 -0
  150. package/dist/registry/registry-docs.d.ts +15 -0
  151. package/dist/registry/registry-docs.d.ts.map +1 -0
  152. package/dist/registry/registry-docs.js +305 -0
  153. package/dist/registry/registry-docs.js.map +1 -0
  154. package/dist/registry/registry-landing.d.ts +19 -0
  155. package/dist/registry/registry-landing.d.ts.map +1 -0
  156. package/dist/registry/registry-landing.js +95 -0
  157. package/dist/registry/registry-landing.js.map +1 -0
  158. package/dist/registry/registry.d.ts +30 -0
  159. package/dist/registry/registry.d.ts.map +1 -0
  160. package/dist/registry/registry.js +61 -0
  161. package/dist/registry/registry.js.map +1 -0
  162. package/dist/registry/types.d.ts +19 -0
  163. package/dist/registry/types.d.ts.map +1 -0
  164. package/dist/registry/types.js +0 -0
  165. package/dist/registry/utils.d.ts +31 -0
  166. package/dist/registry/utils.d.ts.map +1 -0
  167. package/dist/registry/utils.js +54 -0
  168. package/dist/registry/utils.js.map +1 -0
  169. package/package.json +151 -0
  170. package/src/components/marketing/ChangelogPage.tsx +110 -0
  171. package/src/components/marketing/CofounderPage.tsx +409 -0
  172. package/src/components/marketing/ContactClient.tsx +174 -0
  173. package/src/components/marketing/ContributePage.tsx +319 -0
  174. package/src/components/marketing/DesignPartnerPage.tsx +181 -0
  175. package/src/components/marketing/LandingPage.tsx +30 -0
  176. package/src/components/marketing/PricingClient.tsx +446 -0
  177. package/src/components/marketing/ProductClientPage.tsx +391 -0
  178. package/src/components/marketing/index.ts +10 -0
  179. package/src/components/marketing/pricing-thinking-modal.tsx +224 -0
  180. package/src/components/marketing/sections/AudienceSection.tsx +66 -0
  181. package/src/components/marketing/sections/CorePositioningSection.tsx +44 -0
  182. package/src/components/marketing/sections/CtaSection.tsx +57 -0
  183. package/src/components/marketing/sections/DevelopersSection.tsx +38 -0
  184. package/src/components/marketing/sections/FearsSection.tsx +45 -0
  185. package/src/components/marketing/sections/HeroMarketingSection.tsx +73 -0
  186. package/src/components/marketing/sections/IconGridSection.tsx +91 -0
  187. package/src/components/marketing/sections/OutputsSection.tsx +59 -0
  188. package/src/components/marketing/sections/ProblemSection.tsx +47 -0
  189. package/src/components/marketing/sections/SolutionSection.tsx +47 -0
  190. package/src/components/marketing/sections/StepsSection.tsx +55 -0
  191. package/src/components/marketing/waitlist-section.tsx +606 -0
  192. package/src/components/templates/TemplatesClientPage.tsx +711 -0
  193. package/src/components/templates/TemplatesPage.tsx +129 -0
  194. package/src/components/templates/TemplatesPreviewModal.tsx +260 -0
  195. package/src/components/templates/index.ts +3 -0
  196. package/src/index.ts +15 -0
  197. package/src/libs/email/client.test.ts +107 -0
  198. package/src/libs/email/client.ts +146 -0
  199. package/src/libs/email/contact.ts +80 -0
  200. package/src/libs/email/newsletter.ts +108 -0
  201. package/src/libs/email/types.ts +59 -0
  202. package/src/libs/email/utils.ts +8 -0
  203. package/src/libs/email/waitlist-application.ts +192 -0
  204. package/src/libs/email/waitlist.ts +118 -0
  205. package/src/libs/pricing-examples.ts +19 -0
  206. package/src/registry/engine.ts +38 -0
  207. package/src/registry/factory.ts +110 -0
  208. package/src/registry/index.ts +7 -0
  209. package/src/registry/registry-docs.ts +843 -0
  210. package/src/registry/registry-landing.ts +118 -0
  211. package/src/registry/registry.ts +85 -0
  212. package/src/registry/types.ts +17 -0
  213. package/src/registry/utils.ts +99 -0
  214. package/tsconfig.json +13 -0
  215. package/tsconfig.tsbuildinfo +1 -0
  216. package/tsdown.config.js +10 -0
@@ -0,0 +1,80 @@
1
+ 'use server';
2
+
3
+ import { getEmailConfig, sendEmail } from './client';
4
+ import { escapeHtml, formatMultilineHtml } from './utils';
5
+ import type { SubmitContactFormResult } from './types';
6
+
7
+ const CONTACT_MISSING_CONFIG =
8
+ 'Email service is not configured. Please contact us directly at contact@contractspec.io.';
9
+ const CONTACT_SEND_ERROR =
10
+ 'Failed to send message. Please contact us directly at contact@contractspec.io.';
11
+
12
+ export const submitContactForm = async (
13
+ formData: FormData
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();
18
+
19
+ if (!email) {
20
+ return {
21
+ success: false,
22
+ text: 'Please fill in all required fields.',
23
+ };
24
+ }
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
+ }
33
+
34
+ const senderName = name || email;
35
+ const emailContentText = `
36
+ New contact form submission from ${senderName}
37
+
38
+ Contact Information:
39
+ - Name: ${name || 'Not provided'}
40
+ - Email: ${email}
41
+
42
+ Message:
43
+ ${message || 'No message provided'}
44
+
45
+ ---
46
+ Submitted via ContractSpec contact form
47
+ `.trim();
48
+
49
+ const emailContentHtml = `
50
+ <div style="font-family: sans-serif; max-width: 640px; margin: 0 auto;">
51
+ <h1 style="color: #8b5cf6; margin-bottom: 12px;">New contact form submission</h1>
52
+ <p style="margin: 0 0 12px;">From ${escapeHtml(senderName)}</p>
53
+ <h2 style="color: #8b5cf6; margin: 16px 0 8px;">Contact Information</h2>
54
+ <ul style="padding-left: 16px; line-height: 1.6; margin: 0 0 16px;">
55
+ <li>Name: ${escapeHtml(name || 'Not provided')}</li>
56
+ <li>Email: ${escapeHtml(email)}</li>
57
+ </ul>
58
+ <h2 style="color: #8b5cf6; margin: 16px 0 8px;">Message</h2>
59
+ <div style="border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px; background: #f9fafb; white-space: pre-wrap; line-height: 1.6;">
60
+ ${message ? formatMultilineHtml(message) : 'No message provided'}
61
+ </div>
62
+ <p style="color: #6b7280; font-size: 12px; margin-top: 20px;">Submitted via ContractSpec contact form</p>
63
+ </div>
64
+ `;
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
+ });
74
+
75
+ if (!sendResult.success) {
76
+ return { success: false, text: CONTACT_SEND_ERROR };
77
+ }
78
+
79
+ return { success: true, text: 'Message sent successfully!' };
80
+ };
@@ -0,0 +1,108 @@
1
+ 'use server';
2
+
3
+ import { getEmailConfig, sendEmail } from './client';
4
+ import { formatMultilineHtml } from './utils';
5
+ import type { SubmitNewsletterResult } from './types';
6
+
7
+ const NEWSLETTER_MISSING_CONFIG =
8
+ 'Newsletter service is not configured. Please try again later.';
9
+ const NEWSLETTER_SEND_ERROR =
10
+ 'Failed to subscribe. Please try again later or contact us directly.';
11
+
12
+ export const subscribeToNewsletter = async (
13
+ formData: FormData
14
+ ): Promise<SubmitNewsletterResult> => {
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
+ Welcome to ContractSpec!
34
+
35
+ Thanks for subscribing to our newsletter. You'll receive updates on:
36
+
37
+ • New integrations (Stripe, OpenAI, Qdrant, and more)
38
+ • Product features and improvements
39
+ • New templates and examples
40
+ • Architecture insights and best practices
41
+ • Documentation updates
42
+
43
+ Stay tuned for exciting updates!
44
+
45
+ ---
46
+ ContractSpec Team
47
+ https://contractspec.io
48
+
49
+ Unsubscribe: Reply to this email with "unsubscribe"
50
+ `.trim();
51
+
52
+ const welcomeHtml = `
53
+ <div style="font-family: sans-serif; max-width: 640px; margin: 0 auto;">
54
+ <h1 style="color: #8b5cf6;">Welcome to ContractSpec!</h1>
55
+ <p>Thanks for subscribing to our newsletter. You'll receive updates on:</p>
56
+ <ul style="line-height: 1.8;">
57
+ <li>New integrations (Stripe, OpenAI, Qdrant, and more)</li>
58
+ <li>Product features and improvements</li>
59
+ <li>New templates and examples</li>
60
+ <li>Architecture insights and best practices</li>
61
+ <li>Documentation updates</li>
62
+ </ul>
63
+ <p>Stay tuned for exciting updates!</p>
64
+ <hr style="margin: 30px 0; border: none; border-top: 1px solid #e5e7eb;" />
65
+ <p style="color: #6b7280; font-size: 14px;">
66
+ ContractSpec Team<br />
67
+ <a href="https://contractspec.io" style="color: #8b5cf6;">contractspec.io</a>
68
+ </p>
69
+ <p style="color: #9ca3af; font-size: 12px;">
70
+ To unsubscribe, reply to this email with "unsubscribe"
71
+ </p>
72
+ </div>
73
+ `;
74
+
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
+
83
+ if (!userSend.success) {
84
+ return { success: false, text: NEWSLETTER_SEND_ERROR };
85
+ }
86
+
87
+ const teamNotificationText = `New newsletter subscription from: ${email}`;
88
+ const teamNotificationHtml = `
89
+ <div style="font-family: sans-serif; max-width: 640px; margin: 0 auto;">
90
+ <p style="margin: 0 0 8px;">New newsletter subscription</p>
91
+ <p style="margin: 0;"><strong>Email:</strong> ${formatMultilineHtml(email)}</p>
92
+ </div>
93
+ `;
94
+
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
+
103
+ if (!teamSend.success) {
104
+ return { success: false, text: NEWSLETTER_SEND_ERROR };
105
+ }
106
+
107
+ return { success: true, text: 'Successfully subscribed!' };
108
+ };
@@ -0,0 +1,59 @@
1
+ 'use server';
2
+
3
+ import type { Region } from '@scaleway/sdk-client';
4
+
5
+ export interface SubmitContactFormResult {
6
+ success: boolean;
7
+ text: string;
8
+ }
9
+
10
+ export interface SubmitNewsletterResult {
11
+ success: boolean;
12
+ text: string;
13
+ }
14
+
15
+ export interface SubmitWaitlistResult {
16
+ success: boolean;
17
+ text: string;
18
+ }
19
+
20
+ export interface SubmitWaitlistApplicationResult {
21
+ success: boolean;
22
+ text: string;
23
+ }
24
+
25
+ export interface EmailAddress {
26
+ email: string;
27
+ name?: string;
28
+ }
29
+
30
+ export interface EmailServiceConfig {
31
+ accessKey: string;
32
+ secretKey: string;
33
+ projectId: string;
34
+ region: Region;
35
+ defaultZone: string;
36
+ from: EmailAddress;
37
+ teamInbox: EmailAddress;
38
+ }
39
+
40
+ export interface EmailConfigResult {
41
+ ok: boolean;
42
+ config?: EmailServiceConfig;
43
+ errorMessage?: string;
44
+ }
45
+
46
+ export interface SendEmailRequest {
47
+ to: EmailAddress[];
48
+ subject: string;
49
+ text: string;
50
+ html: string;
51
+ replyTo?: string;
52
+ context?: string;
53
+ }
54
+
55
+ export interface EmailSendOutcome {
56
+ success: boolean;
57
+ error?: unknown;
58
+ errorMessage?: string;
59
+ }
@@ -0,0 +1,8 @@
1
+ export const escapeHtml = (value: string): string =>
2
+ value
3
+ .replaceAll('&', '&amp;')
4
+ .replaceAll('<', '&lt;')
5
+ .replaceAll('>', '&gt;');
6
+
7
+ export const formatMultilineHtml = (value: string): string =>
8
+ escapeHtml(value).replaceAll('\n', '<br />');
@@ -0,0 +1,192 @@
1
+ 'use server';
2
+
3
+ import { getEmailConfig, sendEmail } from './client';
4
+ import { escapeHtml, formatMultilineHtml } from './utils';
5
+ import type { SubmitWaitlistApplicationResult } from './types';
6
+
7
+ const APPLICATION_MISSING_CONFIG =
8
+ 'Waitlist application service is not configured. Please try again later.';
9
+ const APPLICATION_SEND_ERROR =
10
+ 'Failed to submit application. Please try again later or contact us directly.';
11
+
12
+ export const submitWaitlistApplication = async (
13
+ formData: FormData
14
+ ): Promise<SubmitWaitlistApplicationResult> => {
15
+ const email = (formData.get('email') ?? '').toString().trim();
16
+ const name = (formData.get('name') ?? '').toString().trim();
17
+ const company = (formData.get('company') ?? '').toString().trim();
18
+ const role = (formData.get('role') ?? '').toString().trim();
19
+ const useCase = (formData.get('useCase') ?? '').toString().trim();
20
+ const currentStack = (formData.get('currentStack') ?? '').toString().trim();
21
+ const whatBuilding = (formData.get('whatBuilding') ?? '').toString().trim();
22
+ const whatSolving = (formData.get('whatSolving') ?? '').toString().trim();
23
+ const teamSize = (formData.get('teamSize') ?? '').toString().trim();
24
+ const timeline = (formData.get('timeline') ?? '').toString().trim();
25
+
26
+ const openToSessions = formData.get('openToSessions') === 'on';
27
+ const okayWithCaseStudies = formData.get('okayWithCaseStudies') === 'on';
28
+
29
+ if (!email || !email.includes('@')) {
30
+ return {
31
+ success: false,
32
+ text: 'Please enter a valid email address.',
33
+ };
34
+ }
35
+
36
+ if (!name || !whatBuilding || !whatSolving) {
37
+ return {
38
+ success: false,
39
+ text: 'Please fill in all required fields.',
40
+ };
41
+ }
42
+
43
+ const configResult = getEmailConfig();
44
+ if (!configResult.ok || !configResult.config) {
45
+ return {
46
+ success: false,
47
+ text: configResult.errorMessage ?? APPLICATION_MISSING_CONFIG,
48
+ };
49
+ }
50
+
51
+ const applicantText = `
52
+ You're on the list.
53
+
54
+ Thanks for applying to the ContractSpec design partner program. We're slowly onboarding design partners in waves. If your use case is a good fit, we'll reach out personally.
55
+
56
+ What happens next:
57
+ • We review applications weekly
58
+ • If selected, we'll reach out via email to schedule an intro call
59
+ • During early access, you'll get hands-on support and influence over the roadmap
60
+
61
+ In the meantime:
62
+ • Check out our docs: https://contractspec.io/docs
63
+ • Book a demo call: https://contractspec.io/contact
64
+
65
+ We're excited about the possibility of working together!
66
+
67
+ ---
68
+ ContractSpec Team
69
+ https://contractspec.io
70
+ `.trim();
71
+
72
+ const applicantHtml = `
73
+ <div style="font-family: sans-serif; max-width: 640px; margin: 0 auto;">
74
+ <h1 style="color: #8b5cf6;">You're on the list.</h1>
75
+ <p>Thanks for applying to the ContractSpec design partner program. We're slowly onboarding design partners in waves. If your use case is a good fit, we'll reach out personally.</p>
76
+ <h2 style="color: #8b5cf6; margin-top: 24px;">What happens next:</h2>
77
+ <ul style="line-height: 1.8;">
78
+ <li>We review applications weekly</li>
79
+ <li>If selected, we'll reach out via email to schedule an intro call</li>
80
+ <li>During early access, you'll get hands-on support and influence over the roadmap</li>
81
+ </ul>
82
+ <h2 style="color: #8b5cf6; margin-top: 24px;">In the meantime:</h2>
83
+ <ul style="line-height: 1.8;">
84
+ <li>Check out our <a href="https://contractspec.io/docs" style="color: #8b5cf6;">docs</a></li>
85
+ <li>Book a demo call: <a href="https://contractspec.io/contact" style="color: #8b5cf6;">contractspec.io/contact</a></li>
86
+ </ul>
87
+ <p>We're excited about the possibility of working together!</p>
88
+ <hr style="margin: 30px 0; border: none; border-top: 1px solid #e5e7eb;" />
89
+ <p style="color: #6b7280; font-size: 14px;">
90
+ ContractSpec Team<br />
91
+ <a href="https://contractspec.io" style="color: #8b5cf6;">contractspec.io</a>
92
+ </p>
93
+ </div>
94
+ `;
95
+
96
+ const applicantSend = await sendEmail(configResult.config, {
97
+ to: [{ email }],
98
+ subject: "You're on the ContractSpec design partner waitlist!",
99
+ text: applicantText,
100
+ html: applicantHtml,
101
+ context: 'waitlist-application-welcome',
102
+ });
103
+
104
+ if (!applicantSend.success) {
105
+ return { success: false, text: APPLICATION_SEND_ERROR };
106
+ }
107
+
108
+ const preferencesText = `
109
+ Open to 1:1 product/design sessions: ${openToSessions ? 'Yes' : 'No'}
110
+ Okay with anonymized case studies: ${okayWithCaseStudies ? 'Yes' : 'No'}
111
+ `.trim();
112
+
113
+ const teamEmailText = `
114
+ New Design Partner Waitlist Application
115
+
116
+ Contact Information:
117
+ - Name: ${name}
118
+ - Email: ${email}
119
+ ${company ? `- Company/Project: ${company}` : ''}
120
+ ${role ? `- Role: ${role}` : ''}
121
+
122
+ Application Details:
123
+ - What are you building with AI today?
124
+ ${whatBuilding}
125
+
126
+ - What do you hope ContractSpec will solve for you?
127
+ ${whatSolving}
128
+
129
+ - Primary use case: ${useCase || 'Not specified'}
130
+ - Current stack: ${currentStack || 'Not specified'}
131
+ - Team Size: ${teamSize || 'Not specified'}
132
+ - Timeline: ${timeline || 'Not specified'}
133
+
134
+ Preferences:
135
+ - Open to 1:1 product/design sessions: ${openToSessions ? 'Yes' : 'No'}
136
+ - Okay with anonymized case studies: ${okayWithCaseStudies ? 'Yes' : 'No'}
137
+
138
+ ---
139
+ Submitted via ContractSpec waitlist application form
140
+ `.trim();
141
+
142
+ const teamEmailHtml = `
143
+ <div style="font-family: sans-serif; max-width: 720px; margin: 0 auto;">
144
+ <h1 style="color: #8b5cf6;">New Design Partner Waitlist Application</h1>
145
+ <h2 style="color: #8b5cf6; margin: 16px 0 8px;">Contact Information</h2>
146
+ <ul style="padding-left: 16px; line-height: 1.6; margin: 0 0 16px;">
147
+ <li>Name: ${escapeHtml(name)}</li>
148
+ <li>Email: ${escapeHtml(email)}</li>
149
+ ${company ? `<li>Company/Project: ${escapeHtml(company)}</li>` : ''}
150
+ ${role ? `<li>Role: ${escapeHtml(role)}</li>` : ''}
151
+ </ul>
152
+ <h2 style="color: #8b5cf6; margin: 16px 0 8px;">Application Details</h2>
153
+ <p style="margin: 0 0 8px; font-weight: 600;">What are you building with AI today?</p>
154
+ <div style="border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px; background: #f9fafb; white-space: pre-wrap; line-height: 1.6;">
155
+ ${formatMultilineHtml(whatBuilding)}
156
+ </div>
157
+ <p style="margin: 16px 0 8px; font-weight: 600;">What do you hope ContractSpec will solve for you?</p>
158
+ <div style="border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px; background: #f9fafb; white-space: pre-wrap; line-height: 1.6;">
159
+ ${formatMultilineHtml(whatSolving)}
160
+ </div>
161
+ <ul style="padding-left: 16px; line-height: 1.6; margin: 16px 0;">
162
+ <li>Primary use case: ${escapeHtml(useCase || 'Not specified')}</li>
163
+ <li>Current stack: ${escapeHtml(currentStack || 'Not specified')}</li>
164
+ <li>Team Size: ${escapeHtml(teamSize || 'Not specified')}</li>
165
+ <li>Timeline: ${escapeHtml(timeline || 'Not specified')}</li>
166
+ </ul>
167
+ <h2 style="color: #8b5cf6; margin: 16px 0 8px;">Preferences</h2>
168
+ <div style="border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px; background: #f9fafb; line-height: 1.6;">
169
+ ${formatMultilineHtml(preferencesText)}
170
+ </div>
171
+ <p style="color: #6b7280; font-size: 12px; margin-top: 20px;">Submitted via ContractSpec waitlist application form</p>
172
+ </div>
173
+ `;
174
+
175
+ const teamSend = await sendEmail(configResult.config, {
176
+ to: [configResult.config.teamInbox],
177
+ subject: `New Design Partner Application: ${name} (${email})`,
178
+ text: teamEmailText,
179
+ html: teamEmailHtml,
180
+ replyTo: email,
181
+ context: 'waitlist-application-team-notification',
182
+ });
183
+
184
+ if (!teamSend.success) {
185
+ return { success: false, text: APPLICATION_SEND_ERROR };
186
+ }
187
+
188
+ return {
189
+ success: true,
190
+ text: 'Application submitted successfully!',
191
+ };
192
+ };
@@ -0,0 +1,118 @@
1
+ 'use server';
2
+
3
+ import { getEmailConfig, sendEmail } from './client';
4
+ import { formatMultilineHtml } from './utils';
5
+ import type { SubmitWaitlistResult } from './types';
6
+
7
+ const WAITLIST_MISSING_CONFIG =
8
+ 'Waitlist service is not configured. Please try again later.';
9
+ const WAITLIST_SEND_ERROR =
10
+ 'Failed to join waitlist. Please try again later or contact us directly.';
11
+
12
+ export const joinWaitlist = async (
13
+ formData: FormData
14
+ ): Promise<SubmitWaitlistResult> => {
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 ?? WAITLIST_MISSING_CONFIG,
29
+ };
30
+ }
31
+
32
+ const waitlistText = `
33
+ You're on the waitlist!
34
+
35
+ Thanks for joining the ContractSpec waitlist. You're now in line for early access to:
36
+
37
+ • Stabilize your AI-generated code with ContractSpec
38
+ • Multi-surface consistency (API, DB, UI, events)
39
+ • Safe regeneration without breaking changes
40
+ • AI governance and contract enforcement
41
+
42
+ We'll notify you as soon as early access is available. In the meantime, you can:
43
+
44
+ • Check out our docs: https://contractspec.io/docs
45
+ • Follow our progress on GitHub
46
+ • Book a demo call to see ContractSpec in action
47
+
48
+ We're excited to have you on board!
49
+
50
+ ---
51
+ ContractSpec Team
52
+ https://contractspec.io
53
+
54
+ To remove yourself from the waitlist, reply to this email with "remove"
55
+ `.trim();
56
+
57
+ const waitlistHtml = `
58
+ <div style="font-family: sans-serif; max-width: 640px; margin: 0 auto;">
59
+ <h1 style="color: #8b5cf6;">You're on the waitlist!</h1>
60
+ <p>Thanks for joining the ContractSpec waitlist. You're now in line for early access to:</p>
61
+ <ul style="line-height: 1.8;">
62
+ <li>Stabilize your AI-generated code with ContractSpec</li>
63
+ <li>Multi-surface consistency (API, DB, UI, events)</li>
64
+ <li>Safe regeneration without breaking changes</li>
65
+ <li>AI governance and contract enforcement</li>
66
+ </ul>
67
+ <p>We'll notify you as soon as early access is available. In the meantime, you can:</p>
68
+ <ul style="line-height: 1.8;">
69
+ <li>Check out our <a href="https://contractspec.io/docs" style="color: #8b5cf6;">docs</a></li>
70
+ <li>Follow our progress on GitHub</li>
71
+ <li>Book a demo call to see ContractSpec in action</li>
72
+ </ul>
73
+ <p>We're excited to have you on board!</p>
74
+ <hr style="margin: 30px 0; border: none; border-top: 1px solid #e5e7eb;" />
75
+ <p style="color: #6b7280; font-size: 14px;">
76
+ ContractSpec Team<br />
77
+ <a href="https://contractspec.io" style="color: #8b5cf6;">contractspec.io</a>
78
+ </p>
79
+ <p style="color: #9ca3af; font-size: 12px;">
80
+ To remove yourself from the waitlist, reply to this email with "remove"
81
+ </p>
82
+ </div>
83
+ `;
84
+
85
+ const userSend = await sendEmail(configResult.config, {
86
+ to: [{ email }],
87
+ subject: "You're on the ContractSpec waitlist!",
88
+ text: waitlistText,
89
+ html: waitlistHtml,
90
+ context: 'waitlist-welcome',
91
+ });
92
+
93
+ if (!userSend.success) {
94
+ return { success: false, text: WAITLIST_SEND_ERROR };
95
+ }
96
+
97
+ const teamNotificationText = `New waitlist signup from: ${email}`;
98
+ const teamNotificationHtml = `
99
+ <div style="font-family: sans-serif; max-width: 640px; margin: 0 auto;">
100
+ <p style="margin: 0 0 8px;">New waitlist signup</p>
101
+ <p style="margin: 0;"><strong>Email:</strong> ${formatMultilineHtml(email)}</p>
102
+ </div>
103
+ `;
104
+
105
+ const teamSend = await sendEmail(configResult.config, {
106
+ to: [configResult.config.teamInbox],
107
+ subject: `New Waitlist Signup: ${email}`,
108
+ text: teamNotificationText,
109
+ html: teamNotificationHtml,
110
+ context: 'waitlist-team-notification',
111
+ });
112
+
113
+ if (!teamSend.success) {
114
+ return { success: false, text: WAITLIST_SEND_ERROR };
115
+ }
116
+
117
+ return { success: true, text: 'Successfully joined waitlist!' };
118
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Tentative pricing example values for UI display.
3
+ * These are examples only and subject to change based on learnings from design partners.
4
+ */
5
+
6
+ export const PRICING_EXAMPLES = {
7
+ free: {
8
+ regenerationsPerMonth: 200,
9
+ aiActionsPerMonth: 100,
10
+ projects: 1,
11
+ },
12
+ builder: {
13
+ regenerationsPerMonthHint: '1,000–2,000+',
14
+ aiActionsPerMonthHint: '1,000+',
15
+ },
16
+ team: {
17
+ description: 'Higher limits + cheaper per-regen at scale',
18
+ },
19
+ } as const;
@@ -0,0 +1,38 @@
1
+ import {
2
+ createDefaultTransformEngine,
3
+ registerBasicValidation,
4
+ registerDefaultReactRenderer,
5
+ registerReactToMarkdownRenderer,
6
+ type TransformEngine,
7
+ } from '@contractspec/lib.contracts/presentations';
8
+ import { componentMap } from './registry';
9
+ import type { ComponentMap } from './types';
10
+
11
+ /**
12
+ * Create and configure the TransformEngine for web-landing presentations.
13
+ * Includes React and Markdown renderers with component map registration.
14
+ */
15
+ export function createPresentationEngine(
16
+ customComponentMap?: ComponentMap
17
+ ): TransformEngine {
18
+ const engine = createDefaultTransformEngine();
19
+
20
+ // Register React renderer
21
+ registerDefaultReactRenderer(engine);
22
+
23
+ // Register basic validation
24
+ registerBasicValidation(engine);
25
+
26
+ // Register React-to-markdown renderer with component map
27
+ // This enables rendering React components to markdown for LLM consumption
28
+ const mapToUse = customComponentMap ?? componentMap;
29
+ registerReactToMarkdownRenderer(engine, mapToUse);
30
+
31
+ return engine;
32
+ }
33
+
34
+ /**
35
+ * Singleton engine instance for the app.
36
+ * Use this for rendering presentations.
37
+ */
38
+ export const presentationEngine = createPresentationEngine();