@hexclave/shared 1.0.3 → 1.0.6
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/dist/ai/unified-prompts/reminders.js +4 -3
- package/dist/ai/unified-prompts/reminders.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts +3 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +216 -29
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.d.ts.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.js +1 -0
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +18 -6
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +23 -8
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js.map +1 -1
- package/dist/config/schema.d.ts +1 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +5 -5
- package/dist/config/schema.js.map +1 -1
- package/dist/config-authoring.d.ts +4 -4
- package/dist/config-authoring.d.ts.map +1 -1
- package/dist/config-authoring.js +2 -2
- package/dist/config-authoring.js.map +1 -1
- package/dist/config-rendering.d.ts +4 -4
- package/dist/config-rendering.d.ts.map +1 -1
- package/dist/config-rendering.js +19 -18
- package/dist/config-rendering.js.map +1 -1
- package/dist/esm/ai/unified-prompts/reminders.js +4 -3
- package/dist/esm/ai/unified-prompts/reminders.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts +3 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +215 -30
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.d.ts.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.js +1 -0
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +18 -6
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +23 -8
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js.map +1 -1
- package/dist/esm/config/schema.d.ts +1 -1
- package/dist/esm/config/schema.d.ts.map +1 -1
- package/dist/esm/config/schema.js +5 -5
- package/dist/esm/config/schema.js.map +1 -1
- package/dist/esm/config-authoring.d.ts +4 -4
- package/dist/esm/config-authoring.d.ts.map +1 -1
- package/dist/esm/config-authoring.js +2 -2
- package/dist/esm/config-authoring.js.map +1 -1
- package/dist/esm/config-rendering.d.ts +4 -4
- package/dist/esm/config-rendering.d.ts.map +1 -1
- package/dist/esm/config-rendering.js +15 -14
- package/dist/esm/config-rendering.js.map +1 -1
- package/dist/esm/helpers/vault/client-side.d.ts.map +1 -1
- package/dist/esm/helpers/vault/client-side.js.map +1 -1
- package/dist/esm/helpers/vault/server-side.d.ts.map +1 -1
- package/dist/esm/helpers/vault/server-side.js.map +1 -1
- package/dist/esm/hexclave-config-file.d.ts +15 -0
- package/dist/esm/hexclave-config-file.d.ts.map +1 -0
- package/dist/esm/{stack-config-file.js → hexclave-config-file.js} +7 -7
- package/dist/esm/hexclave-config-file.js.map +1 -0
- package/dist/esm/interface/admin-metrics.d.ts +2 -2
- package/dist/esm/interface/admin-metrics.js.map +1 -1
- package/dist/esm/interface/client-interface.js +2 -2
- package/dist/esm/interface/client-interface.js.map +1 -1
- package/dist/esm/interface/conversations.d.ts +32 -32
- package/dist/esm/interface/crud/current-user.d.ts +7 -7
- package/dist/esm/interface/crud/email-outbox.d.ts +42 -42
- package/dist/esm/interface/crud/project-permissions.js +5 -5
- package/dist/esm/interface/crud/project-permissions.js.map +1 -1
- package/dist/esm/interface/crud/team-member-profiles.d.ts +18 -18
- package/dist/esm/interface/crud/team-permissions.js +5 -5
- package/dist/esm/interface/crud/team-permissions.js.map +1 -1
- package/dist/esm/interface/crud/users.d.ts +10 -10
- package/dist/esm/interface/page-component-versions.js +107 -107
- package/dist/esm/interface/page-component-versions.js.map +1 -1
- package/dist/esm/interface/webhooks.d.ts +2 -2
- package/dist/esm/known-errors.d.ts +1 -1
- package/dist/esm/known-errors.d.ts.map +1 -1
- package/dist/esm/known-errors.js +9 -5
- package/dist/esm/known-errors.js.map +1 -1
- package/dist/esm/schema-fields.d.ts +15 -5
- package/dist/esm/schema-fields.d.ts.map +1 -1
- package/dist/esm/schema-fields.js +26 -16
- package/dist/esm/schema-fields.js.map +1 -1
- package/dist/esm/utils/crypto.d.ts.map +1 -1
- package/dist/esm/utils/crypto.js.map +1 -1
- package/dist/esm/utils/currency-constants.d.ts +6 -0
- package/dist/esm/utils/currency-constants.d.ts.map +1 -1
- package/dist/esm/utils/currency-constants.js.map +1 -1
- package/dist/esm/utils/dev-env-state-path.d.ts +3 -3
- package/dist/esm/utils/dev-env-state-path.d.ts.map +1 -1
- package/dist/esm/utils/dev-env-state-path.js +4 -4
- package/dist/esm/utils/dev-env-state-path.js.map +1 -1
- package/dist/esm/utils/errors.js +2 -2
- package/dist/esm/utils/errors.js.map +1 -1
- package/dist/esm/utils/esbuild.d.ts.map +1 -1
- package/dist/esm/utils/esbuild.js +0 -1
- package/dist/esm/utils/esbuild.js.map +1 -1
- package/dist/esm/utils/featurebase.d.ts +3 -3
- package/dist/esm/utils/featurebase.d.ts.map +1 -1
- package/dist/esm/utils/featurebase.js +15 -15
- package/dist/esm/utils/featurebase.js.map +1 -1
- package/dist/esm/utils/fs.js +6 -6
- package/dist/esm/utils/fs.js.map +1 -1
- package/dist/esm/utils/globals.d.ts.map +1 -1
- package/dist/esm/utils/globals.js +10 -10
- package/dist/esm/utils/globals.js.map +1 -1
- package/dist/esm/utils/jwt.d.ts.map +1 -1
- package/dist/esm/utils/jwt.js +3 -3
- package/dist/esm/utils/jwt.js.map +1 -1
- package/dist/esm/utils/redirect-urls.d.ts +3 -3
- package/dist/esm/utils/redirect-urls.d.ts.map +1 -1
- package/dist/esm/utils/redirect-urls.js +5 -5
- package/dist/esm/utils/redirect-urls.js.map +1 -1
- package/dist/esm/utils/urls.js.map +1 -1
- package/dist/helpers/vault/client-side.d.ts.map +1 -1
- package/dist/helpers/vault/client-side.js.map +1 -1
- package/dist/helpers/vault/server-side.d.ts.map +1 -1
- package/dist/helpers/vault/server-side.js.map +1 -1
- package/dist/hexclave-config-file.d.ts +15 -0
- package/dist/hexclave-config-file.d.ts.map +1 -0
- package/dist/{stack-config-file.js → hexclave-config-file.js} +8 -8
- package/dist/hexclave-config-file.js.map +1 -0
- package/dist/interface/admin-metrics.d.ts +12 -12
- package/dist/interface/admin-metrics.js.map +1 -1
- package/dist/interface/client-interface.js +2 -2
- package/dist/interface/client-interface.js.map +1 -1
- package/dist/interface/conversations.d.ts +32 -32
- package/dist/interface/crud/current-user.d.ts +7 -7
- package/dist/interface/crud/email-outbox.d.ts +42 -42
- package/dist/interface/crud/project-permissions.js +5 -5
- package/dist/interface/crud/project-permissions.js.map +1 -1
- package/dist/interface/crud/team-member-profiles.d.ts +18 -18
- package/dist/interface/crud/team-permissions.js +5 -5
- package/dist/interface/crud/team-permissions.js.map +1 -1
- package/dist/interface/crud/users.d.ts +10 -10
- package/dist/interface/page-component-versions.js +107 -107
- package/dist/interface/page-component-versions.js.map +1 -1
- package/dist/interface/webhooks.d.ts +2 -2
- package/dist/known-errors.d.ts +1 -1
- package/dist/known-errors.d.ts.map +1 -1
- package/dist/known-errors.js +9 -5
- package/dist/known-errors.js.map +1 -1
- package/dist/schema-fields.d.ts +15 -5
- package/dist/schema-fields.d.ts.map +1 -1
- package/dist/schema-fields.js +26 -16
- package/dist/schema-fields.js.map +1 -1
- package/dist/utils/crypto.d.ts.map +1 -1
- package/dist/utils/crypto.js.map +1 -1
- package/dist/utils/currency-constants.d.ts +6 -0
- package/dist/utils/currency-constants.d.ts.map +1 -1
- package/dist/utils/currency-constants.js.map +1 -1
- package/dist/utils/dev-env-state-path.d.ts +3 -3
- package/dist/utils/dev-env-state-path.d.ts.map +1 -1
- package/dist/utils/dev-env-state-path.js +5 -5
- package/dist/utils/dev-env-state-path.js.map +1 -1
- package/dist/utils/errors.js +2 -2
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/esbuild.d.ts.map +1 -1
- package/dist/utils/esbuild.js +0 -1
- package/dist/utils/esbuild.js.map +1 -1
- package/dist/utils/featurebase.d.ts +3 -3
- package/dist/utils/featurebase.d.ts.map +1 -1
- package/dist/utils/featurebase.js +15 -15
- package/dist/utils/featurebase.js.map +1 -1
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/globals.d.ts.map +1 -1
- package/dist/utils/globals.js +10 -10
- package/dist/utils/globals.js.map +1 -1
- package/dist/utils/jwt.d.ts.map +1 -1
- package/dist/utils/jwt.js +3 -3
- package/dist/utils/jwt.js.map +1 -1
- package/dist/utils/redirect-urls.d.ts +3 -3
- package/dist/utils/redirect-urls.d.ts.map +1 -1
- package/dist/utils/redirect-urls.js +5 -5
- package/dist/utils/redirect-urls.js.map +1 -1
- package/dist/utils/urls.js.map +1 -1
- package/package.json +2 -1
- package/src/ai/image-limits.ts +62 -0
- package/src/ai/llms/llms.ts +36 -0
- package/src/ai/unified-prompts/reminders.ts +27 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts +1047 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/brain.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/cli-help.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/config-docs.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/custom-components-instructions.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/dashboard-instructions.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/docs-index.ts +80 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.ts +322 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/sdk-interface-source.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt.ts +72 -0
- package/src/apps/apps-config.ts +177 -0
- package/src/apps/apps-ui.tsx +112 -0
- package/src/config/README.md +154 -0
- package/src/config/db-sync-mappings.ts +2293 -0
- package/src/config/format.ts +388 -0
- package/src/config/migrate-catalogs-to-product-lines.ts +475 -0
- package/src/config/schema-fuzzer.test.ts +377 -0
- package/src/config/schema.ts +1507 -0
- package/src/config-authoring.test.ts +52 -0
- package/src/config-authoring.ts +37 -0
- package/src/config-rendering.ts +142 -0
- package/src/crud.tsx +275 -0
- package/src/global.d.ts +1 -0
- package/src/helpers/emails.ts +250 -0
- package/src/helpers/init-prompt.ts +39 -0
- package/src/helpers/password.ts +16 -0
- package/src/helpers/production-mode.ts +58 -0
- package/src/helpers/support-sla.test.ts +124 -0
- package/src/helpers/support-sla.ts +93 -0
- package/src/helpers/vault/client-side.ts +92 -0
- package/src/helpers/vault/server-side.ts +153 -0
- package/src/hexclave-config-file.ts +120 -0
- package/src/hooks/use-async-callback.tsx +43 -0
- package/src/hooks/use-async-external-store.tsx +24 -0
- package/src/hooks/use-hash.tsx +14 -0
- package/src/hooks/use-hover.tsx +88 -0
- package/src/hooks/use-strict-memo.tsx +157 -0
- package/src/index.ts +17 -0
- package/src/interface/admin-interface.ts +1043 -0
- package/src/interface/admin-metrics.ts +214 -0
- package/src/interface/client-interface.test.ts +765 -0
- package/src/interface/client-interface.ts +2586 -0
- package/src/interface/conversations.ts +109 -0
- package/src/interface/crud/analytics.ts +11 -0
- package/src/interface/crud/connected-accounts.ts +52 -0
- package/src/interface/crud/contact-channels.ts +74 -0
- package/src/interface/crud/current-user.ts +72 -0
- package/src/interface/crud/email-outbox.ts +263 -0
- package/src/interface/crud/email-templates.ts +50 -0
- package/src/interface/crud/emails.ts +22 -0
- package/src/interface/crud/internal-api-keys.ts +69 -0
- package/src/interface/crud/invoices.ts +36 -0
- package/src/interface/crud/items.ts +24 -0
- package/src/interface/crud/notification-preferences.ts +33 -0
- package/src/interface/crud/oauth-providers.ts +91 -0
- package/src/interface/crud/products.ts +42 -0
- package/src/interface/crud/project-api-keys.ts +100 -0
- package/src/interface/crud/project-permissions.ts +118 -0
- package/src/interface/crud/projects.ts +229 -0
- package/src/interface/crud/session-replays.ts +85 -0
- package/src/interface/crud/sessions.ts +66 -0
- package/src/interface/crud/sign-up-rules.ts +12 -0
- package/src/interface/crud/svix-token.ts +20 -0
- package/src/interface/crud/team-invitation-details.ts +22 -0
- package/src/interface/crud/team-invitation.ts +35 -0
- package/src/interface/crud/team-member-profiles.ts +61 -0
- package/src/interface/crud/team-memberships.ts +56 -0
- package/src/interface/crud/team-permissions.ts +119 -0
- package/src/interface/crud/teams.ts +142 -0
- package/src/interface/crud/transactions.ts +131 -0
- package/src/interface/crud/users.ts +185 -0
- package/src/interface/handler-urls.ts +74 -0
- package/src/interface/page-component-versions.ts +1702 -0
- package/src/interface/server-interface.ts +1146 -0
- package/src/interface/webhooks.ts +28 -0
- package/src/known-errors.tsx +2034 -0
- package/src/local-emulator.ts +5 -0
- package/src/payments/payment-methods.ts +167 -0
- package/src/payments/stripe-limits.ts +18 -0
- package/src/plans.ts +107 -0
- package/src/schema-fields.ts +944 -0
- package/src/sessions.ts +310 -0
- package/src/utils/api-keys.tsx +122 -0
- package/src/utils/arrays.tsx +248 -0
- package/src/utils/auth-methods.ts +8 -0
- package/src/utils/base64.tsx +24 -0
- package/src/utils/booleans.tsx +34 -0
- package/src/utils/browser-compat.tsx +17 -0
- package/src/utils/bytes.tsx +280 -0
- package/src/utils/caches.tsx +280 -0
- package/src/utils/cel-fields.ts +92 -0
- package/src/utils/cloud-hosts.tsx +92 -0
- package/src/utils/compile-time.tsx +11 -0
- package/src/utils/country-codes.ts +39 -0
- package/src/utils/crypto.tsx +174 -0
- package/src/utils/currencies.tsx +53 -0
- package/src/utils/currency-constants.tsx +51 -0
- package/src/utils/dates.tsx +246 -0
- package/src/utils/dev-env-state-path.ts +14 -0
- package/src/utils/dom.tsx +7 -0
- package/src/utils/env.tsx +117 -0
- package/src/utils/errors.tsx +284 -0
- package/src/utils/esbuild.tsx +235 -0
- package/src/utils/featurebase.tsx +247 -0
- package/src/utils/fs.tsx +33 -0
- package/src/utils/functions.tsx +22 -0
- package/src/utils/geo.tsx +16 -0
- package/src/utils/globals.tsx +48 -0
- package/src/utils/hashes.tsx +52 -0
- package/src/utils/html.tsx +60 -0
- package/src/utils/http.tsx +91 -0
- package/src/utils/ips.tsx +47 -0
- package/src/utils/json.tsx +200 -0
- package/src/utils/jsx-editable-transpiler.test.ts +156 -0
- package/src/utils/jsx-editable-transpiler.tsx +401 -0
- package/src/utils/jwt.tsx +170 -0
- package/src/utils/locks.tsx +71 -0
- package/src/utils/maps.tsx +407 -0
- package/src/utils/math.tsx +18 -0
- package/src/utils/monkey-patch.tsx +29 -0
- package/src/utils/node-http.tsx +47 -0
- package/src/utils/numbers.tsx +82 -0
- package/src/utils/oauth.tsx +9 -0
- package/src/utils/objects.tsx +581 -0
- package/src/utils/paginated-lists.test.ts +1039 -0
- package/src/utils/paginated-lists.tsx +479 -0
- package/src/utils/passkey.tsx +1 -0
- package/src/utils/promises.tsx +540 -0
- package/src/utils/proxies.tsx +212 -0
- package/src/utils/react.tsx +308 -0
- package/src/utils/redirect-urls.tsx +299 -0
- package/src/utils/regex.tsx +10 -0
- package/src/utils/results.tsx +411 -0
- package/src/utils/sentry.tsx +20 -0
- package/src/utils/stores.tsx +238 -0
- package/src/utils/strings.nicify.test.ts +253 -0
- package/src/utils/strings.tsx +723 -0
- package/src/utils/telemetry.tsx +37 -0
- package/src/utils/turnstile-browser.ts +106 -0
- package/src/utils/turnstile-flow.test.ts +101 -0
- package/src/utils/turnstile-flow.ts +266 -0
- package/src/utils/turnstile.ts +34 -0
- package/src/utils/typed-arrays.tsx +17 -0
- package/src/utils/types.tsx +208 -0
- package/src/utils/unicode.tsx +24 -0
- package/src/utils/urls.tsx +411 -0
- package/src/utils/uuids.tsx +47 -0
- package/dist/esm/stack-config-file.d.ts +0 -15
- package/dist/esm/stack-config-file.d.ts.map +0 -1
- package/dist/esm/stack-config-file.js.map +0 -1
- package/dist/stack-config-file.d.ts +0 -15
- package/dist/stack-config-file.d.ts.map +0 -1
- package/dist/stack-config-file.js.map +0 -1
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { deindent } from "../utils/strings";
|
|
2
|
+
|
|
3
|
+
export const defaultNewTemplateSource = deindent`
|
|
4
|
+
import { type } from "arktype"
|
|
5
|
+
import { Container } from "@react-email/components";
|
|
6
|
+
import { Subject, NotificationCategory, Props } from "@stackframe/emails";
|
|
7
|
+
|
|
8
|
+
export const variablesSchema = type({
|
|
9
|
+
count: "number"
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export function EmailTemplate({ user, variables }: Props<typeof variablesSchema.infer>) {
|
|
13
|
+
return (
|
|
14
|
+
<Container>
|
|
15
|
+
<Subject value={\`Hello \${user.displayName}!\`} />
|
|
16
|
+
<NotificationCategory value="Transactional" />
|
|
17
|
+
<div className="font-bold">Hi {user.displayName}!</div>
|
|
18
|
+
<br />
|
|
19
|
+
count is {variables.count}
|
|
20
|
+
</Container>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
EmailTemplate.PreviewVariables = {
|
|
25
|
+
count: 10
|
|
26
|
+
} satisfies typeof variablesSchema.infer
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
export const previewTemplateSource = deindent`
|
|
30
|
+
import { Button, Section, Hr, Text, Heading } from "@react-email/components";
|
|
31
|
+
import { Subject, NotificationCategory } from "@stackframe/emails";
|
|
32
|
+
|
|
33
|
+
export const variablesSchema = v => v;
|
|
34
|
+
|
|
35
|
+
export function EmailTemplate({ user, project }) {
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
<Subject value={\`Verify your email at \${project.displayName}\`} />
|
|
39
|
+
<NotificationCategory value="Transactional" />
|
|
40
|
+
<div className="font-sans text-base font-normal tracking-[0.15008px] leading-[1.5] m-0 py-8 w-full min-h-full">
|
|
41
|
+
<Section>
|
|
42
|
+
<Heading as="h3" className="font-sans font-bold text-[20px] text-center py-4 px-6 m-0">
|
|
43
|
+
Verify your email at {project.displayName}
|
|
44
|
+
</Heading>
|
|
45
|
+
<Text className="font-sans font-normal text-[14px] text-center pt-2 px-6 pb-4 m-0 opacity-80">
|
|
46
|
+
Hi{user.displayName ? (", " + user.displayName) : ''}! Please click on the following button to verify your email.
|
|
47
|
+
</Text>
|
|
48
|
+
<div className="text-center py-3 px-6">
|
|
49
|
+
<Button
|
|
50
|
+
href="#"
|
|
51
|
+
className="text-black font-sans font-bold text-[14px] inline-block bg-[#f0f0f0] rounded-[4px] py-3 px-5 no-underline border-0"
|
|
52
|
+
>
|
|
53
|
+
Verify my email
|
|
54
|
+
</Button>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="py-4 px-6">
|
|
57
|
+
<Hr className="opacity-20" />
|
|
58
|
+
</div>
|
|
59
|
+
<Text className="font-sans font-normal text-[12px] text-center pt-1 px-6 pb-6 m-0 opacity-60">
|
|
60
|
+
If you were not expecting this email, you can safely ignore it.
|
|
61
|
+
</Text>
|
|
62
|
+
</Section>
|
|
63
|
+
</div>
|
|
64
|
+
</>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
export const emptyEmailTheme = deindent`
|
|
70
|
+
import { Html, Tailwind, Body } from '@react-email/components';
|
|
71
|
+
export function EmailTheme({ children }: { children: React.ReactNode }) {
|
|
72
|
+
return (
|
|
73
|
+
<Html>
|
|
74
|
+
<Tailwind>
|
|
75
|
+
<Body>
|
|
76
|
+
{children}
|
|
77
|
+
</Body>
|
|
78
|
+
</Tailwind>
|
|
79
|
+
</Html>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
export const LightEmailTheme = `import { Html, Head, Tailwind, Body, Container, Section } from '@react-email/components';
|
|
85
|
+
import { ThemeProps, ProjectLogo } from "@stackframe/emails";
|
|
86
|
+
|
|
87
|
+
export function EmailTheme({ children, unsubscribeLink, projectLogos }: ThemeProps) {
|
|
88
|
+
return (
|
|
89
|
+
<Html>
|
|
90
|
+
<Head />
|
|
91
|
+
<Tailwind>
|
|
92
|
+
<Body className="bg-[#fafbfb] font-sans text-base m-0 p-0" style={{ overflowX: 'hidden' }}>
|
|
93
|
+
<div className="py-8 px-4 flex justify-center">
|
|
94
|
+
<Container className="bg-white p-[45px] rounded-lg shadow-sm mx-auto" style={{ width: '100%', maxWidth: '600px' }}>
|
|
95
|
+
<ProjectLogo data={projectLogos} mode="light" />
|
|
96
|
+
{children}
|
|
97
|
+
</Container>
|
|
98
|
+
</div>
|
|
99
|
+
{unsubscribeLink && (
|
|
100
|
+
<Section className="p-4 opacity-60 text-center" style={{ textAlign: 'center' }}>
|
|
101
|
+
<a href={unsubscribeLink} className="text-blue-600" target="_blank" rel="noopener noreferrer">Click here</a> to unsubscribe from these emails
|
|
102
|
+
</Section>
|
|
103
|
+
)}
|
|
104
|
+
</Body>
|
|
105
|
+
</Tailwind>
|
|
106
|
+
</Html>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
EmailTheme.PreviewProps = {
|
|
111
|
+
unsubscribeLink: "https://example.com",
|
|
112
|
+
} satisfies Partial<ThemeProps>
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
const DarkEmailTheme = `import { Html, Head, Tailwind, Body, Container, Section } from '@react-email/components';
|
|
117
|
+
import { ThemeProps, ProjectLogo } from "@stackframe/emails";
|
|
118
|
+
|
|
119
|
+
export function EmailTheme({ children, unsubscribeLink, projectLogos }: ThemeProps) {
|
|
120
|
+
return (
|
|
121
|
+
<Html>
|
|
122
|
+
<Head />
|
|
123
|
+
<Tailwind>
|
|
124
|
+
<Body className="bg-[#323232] font-sans text-white m-0 p-0" style={{ overflowX: 'hidden' }}>
|
|
125
|
+
<div className="py-8 px-4 flex justify-center">
|
|
126
|
+
<Container className="bg-black p-[45px] rounded-lg shadow-sm mx-auto" style={{ width: '100%', maxWidth: '600px' }}>
|
|
127
|
+
<ProjectLogo data={projectLogos} mode="dark" />
|
|
128
|
+
{children}
|
|
129
|
+
</Container>
|
|
130
|
+
</div>
|
|
131
|
+
{unsubscribeLink && (
|
|
132
|
+
<Section className="p-4 opacity-60 text-center" style={{ textAlign: 'center' }}>
|
|
133
|
+
<a href={unsubscribeLink} className="text-blue-600" target="_blank" rel="noopener noreferrer">Click here</a> to unsubscribe from these emails
|
|
134
|
+
</Section>
|
|
135
|
+
)}
|
|
136
|
+
</Body>
|
|
137
|
+
</Tailwind>
|
|
138
|
+
</Html>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
EmailTheme.PreviewProps = {
|
|
143
|
+
unsubscribeLink: "https://example.com",
|
|
144
|
+
} satisfies Partial<ThemeProps>
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
const ColorfulEmailTheme = `import { Html, Head, Tailwind, Body, Container, Section } from '@react-email/components';
|
|
149
|
+
import { ThemeProps, ProjectLogo } from "@stackframe/emails";
|
|
150
|
+
|
|
151
|
+
export function EmailTheme({ children, unsubscribeLink, projectLogos }: ThemeProps) {
|
|
152
|
+
return (
|
|
153
|
+
<Html>
|
|
154
|
+
<Head />
|
|
155
|
+
<Tailwind>
|
|
156
|
+
<Body className="bg-[#f0eef6] font-sans text-base m-0 p-0" style={{ overflowX: 'hidden' }}>
|
|
157
|
+
<div className="py-8 px-4 flex justify-center">
|
|
158
|
+
<Container className="bg-white p-[45px] rounded-2xl mx-auto" style={{ width: '100%', maxWidth: '600px', borderTop: '4px solid #7c5ce0' }}>
|
|
159
|
+
<ProjectLogo data={projectLogos} mode="light" />
|
|
160
|
+
{children}
|
|
161
|
+
</Container>
|
|
162
|
+
</div>
|
|
163
|
+
{unsubscribeLink && (
|
|
164
|
+
<Section className="p-4 opacity-60 text-center" style={{ textAlign: 'center' }}>
|
|
165
|
+
<a href={unsubscribeLink} className="text-[#7c5ce0]" target="_blank" rel="noopener noreferrer">Click here</a> to unsubscribe from these emails
|
|
166
|
+
</Section>
|
|
167
|
+
)}
|
|
168
|
+
</Body>
|
|
169
|
+
</Tailwind>
|
|
170
|
+
</Html>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
EmailTheme.PreviewProps = {
|
|
175
|
+
unsubscribeLink: "https://example.com",
|
|
176
|
+
} satisfies Partial<ThemeProps>
|
|
177
|
+
`;
|
|
178
|
+
|
|
179
|
+
export const DEFAULT_EMAIL_THEME_ID = "1df07ae6-abf3-4a40-83a5-a1a2cbe336ac";
|
|
180
|
+
|
|
181
|
+
export const DEFAULT_EMAIL_THEMES = {
|
|
182
|
+
[DEFAULT_EMAIL_THEME_ID]: {
|
|
183
|
+
displayName: 'Default Light',
|
|
184
|
+
tsxSource: LightEmailTheme,
|
|
185
|
+
},
|
|
186
|
+
"a0172b5d-cff0-463b-83bb-85124697373a": {
|
|
187
|
+
displayName: 'Default Dark',
|
|
188
|
+
tsxSource: DarkEmailTheme,
|
|
189
|
+
},
|
|
190
|
+
"c7e2d94f-8a1b-4e6d-b3f5-9d0a2e7c1b8f": {
|
|
191
|
+
displayName: 'Default Colorful',
|
|
192
|
+
tsxSource: ColorfulEmailTheme,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const EMAIL_TEMPLATE_EMAIL_VERIFICATION_ID = "e7d009ce-8d47-4528-b245-5bf119f2ffa3";
|
|
197
|
+
const EMAIL_TEMPLATE_PASSWORD_RESET_ID = "a70fb3a4-56c1-4e42-af25-49d25603abd0";
|
|
198
|
+
const EMAIL_TEMPLATE_MAGIC_LINK_ID = "822687fe-8d0a-4467-a0d1-416b6e639478";
|
|
199
|
+
const EMAIL_TEMPLATE_TEAM_INVITATION_ID = "e84de395-2076-4831-9c19-8e9a96a868e4";
|
|
200
|
+
const EMAIL_TEMPLATE_SIGN_IN_INVITATION_ID = "066dd73c-36da-4fd0-b6d6-ebf87683f8bc";
|
|
201
|
+
const EMAIL_TEMPLATE_PAYMENT_RECEIPT_ID = "70372aee-0441-4d80-974c-2e858e40123a";
|
|
202
|
+
const EMAIL_TEMPLATE_PAYMENT_FAILED_ID = "f64b1afe-27ec-4a28-a277-7178f3261f9a";
|
|
203
|
+
|
|
204
|
+
export const DEFAULT_EMAIL_TEMPLATES = {
|
|
205
|
+
[EMAIL_TEMPLATE_EMAIL_VERIFICATION_ID]: {
|
|
206
|
+
"displayName": "Email Verification",
|
|
207
|
+
"tsxSource": "import { type } from \"arktype\"\nimport { Button, Section, Hr, Text, Heading } from \"@react-email/components\";\nimport { Subject, NotificationCategory, Props } from \"@stackframe/emails\";\n\nexport const variablesSchema = type({\n emailVerificationLink: \"string\"\n})\n\nexport function EmailTemplate({ user, project, variables }: Props<typeof variablesSchema.infer>) {\n return (\n <>\n <Subject value={`Verify your email at ${project.displayName}`} />\n <NotificationCategory value=\"Transactional\" />\n <div className=\"font-sans text-base font-normal tracking-[0.15008px] leading-[1.5] m-0 py-8 w-full min-h-full\">\n <Section>\n <Heading as=\"h3\" className=\"font-sans font-bold text-[20px] text-center py-4 px-6 m-0\">\n Verify your email at {project.displayName}\n </Heading>\n <p className=\"font-sans font-normal text-[14px] text-center pt-2 px-6 pb-4 m-0 opacity-80\">\n Hi{user.displayName ? (\", \" + user.displayName) : ''}! Please click on the following button to verify your email.\n </p>\n <div className=\"text-center py-3 px-6\">\n <Button\n href={variables.emailVerificationLink}\n className=\"text-black font-sans font-bold text-[14px] inline-block bg-[#f0f0f0] rounded-[4px] py-3 px-5 no-underline border-0\"\n >\n Verify my email\n </Button>\n </div>\n <div className=\"py-4 px-6\">\n <Hr className=\"opacity-20\" />\n </div>\n <p className=\"font-sans font-normal text-[12px] text-center pt-1 px-6 pb-6 m-0 opacity-60\">\n If you were not expecting this email, you can safely ignore it. \n </p>\n </Section>\n </div>\n </>\n )\n}\n\nEmailTemplate.PreviewVariables = {\n emailVerificationLink: \"#\"\n} satisfies typeof variablesSchema.infer",
|
|
208
|
+
"themeId": undefined,
|
|
209
|
+
},
|
|
210
|
+
[EMAIL_TEMPLATE_PASSWORD_RESET_ID]: {
|
|
211
|
+
"displayName": "Password Reset",
|
|
212
|
+
"tsxSource": "import { type } from \"arktype\"\nimport { Button, Section, Hr, Text, Heading } from \"@react-email/components\"\nimport { Subject, NotificationCategory, Props} from \"@stackframe/emails\"\n\nexport const variablesSchema = type({\n passwordResetLink: \"string\"\n})\n\nexport function EmailTemplate({ user, project, variables }: Props<typeof variablesSchema.infer>) {\n return (\n <>\n <Subject value={\"Reset your password at \" + project.displayName} />\n <NotificationCategory value=\"Transactional\" />\n <div className=\"font-sans text-base font-normal tracking-tight leading-relaxed py-8 w-full min-h-full\">\n <Section>\n <Heading as=\"h3\" className=\"font-sans font-bold text-[20px] text-center py-4 px-6 m-0\">\n Reset your password at {project.displayName}\n </Heading>\n\n <p className=\"text-sm font-sans font-normal text-center pt-2 pb-4 px-6 m-0 opacity-80\">\n Hi{user.displayName ? (\", \" + user.displayName) : \"\"}! Please click on the following button to start the password reset process.\n </p>\n\n <div className=\"text-center px-6 py-3\">\n <Button\n href={variables.passwordResetLink}\n className=\"text-black text-sm font-sans font-bold bg-[#f0f0f0] rounded-[4px] inline-block py-3 px-5 no-underline border-none\"\n target=\"_blank\"\n >\n Reset my password\n </Button>\n </div>\n\n <div className=\"px-6 py-4\">\n <Hr className=\"opacity-20\" />\n </div>\n\n <p className=\"text-xs font-sans font-normal text-center pt-1 pb-6 px-6 m-0 opacity-60\">\n If you were not expecting this email, you can safely ignore it.\n </p>\n </Section>\n </div>\n </>\n )\n}\n\nEmailTemplate.PreviewVariables = {\n passwordResetLink: \"#\"\n} satisfies typeof variablesSchema.infer",
|
|
213
|
+
"themeId": undefined,
|
|
214
|
+
},
|
|
215
|
+
[EMAIL_TEMPLATE_MAGIC_LINK_ID]: {
|
|
216
|
+
"displayName": "Magic Link/OTP",
|
|
217
|
+
"tsxSource": "import { type } from 'arktype';\nimport { Section, Hr, Text, Heading } from '@react-email/components';\nimport { Subject, NotificationCategory, Props } from '@stackframe/emails';\n\nexport const variablesSchema = type({\n magicLink: 'string',\n otp: 'string',\n});\n\nexport function EmailTemplate({ user, project, variables }: Props<typeof variablesSchema.infer>) {\n return (\n <>\n <Subject value={\"Sign in to \" + project.displayName + \": Your code is \" + variables.otp} />\n <NotificationCategory value=\"Transactional\" />\n <div className=\"font-sans text-base font-normal tracking-[0.15008px] leading-6 m-0 py-8 w-full min-h-full\">\n <Section className=\"mx-auto\">\n <Heading as=\"h3\" className=\"font-sans font-bold text-xl text-center px-6 py-4 m-0\">\n Sign in to {project.displayName}\n </Heading>\n <p className=\"text-sm font-sans font-normal text-center px-6 py-4 m-0 opacity-80\">\n Hi{user.displayName ? \", \" + user.displayName : \"\"}! This is your one-time-password for signing in:\n </p>\n <p className=\"text-2xl font-mono font-bold text-center px-6 py-4 m-0\">\n {variables.otp}\n </p>\n <p className=\"text-sm font-sans font-normal text-center px-6 py-4 m-0\">\n Or you can click on{' '}\n <a\n key={20}\n href={variables.magicLink}\n rel=\"noopener noreferrer\"\n className=\"text-blue-600 underline\"\n >\n this link\n </a>{' '}\n to sign in\n </p>\n <Hr className=\"px-6 py-4 opacity-20\" />\n <p className=\"text-xs font-sans font-normal text-center px-6 pt-1 pb-6 m-0 opacity-60\">\n If you were not expecting this email, you can safely ignore it.\n </p>\n </Section>\n </div>\n </>\n );\n}\n\nEmailTemplate.PreviewVariables = {\n magicLink: \"#\",\n otp: \"3SLSWZ\"\n} satisfies typeof variablesSchema.infer",
|
|
218
|
+
"themeId": undefined,
|
|
219
|
+
},
|
|
220
|
+
[EMAIL_TEMPLATE_TEAM_INVITATION_ID]: {
|
|
221
|
+
"displayName": "Team Invitation",
|
|
222
|
+
"tsxSource": "import { type } from \"arktype\";\nimport { Button, Section, Hr, Text, Heading } from \"@react-email/components\";\nimport { Subject, NotificationCategory, Props } from \"@stackframe/emails\";\n\n\nexport const variablesSchema = type({\n teamDisplayName: \"string\",\n teamInvitationLink: \"string\"\n});\n\nexport function EmailTemplate({ user, variables }: Props<typeof variablesSchema.infer>) {\n return (\n <>\n <Subject value={\"You have been invited to join \" + variables.teamDisplayName} />\n <NotificationCategory value=\"Transactional\" />\n <div className=\"font-sans text-base font-normal tracking-[0.15008px] leading-[1.5] m-0 py-8 w-full min-h-full\">\n <Section className=\"mx-auto max-w-lg\">\n <Heading as=\"h3\" className=\"font-sans font-bold text-xl text-center px-6 pt-8 m-0\">\n You are invited to {variables.teamDisplayName}\n </Heading>\n <p className=\"text-sm font-sans font-normal text-center px-6 pt-2 pb-4 m-0 opacity-80\">\n Hi{user.displayName ? \", \" + user.displayName : \"\"}! Please click the button below to join the team {variables.teamDisplayName}\n </p>\n <div className=\"text-center px-6 py-3\">\n <Button\n href={variables.teamInvitationLink}\n className=\"text-black text-sm font-sans font-bold bg-[#f0f0f0] rounded-md inline-block px-5 py-3 no-underline border-0\"\n >\n Join team\n </Button>\n </div>\n <div className=\"px-6 py-4\">\n <Hr className=\"opacity-20\" />\n </div>\n <p className=\"text-xs font-sans font-normal text-center px-6 pb-6 pt-1 m-0 opacity-60\">\n If you were not expecting this email, you can safely ignore it.\n </p>\n </Section>\n </div>\n </>\n );\n}\n\nEmailTemplate.PreviewVariables = {\n teamDisplayName: \"My Team\",\n teamInvitationLink: \"#\"\n} satisfies typeof variablesSchema.infer ",
|
|
223
|
+
"themeId": undefined,
|
|
224
|
+
},
|
|
225
|
+
[EMAIL_TEMPLATE_SIGN_IN_INVITATION_ID]: {
|
|
226
|
+
"displayName": "Sign In Invitation",
|
|
227
|
+
"tsxSource": "import { type } from \"arktype\"\nimport { Button, Section, Hr, Text, Heading } from \"@react-email/components\";\nimport { Subject, NotificationCategory, Props } from \"@stackframe/emails\";\n\nexport const variablesSchema = type({\n signInInvitationLink: \"string\",\n teamDisplayName: \"string\"\n})\n\nexport function EmailTemplate({ user, project, variables }: Props<typeof variablesSchema.infer>) {\n return (\n <>\n <Subject\n value={\"You have been invited to sign in to \" + project.displayName}\n />\n <NotificationCategory value=\"Transactional\" />\n\n <div className=\"font-sans text-base font-normal leading-normal w-full min-h-full m-0 py-8\">\n <Section>\n <Heading as=\"h3\" className=\"font-sans font-bold text-xl text-center pt-8 px-6 m-0\">\n You are invited to sign in to {variables.teamDisplayName}\n </Heading>\n\n <p className=\"text-sm font-sans font-normal text-center pt-2 pb-4 px-6 m-0 opacity-80\">\n Hi\n {user.displayName ? \", \" + user.displayName : \"\"}! Please click on the following\n link to sign in to your account\n </p>\n\n <div className=\"text-center px-6 py-3\">\n <Button\n href={variables.signInInvitationLink}\n className=\"text-black text-sm font-sans font-bold bg-gray-200 rounded-md inline-block py-3 px-5 no-underline border-none\"\n >\n Sign in\n </Button>\n </div>\n\n <div className=\"px-6 py-4\">\n <Hr className=\"opacity-20\" />\n </div>\n\n <p className=\"text-xs font-sans font-normal text-center pt-1 pb-6 px-6 m-0 opacity-60\">\n If you were not expecting this email, you can safely ignore it.\n </p>\n </Section>\n </div>\n </>\n )\n}\n\nEmailTemplate.PreviewVariables = {\n signInInvitationLink: \"#\",\n teamDisplayName: \"My Team\"\n} satisfies typeof variablesSchema.infer",
|
|
228
|
+
"themeId": undefined,
|
|
229
|
+
},
|
|
230
|
+
[EMAIL_TEMPLATE_PAYMENT_RECEIPT_ID]: {
|
|
231
|
+
"displayName": "Payment Receipt",
|
|
232
|
+
"tsxSource": "import { type } from \"arktype\";\nimport { Button, Section, Hr } from \"@react-email/components\";\nimport { Subject, NotificationCategory, Props } from \"@stackframe/emails\";\n\nexport const variablesSchema = type({\n productName: \"string\",\n quantity: \"number\",\n amount: \"string\",\n receiptLink: \"string?\"\n});\n\nexport function EmailTemplate({ user, project, variables }: Props<typeof variablesSchema.infer>) {\n return (\n <>\n <Subject value={\"Your receipt from \" + project.displayName} />\n <NotificationCategory value=\"Transactional\" />\n <div className=\"bg-white text-[#242424] font-sans text-base font-normal tracking-[0.15008px] leading-[1.5] m-0 py-8 w-full min-h-full\">\n <Section className=\"bg-white\">\n <h3 className=\"text-black font-sans font-bold text-[20px] text-center py-4 px-6 m-0\">\n Thanks for your purchase\n </h3>\n <p className=\"text-[#474849] font-sans font-normal text-[14px] text-center pt-2 px-6 pb-4 m-0\">\n Hi{user.displayName ? (\", \" + user.displayName) : \"\"}! Your payment for {variables.productName} is complete.\n </p>\n <div className=\"px-6\">\n <p className=\"text-black font-sans font-bold text-[16px] text-center m-0\">\n Total paid: {variables.amount}\n </p>\n <p className=\"text-[#474849] font-sans text-[13px] text-center mt-2 mb-0\">\n Quantity: {variables.quantity}\n </p>\n </div>\n {variables.receiptLink ? (\n <div className=\"text-center py-4 px-6\">\n <Button\n href={variables.receiptLink}\n target=\"_blank\"\n className=\"text-black font-sans font-bold text-[14px] inline-block bg-[#f0f0f0] rounded-[4px] py-3 px-5 no-underline border-0\"\n >\n View receipt\n </Button>\n </div>\n ) : null}\n <div className=\"py-4 px-6\">\n <Hr />\n </div>\n </Section>\n </div>\n </>\n );\n}\n\nEmailTemplate.PreviewVariables = {\n productName: \"Pro Plan\",\n quantity: 1,\n amount: \"$29.00\",\n receiptLink: \"https://example.com/receipt\"\n} satisfies typeof variablesSchema.infer;\n",
|
|
233
|
+
"themeId": undefined,
|
|
234
|
+
},
|
|
235
|
+
[EMAIL_TEMPLATE_PAYMENT_FAILED_ID]: {
|
|
236
|
+
"displayName": "Payment Failed",
|
|
237
|
+
"tsxSource": "import { type } from \"arktype\";\nimport { Button, Section, Hr } from \"@react-email/components\";\nimport { Subject, NotificationCategory, Props } from \"@stackframe/emails\";\n\nexport const variablesSchema = type({\n productName: \"string\",\n amount: \"string\",\n invoiceUrl: \"string?\",\n failureReason: \"string?\"\n});\n\nexport function EmailTemplate({ user, project, variables }: Props<typeof variablesSchema.infer>) {\n return (\n <>\n <Subject value={\"Payment failed for \" + project.displayName} />\n <NotificationCategory value=\"Transactional\" />\n <div className=\"bg-white text-[#242424] font-sans text-base font-normal tracking-[0.15008px] leading-[1.5] m-0 py-8 w-full min-h-full\">\n <Section className=\"bg-white\">\n <h3 className=\"text-black font-sans font-bold text-[20px] text-center py-4 px-6 m-0\">\n We couldn't process your payment\n </h3>\n <p className=\"text-[#474849] font-sans font-normal text-[14px] text-center pt-2 px-6 pb-4 m-0\">\n Hi{user.displayName ? (\", \" + user.displayName) : \"\"}! Your payment for {variables.productName} ({variables.amount}) did not go through.\n </p>\n {variables.failureReason ? (\n <p className=\"text-[#8a8b8b] font-sans text-[12px] text-center px-6 pb-2 m-0\">\n Reason: {variables.failureReason}\n </p>\n ) : null}\n {variables.invoiceUrl ? (\n <div className=\"text-center py-3 px-6\">\n <Button\n href={variables.invoiceUrl}\n target=\"_blank\"\n className=\"text-black font-sans font-bold text-[14px] inline-block bg-[#f0f0f0] rounded-[4px] py-3 px-5 no-underline border-0\"\n >\n View invoice\n </Button>\n </div>\n ) : null}\n <div className=\"py-4 px-6\">\n <Hr />\n </div>\n <p className=\"text-[#474849] font-sans font-normal text-[12px] text-center pt-1 px-6 pb-6 m-0\">\n Please update your payment details to avoid service interruption.\n </p>\n </Section>\n </div>\n </>\n );\n}\n\nEmailTemplate.PreviewVariables = {\n productName: \"Pro Plan\",\n amount: \"$29.00\",\n invoiceUrl: \"https://example.com/billing\",\n failureReason: \"Your card was declined\"\n} satisfies typeof variablesSchema.infer;\n",
|
|
238
|
+
"themeId": undefined,
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const DEFAULT_TEMPLATE_IDS = {
|
|
243
|
+
email_verification: EMAIL_TEMPLATE_EMAIL_VERIFICATION_ID,
|
|
244
|
+
password_reset: EMAIL_TEMPLATE_PASSWORD_RESET_ID,
|
|
245
|
+
magic_link: EMAIL_TEMPLATE_MAGIC_LINK_ID,
|
|
246
|
+
team_invitation: EMAIL_TEMPLATE_TEAM_INVITATION_ID,
|
|
247
|
+
sign_in_invitation: EMAIL_TEMPLATE_SIGN_IN_INVITATION_ID,
|
|
248
|
+
payment_receipt: EMAIL_TEMPLATE_PAYMENT_RECEIPT_ID,
|
|
249
|
+
payment_failed: EMAIL_TEMPLATE_PAYMENT_FAILED_ID,
|
|
250
|
+
} as const;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { aiSetupPrompt } from "../ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt";
|
|
2
|
+
|
|
3
|
+
function getCliProjectSetupContext(configPath?: string): string {
|
|
4
|
+
if (configPath != null) {
|
|
5
|
+
return `
|
|
6
|
+
The Hexclave CLI already created or linked this project to a local Hexclave development environment config file:
|
|
7
|
+
|
|
8
|
+
\`\`\`text
|
|
9
|
+
${configPath}
|
|
10
|
+
\`\`\`
|
|
11
|
+
|
|
12
|
+
Do not create or link another Hexclave project. When the SDK setup instructions mention creating \`stack.config.ts\` or wrapping the dev script with \`stack dev --config-file\`, use the config file path above.
|
|
13
|
+
`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return `
|
|
17
|
+
The Hexclave CLI already created or linked this project to a hosted Hexclave cloud project and wrote or printed the Hexclave environment variables.
|
|
18
|
+
|
|
19
|
+
Do not create or link another Hexclave project. Use the existing environment variables in this workspace. If the variables were printed instead of written because the user declined to append them to an env file, tell the user exactly which variables still need to be added.
|
|
20
|
+
`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const createInitPrompt = (web: boolean, configPath?: string) => `=============================
|
|
24
|
+
HEXCLAVE SETUP INSTRUCTIONS
|
|
25
|
+
=============================
|
|
26
|
+
|
|
27
|
+
These instructions describe how to set up Hexclave.
|
|
28
|
+
${web ? `
|
|
29
|
+
First of all, if you have access to a terminal, it is better to use \`npx @hexclave/cli init\`.
|
|
30
|
+
` : ""}
|
|
31
|
+
|
|
32
|
+
${getCliProjectSetupContext(configPath)}
|
|
33
|
+
|
|
34
|
+
Use the full setup guide below as the source of truth, with one important CLI-specific adjustment: the "Setting up the project" step is already complete. Use that section only to understand how the existing config/env files should connect to the SDK wiring; do not ask the user for project IDs or keys that the CLI already generated or linked.
|
|
35
|
+
|
|
36
|
+
Apply only the sections relevant to this project. For example, do not add Convex, Supabase, or command-line-app authentication unless the existing project already uses that surface or the user explicitly asked for it.
|
|
37
|
+
|
|
38
|
+
${aiSetupPrompt}
|
|
39
|
+
`;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { KnownErrors } from "..";
|
|
2
|
+
|
|
3
|
+
const minLength = 8;
|
|
4
|
+
const maxLength = 70;
|
|
5
|
+
|
|
6
|
+
export function getPasswordError(password: string): KnownErrors["PasswordRequirementsNotMet"] | undefined {
|
|
7
|
+
if (password.length < minLength) {
|
|
8
|
+
return new KnownErrors.PasswordTooShort(minLength);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (password.length > maxLength) {
|
|
12
|
+
return new KnownErrors.PasswordTooLong(maxLength);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ProjectsCrud } from "../interface/crud/projects";
|
|
2
|
+
import { HexclaveAssertionError, captureError } from "../utils/errors";
|
|
3
|
+
import { isLocalhost } from "../utils/urls";
|
|
4
|
+
|
|
5
|
+
export type ProductionModeError = {
|
|
6
|
+
message: string,
|
|
7
|
+
relativeFixUrl: `/${string}`,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function getProductionModeErrors(project: ProjectsCrud["Admin"]["Read"]): ProductionModeError[] {
|
|
11
|
+
const errors: ProductionModeError[] = [];
|
|
12
|
+
const domainsFixUrl = `/projects/${project.id}/domains` as const;
|
|
13
|
+
|
|
14
|
+
if (project.config.allow_localhost) {
|
|
15
|
+
errors.push({
|
|
16
|
+
message: "Localhost is not allowed in production mode, turn off 'Allow localhost' in project settings",
|
|
17
|
+
relativeFixUrl: domainsFixUrl,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const { domain } of project.config.domains) {
|
|
22
|
+
let url;
|
|
23
|
+
try {
|
|
24
|
+
// For wildcard domains, replace wildcards with a valid placeholder to validate the URL structure
|
|
25
|
+
const normalizedDomain = domain.replace(/\*+/g, 'wildcard-placeholder');
|
|
26
|
+
url = new URL(normalizedDomain);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
captureError("production-mode-domain-not-valid", new HexclaveAssertionError("Domain was somehow not a valid URL; we should've caught this when setting the domain in the first place", {
|
|
29
|
+
domain,
|
|
30
|
+
projectId: project
|
|
31
|
+
}));
|
|
32
|
+
errors.push({
|
|
33
|
+
message: "Trusted domain is not a valid URL: " + domain,
|
|
34
|
+
relativeFixUrl: domainsFixUrl,
|
|
35
|
+
});
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isLocalhost(url)) {
|
|
40
|
+
errors.push({
|
|
41
|
+
message: "Localhost domains are not allowed to be trusted in production mode: " + domain,
|
|
42
|
+
relativeFixUrl: domainsFixUrl,
|
|
43
|
+
});
|
|
44
|
+
} else if (url.hostname.match(/^\d+(\.\d+)*$/)) {
|
|
45
|
+
errors.push({
|
|
46
|
+
message: "Direct IPs are not valid for trusted domains in production mode: " + domain,
|
|
47
|
+
relativeFixUrl: domainsFixUrl,
|
|
48
|
+
});
|
|
49
|
+
} else if (url.protocol !== "https:") {
|
|
50
|
+
errors.push({
|
|
51
|
+
message: "Trusted domains should be HTTPS: " + domain,
|
|
52
|
+
relativeFixUrl: domainsFixUrl,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return errors;
|
|
58
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
computeFirstResponseDueAt,
|
|
4
|
+
computeNextResponseDueAt,
|
|
5
|
+
computeSlaUrgency,
|
|
6
|
+
SLA_URGENT_THRESHOLD_MS,
|
|
7
|
+
SLA_WARNING_THRESHOLD_MS,
|
|
8
|
+
type SupportSlaConfig,
|
|
9
|
+
} from "./support-sla";
|
|
10
|
+
|
|
11
|
+
const enabledSla: SupportSlaConfig = {
|
|
12
|
+
enabled: true,
|
|
13
|
+
firstResponseMinutes: 60,
|
|
14
|
+
nextResponseMinutes: 120,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe("computeFirstResponseDueAt", () => {
|
|
18
|
+
const now = new Date("2026-04-22T12:00:00.000Z");
|
|
19
|
+
|
|
20
|
+
it("returns null when SLA is disabled", () => {
|
|
21
|
+
expect(computeFirstResponseDueAt(now, { ...enabledSla, enabled: false })).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns null when no target is set", () => {
|
|
25
|
+
expect(computeFirstResponseDueAt(now, { ...enabledSla, firstResponseMinutes: null })).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("adds the configured minutes to now", () => {
|
|
29
|
+
const due = computeFirstResponseDueAt(now, enabledSla);
|
|
30
|
+
expect(due).not.toBeNull();
|
|
31
|
+
expect(due!.toISOString()).toBe("2026-04-22T13:00:00.000Z");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns null when the configured value is non-positive", () => {
|
|
35
|
+
expect(computeFirstResponseDueAt(now, { ...enabledSla, firstResponseMinutes: 0 })).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("computeNextResponseDueAt", () => {
|
|
40
|
+
const now = new Date("2026-04-22T12:00:00.000Z");
|
|
41
|
+
|
|
42
|
+
it("returns null when SLA is disabled", () => {
|
|
43
|
+
expect(computeNextResponseDueAt(now, { ...enabledSla, enabled: false })).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns null when no target is set", () => {
|
|
47
|
+
expect(computeNextResponseDueAt(now, { ...enabledSla, nextResponseMinutes: null })).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("adds the configured next-response minutes to now", () => {
|
|
51
|
+
const due = computeNextResponseDueAt(now, enabledSla);
|
|
52
|
+
expect(due!.toISOString()).toBe("2026-04-22T14:00:00.000Z");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("computeSlaUrgency", () => {
|
|
57
|
+
const now = new Date("2026-04-22T12:00:00.000Z");
|
|
58
|
+
|
|
59
|
+
it("returns 'overdue' when the due timestamp is in the past", () => {
|
|
60
|
+
expect(computeSlaUrgency(new Date(now.getTime() - 1), now)).toBe("overdue");
|
|
61
|
+
expect(computeSlaUrgency(new Date(now.getTime() - 60_000), now)).toBe("overdue");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("returns 'overdue' when the due timestamp is exactly now", () => {
|
|
65
|
+
expect(computeSlaUrgency(now, now)).toBe("overdue");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("returns 'urgent' within the urgent threshold", () => {
|
|
69
|
+
expect(computeSlaUrgency(new Date(now.getTime() + 60_000), now)).toBe("urgent");
|
|
70
|
+
expect(computeSlaUrgency(new Date(now.getTime() + SLA_URGENT_THRESHOLD_MS), now)).toBe("urgent");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("returns 'warning' within the warning threshold", () => {
|
|
74
|
+
expect(computeSlaUrgency(new Date(now.getTime() + SLA_URGENT_THRESHOLD_MS + 60_000), now)).toBe("warning");
|
|
75
|
+
expect(computeSlaUrgency(new Date(now.getTime() + SLA_WARNING_THRESHOLD_MS), now)).toBe("warning");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("returns 'ok' when comfortably before the warning threshold", () => {
|
|
79
|
+
expect(computeSlaUrgency(new Date(now.getTime() + SLA_WARNING_THRESHOLD_MS + 60_000), now)).toBe("ok");
|
|
80
|
+
expect(computeSlaUrgency(new Date(now.getTime() + 24 * 60 * 60 * 1000), now)).toBe("ok");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("with a windowStartedAt", () => {
|
|
84
|
+
it("scales thresholds down for short SLA windows so t=0 is not immediately urgent", () => {
|
|
85
|
+
// 5-minute SLA window: due 5 min after it started.
|
|
86
|
+
const windowStartedAt = now;
|
|
87
|
+
const dueAt = new Date(now.getTime() + 5 * 60_000);
|
|
88
|
+
// Right at the start, 5 min remaining — should be 'ok' (>50% of window remaining).
|
|
89
|
+
expect(computeSlaUrgency(dueAt, now, { windowStartedAt })).toBe("ok");
|
|
90
|
+
// At t+2min (3 min remaining, 60% of window remaining) — still 'ok'.
|
|
91
|
+
const twoMinLater = new Date(now.getTime() + 2 * 60_000);
|
|
92
|
+
expect(computeSlaUrgency(dueAt, twoMinLater, { windowStartedAt })).toBe("ok");
|
|
93
|
+
// At t+3min (2 min remaining, 40% of window) — 'warning'.
|
|
94
|
+
const threeMinLater = new Date(now.getTime() + 3 * 60_000);
|
|
95
|
+
expect(computeSlaUrgency(dueAt, threeMinLater, { windowStartedAt })).toBe("warning");
|
|
96
|
+
// At t+4min (1 min remaining, 20% of window) — 'urgent'.
|
|
97
|
+
const fourMinLater = new Date(now.getTime() + 4 * 60_000);
|
|
98
|
+
expect(computeSlaUrgency(dueAt, fourMinLater, { windowStartedAt })).toBe("urgent");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("caps thresholds at the fixed wall-clock windows for long SLAs", () => {
|
|
102
|
+
// 24-hour SLA window.
|
|
103
|
+
const windowStartedAt = now;
|
|
104
|
+
const dueAt = new Date(now.getTime() + 24 * 60 * 60 * 1000);
|
|
105
|
+
// 2 hours remaining (long past the 50% mark of the 24h window) — should
|
|
106
|
+
// still be 'ok' because the warning cap is 60 min.
|
|
107
|
+
const tMinusTwoHours = new Date(dueAt.getTime() - 2 * 60 * 60 * 1000);
|
|
108
|
+
expect(computeSlaUrgency(dueAt, tMinusTwoHours, { windowStartedAt })).toBe("ok");
|
|
109
|
+
// 45 minutes remaining — 'warning' (inside the 60-min cap).
|
|
110
|
+
const tMinus45Min = new Date(dueAt.getTime() - 45 * 60_000);
|
|
111
|
+
expect(computeSlaUrgency(dueAt, tMinus45Min, { windowStartedAt })).toBe("warning");
|
|
112
|
+
// 10 minutes remaining — 'urgent' (inside the 15-min cap).
|
|
113
|
+
const tMinus10Min = new Date(dueAt.getTime() - 10 * 60_000);
|
|
114
|
+
expect(computeSlaUrgency(dueAt, tMinus10Min, { windowStartedAt })).toBe("urgent");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("falls back to fixed thresholds when windowStartedAt is nullish", () => {
|
|
118
|
+
const dueAt = new Date(now.getTime() + 30 * 60_000);
|
|
119
|
+
expect(computeSlaUrgency(dueAt, now)).toBe("warning");
|
|
120
|
+
expect(computeSlaUrgency(dueAt, now, { windowStartedAt: null })).toBe("warning");
|
|
121
|
+
expect(computeSlaUrgency(dueAt, now, { windowStartedAt: undefined })).toBe("warning");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export type SupportSlaConfig = {
|
|
2
|
+
enabled: boolean,
|
|
3
|
+
firstResponseMinutes: number | null,
|
|
4
|
+
nextResponseMinutes: number | null,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_SUPPORT_SLA: SupportSlaConfig = {
|
|
8
|
+
enabled: false,
|
|
9
|
+
firstResponseMinutes: null,
|
|
10
|
+
nextResponseMinutes: null,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function addMinutesOrNull(now: Date, minutes: number | null): Date | null {
|
|
14
|
+
if (minutes == null || !Number.isFinite(minutes) || minutes <= 0) return null;
|
|
15
|
+
return new Date(now.getTime() + minutes * 60_000);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns the target first-response due timestamp for a new conversation,
|
|
20
|
+
* or `null` if SLA tracking is off or no target is set.
|
|
21
|
+
*/
|
|
22
|
+
export function computeFirstResponseDueAt(
|
|
23
|
+
now: Date,
|
|
24
|
+
sla: SupportSlaConfig,
|
|
25
|
+
): Date | null {
|
|
26
|
+
if (!sla.enabled) return null;
|
|
27
|
+
return addMinutesOrNull(now, sla.firstResponseMinutes);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns the target next-response due timestamp when a conversation has been
|
|
32
|
+
* re-opened by a user reply, or `null` if SLA tracking is off or no target
|
|
33
|
+
* is set.
|
|
34
|
+
*/
|
|
35
|
+
export function computeNextResponseDueAt(
|
|
36
|
+
now: Date,
|
|
37
|
+
sla: SupportSlaConfig,
|
|
38
|
+
): Date | null {
|
|
39
|
+
if (!sla.enabled) return null;
|
|
40
|
+
return addMinutesOrNull(now, sla.nextResponseMinutes);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type SlaUrgency = "ok" | "warning" | "urgent" | "overdue";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* How soon (in milliseconds) before a due timestamp we switch into the
|
|
47
|
+
* "urgent" state. Within this window the due timestamp should be rendered
|
|
48
|
+
* with a strong alert treatment.
|
|
49
|
+
*/
|
|
50
|
+
export const SLA_URGENT_THRESHOLD_MS = 15 * 60 * 1000;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* How soon (in milliseconds) before a due timestamp we switch into the
|
|
54
|
+
* "warning" state. Within this window the due timestamp should be rendered
|
|
55
|
+
* with a subtle alert treatment so agents can notice it without being
|
|
56
|
+
* distracted.
|
|
57
|
+
*/
|
|
58
|
+
export const SLA_WARNING_THRESHOLD_MS = 60 * 60 * 1000;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Classifies how close a due timestamp is to `now` so we can subtly alert
|
|
62
|
+
* agents as it approaches (and after it passes).
|
|
63
|
+
*
|
|
64
|
+
* If `windowStartedAt` is provided, thresholds scale with the total SLA
|
|
65
|
+
* window length - so a 5-minute SLA doesn't read as "urgent" from t=0 - and
|
|
66
|
+
* are capped by the fixed wall-clock thresholds so a 24-hour SLA doesn't
|
|
67
|
+
* sit in "warning" for 12 hours. If `windowStartedAt` isn't known, we fall
|
|
68
|
+
* back to the fixed wall-clock thresholds.
|
|
69
|
+
*/
|
|
70
|
+
export function computeSlaUrgency(
|
|
71
|
+
dueAt: Date,
|
|
72
|
+
now: Date,
|
|
73
|
+
options?: { windowStartedAt?: Date | null },
|
|
74
|
+
): SlaUrgency {
|
|
75
|
+
const remainingMs = dueAt.getTime() - now.getTime();
|
|
76
|
+
if (remainingMs <= 0) return "overdue";
|
|
77
|
+
|
|
78
|
+
const windowStartedAt = options?.windowStartedAt ?? null;
|
|
79
|
+
const totalWindowMs = windowStartedAt != null
|
|
80
|
+
? Math.max(1, dueAt.getTime() - windowStartedAt.getTime())
|
|
81
|
+
: null;
|
|
82
|
+
|
|
83
|
+
const urgentThresholdMs = totalWindowMs != null
|
|
84
|
+
? Math.min(totalWindowMs * 0.25, SLA_URGENT_THRESHOLD_MS)
|
|
85
|
+
: SLA_URGENT_THRESHOLD_MS;
|
|
86
|
+
const warningThresholdMs = totalWindowMs != null
|
|
87
|
+
? Math.min(totalWindowMs * 0.5, SLA_WARNING_THRESHOLD_MS)
|
|
88
|
+
: SLA_WARNING_THRESHOLD_MS;
|
|
89
|
+
|
|
90
|
+
if (remainingMs <= urgentThresholdMs) return "urgent";
|
|
91
|
+
if (remainingMs <= warningThresholdMs) return "warning";
|
|
92
|
+
return "ok";
|
|
93
|
+
}
|