create-m5kdev 0.25.3 → 0.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/dist/src/create.cjs +18 -1
  2. package/dist/src/create.cjs.map +1 -1
  3. package/dist/src/fs.cjs +65 -5
  4. package/dist/src/fs.cjs.map +1 -1
  5. package/dist/src/fs.d.cts +13 -2
  6. package/dist/src/index.cjs +0 -0
  7. package/dist/src/paths.cjs +2 -2
  8. package/dist/src/prompts.cjs +22 -0
  9. package/dist/src/prompts.cjs.map +1 -1
  10. package/dist/src/runCli.cjs +13 -2
  11. package/dist/src/runCli.cjs.map +1 -1
  12. package/dist/src/types.d.cts +9 -1
  13. package/package.json +8 -6
  14. package/templates/minimal-app/.cursor/rules/frontend-data-guide.mdc +46 -3
  15. package/templates/minimal-app/.cursor/rules/module-db-guide.mdc +15 -1
  16. package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +60 -0
  17. package/templates/minimal-app/.cursor/rules/module-repository-guide.mdc +98 -193
  18. package/templates/minimal-app/.cursor/rules/module-schema-guide.mdc +53 -173
  19. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +133 -240
  20. package/templates/minimal-app/.cursor/rules/module-trpc-guide.mdc +71 -113
  21. package/templates/minimal-app/README.md.tpl +3 -3
  22. package/templates/minimal-app/apps/.gitignore.tpl +13 -0
  23. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -0
  24. package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +39 -0
  25. package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -0
  26. package/templates/minimal-app/apps/e2e/servers.ts.tpl +100 -0
  27. package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -0
  28. package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -0
  29. package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -0
  30. package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -0
  31. package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -0
  32. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -0
  33. package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -0
  34. package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -0
  35. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  36. package/templates/minimal-app/apps/email/src/emails/accountDeletionEmail.tsx.tpl +35 -34
  37. package/templates/minimal-app/apps/email/src/emails/organizationInviteEmail.tsx.tpl +34 -33
  38. package/templates/minimal-app/apps/email/src/emails/passwordResetEmail.tsx.tpl +35 -34
  39. package/templates/minimal-app/apps/email/src/emails/verificationEmail.tsx.tpl +37 -36
  40. package/templates/minimal-app/apps/email/src/emails/waitlistEmails.tsx.tpl +87 -0
  41. package/templates/minimal-app/apps/email/src/index.ts.tpl +72 -32
  42. package/templates/minimal-app/apps/email/src/resources.ts.tpl +65 -36
  43. package/templates/minimal-app/apps/email/tsconfig.json.tpl +16 -11
  44. package/templates/minimal-app/apps/expo/.gitignore.tpl +44 -0
  45. package/templates/minimal-app/apps/expo/.prettierignore +13 -0
  46. package/templates/minimal-app/apps/expo/.prettierrc.json.tpl +9 -0
  47. package/templates/minimal-app/apps/expo/CHANGELOG.md.tpl +156 -0
  48. package/templates/minimal-app/apps/expo/README.md.tpl +44 -0
  49. package/templates/minimal-app/apps/expo/app.json.tpl +36 -0
  50. package/templates/minimal-app/apps/expo/assets/images/adaptive-icon.png +0 -0
  51. package/templates/minimal-app/apps/expo/assets/images/favicon.png +0 -0
  52. package/templates/minimal-app/apps/expo/assets/images/icon.png +0 -0
  53. package/templates/minimal-app/apps/expo/assets/images/splash-icon.png +0 -0
  54. package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -0
  55. package/templates/minimal-app/apps/expo/expo-env.d.ts.tpl +3 -0
  56. package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -0
  57. package/templates/minimal-app/apps/expo/package.json.tpl +59 -0
  58. package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -0
  59. package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -0
  60. package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -0
  61. package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -0
  62. package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -0
  63. package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -0
  64. package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -0
  65. package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -0
  66. package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -0
  67. package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -0
  68. package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -0
  69. package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -0
  70. package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -0
  71. package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -0
  72. package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -0
  73. package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -0
  74. package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -0
  75. package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -0
  76. package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -0
  77. package/templates/minimal-app/apps/expo/uniwind-types.d.ts.tpl +10 -0
  78. package/templates/minimal-app/apps/server/AGENTS.md.tpl +39 -39
  79. package/templates/minimal-app/apps/server/drizzle/0000_wealthy_absorbing_man.sql +268 -0
  80. package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +26 -0
  81. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +1890 -0
  82. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +13 -0
  83. package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +33 -0
  84. package/templates/minimal-app/apps/server/drizzle/seed.e2e.ts.tpl +244 -0
  85. package/templates/minimal-app/apps/server/drizzle/seed.ts.tpl +181 -180
  86. package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +14 -21
  87. package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +33 -35
  88. package/templates/minimal-app/apps/server/package.json.tpl +56 -52
  89. package/templates/minimal-app/apps/server/src/app.ts.tpl +142 -126
  90. package/templates/minimal-app/apps/server/src/index.ts.tpl +58 -58
  91. package/templates/minimal-app/apps/server/src/lib/auth.ts.tpl +12 -12
  92. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +25 -25
  93. package/templates/minimal-app/apps/server/src/modules/posts/posts.dto.ts.tpl +23 -0
  94. package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +30 -40
  95. package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +54 -54
  96. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -112
  97. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +104 -170
  98. package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -48
  99. package/templates/minimal-app/apps/server/src/modules/posts/posts.utils.ts.tpl +19 -0
  100. package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -0
  101. package/templates/minimal-app/apps/server/src/schema.ts.tpl +20 -0
  102. package/templates/minimal-app/apps/server/src/types.ts.tpl +1 -1
  103. package/templates/minimal-app/apps/server/tsconfig.json.tpl +20 -14
  104. package/templates/minimal-app/apps/server/tsup.config.ts.tpl +9 -9
  105. package/templates/minimal-app/apps/shared/package.json.tpl +24 -24
  106. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -2
  107. package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
  108. package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
  109. package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -76
  110. package/templates/minimal-app/apps/shared/tsconfig.json.tpl +16 -12
  111. package/templates/minimal-app/apps/webapp/index.html.tpl +22 -22
  112. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -52
  113. package/templates/minimal-app/apps/webapp/public/{push-sw.js → push-sw.js.tpl} +34 -34
  114. package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +44 -13
  115. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +18 -12
  116. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +33 -40
  117. package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -75
  118. package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
  119. package/templates/minimal-app/apps/webapp/src/modules/posts/PostsRoute.tsx.tpl +278 -307
  120. package/templates/minimal-app/apps/webapp/src/modules/posts/components/PostCard.tsx.tpl +96 -138
  121. package/templates/minimal-app/apps/webapp/src/modules/posts/components/PostEditorModal.tsx.tpl +118 -124
  122. package/templates/minimal-app/apps/webapp/src/modules/posts/hooks/usePostActions.ts.tpl +108 -116
  123. package/templates/minimal-app/apps/webapp/src/modules/posts/hooks/usePostsList.ts.tpl +15 -9
  124. package/templates/minimal-app/apps/webapp/src/modules/posts/hooks/usePostsRoute.ts.tpl +13 -13
  125. package/templates/minimal-app/apps/webapp/src/modules/posts/posts.utils.ts.tpl +16 -16
  126. package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
  127. package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -6
  128. package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
  129. package/templates/minimal-app/apps/webapp/translations/{app.json.tpl → en/app.json.tpl} +7 -7
  130. package/templates/minimal-app/apps/webapp/translations/en/{blog-app.json.tpl → {{APP_SLUG}}.json.tpl} +135 -135
  131. package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
  132. package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
  133. package/templates/minimal-app/template.manifest.json +21 -0
  134. package/templates/minimal-app/apps/server/drizzle/generate-schema.ts.tpl +0 -31
  135. package/templates/minimal-app/apps/server/src/db.ts.tpl +0 -7
  136. package/templates/minimal-app/apps/server/src/generated/schema.ts.tpl +0 -13
  137. package/templates/minimal-app/apps/server/src/utils/trpc.ts.tpl +0 -8
  138. package/templates/minimal-app/apps/shared/src/modules/app/locale.constants.ts.tpl +0 -7
  139. package/templates/minimal-app/apps/shared/src/modules/app/roles.constants.ts.tpl +0 -17
  140. package/templates/minimal-app/apps/webapp/src/Providers.tsx.tpl +0 -36
  141. package/templates/minimal-app/apps/webapp/src/modules/notification/PushNotificationsPanel.tsx.tpl +0 -89
  142. package/templates/minimal-app/apps/webapp/src/navigation/navigation.config.tsx.tpl +0 -85
  143. /package/templates/minimal-app/apps/webapp/public/{logo.svg → logo.svg.tpl} +0 -0
@@ -1,34 +1,34 @@
1
- {
2
- "name": "{{PACKAGE_SCOPE}}/email",
3
- "version": "0.0.0",
4
- "private": true,
5
- "scripts": {
6
- "build": "tsc --noEmit",
7
- "lint": "biome check .",
8
- "lint:fix": "biome check . --write",
9
- "check-types": "tsc --noEmit",
10
- "email:dev": "react-email dev --dir src/emails"
11
- },
12
- "dependencies": {
13
- "@m5kdev/commons": "catalog:",
14
- "@m5kdev/email": "catalog:",
15
- "@react-email/components": "catalog:",
16
- "react": "catalog:",
17
- "react-dom": "catalog:"
18
- },
19
- "devDependencies": {
20
- "@m5kdev/backend": "catalog:",
21
- "@m5kdev/config": "catalog:",
22
- "@types/node": "catalog:",
23
- "@types/react": "catalog:",
24
- "@types/react-dom": "catalog:",
25
- "react-email": "catalog:",
26
- "tslib": "catalog:",
27
- "typescript": "catalog:"
28
- },
29
- "main": "./src/index.ts",
30
- "exports": {
31
- ".": "./src/index.ts",
32
- "./resources": "./src/resources.ts"
33
- }
34
- }
1
+ {
2
+ "name": "{{PACKAGE_SCOPE}}/email",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "build": "tsc --noEmit",
7
+ "lint": "biome check .",
8
+ "lint:fix": "biome check . --write",
9
+ "check-types": "tsc --noEmit",
10
+ "email:dev": "react-email dev --dir src/emails"
11
+ },
12
+ "dependencies": {
13
+ "@m5kdev/commons": "catalog:",
14
+ "@m5kdev/email": "catalog:",
15
+ "@react-email/components": "catalog:",
16
+ "react": "catalog:",
17
+ "react-dom": "catalog:"
18
+ },
19
+ "devDependencies": {
20
+ "@m5kdev/backend": "catalog:",
21
+ "@m5kdev/config": "catalog:",
22
+ "@types/node": "catalog:",
23
+ "@types/react": "catalog:",
24
+ "@types/react-dom": "catalog:",
25
+ "react-email": "catalog:",
26
+ "tslib": "catalog:",
27
+ "typescript": "catalog:"
28
+ },
29
+ "main": "./src/index.ts",
30
+ "exports": {
31
+ ".": "./src/index.ts",
32
+ "./resources": "./src/resources.ts"
33
+ }
34
+ }
@@ -1,34 +1,35 @@
1
- import { Heading, Text } from "@react-email/components";
2
- import { CtaButton } from "@m5kdev/email/components/CtaButton";
3
- import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
4
- import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
5
-
6
- function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
7
- return t ?? ((key: string) => key);
8
- }
9
-
10
- export default function AccountDeletionEmail({
11
- previewText,
12
- url,
13
- brand,
14
- t,
15
- htmlLang,
16
- }: {
17
- previewText: string;
18
- url: string;
19
- brand: Brand;
20
- t?: EmailTranslateFn;
21
- htmlLang?: string;
22
- }) {
23
- const translate = resolveT(t);
24
-
25
- return (
26
- <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
27
- <Heading className="mb-4 text-2xl font-bold text-black">
28
- {translate("accountDeletion.title")}
29
- </Heading>
30
- <Text className="mb-6 text-base text-gray-700">{translate("accountDeletion.body")}</Text>
31
- <CtaButton href={url}>{translate("accountDeletion.action")}</CtaButton>
32
- </EmailLayout>
33
- );
34
- }
1
+ /** @jsxRuntime automatic */
2
+ import { Heading, Text } from "@react-email/components";
3
+ import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
+ import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
+ import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
6
+
7
+ function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
8
+ return t ?? ((key: string) => key);
9
+ }
10
+
11
+ export default function AccountDeletionEmail({
12
+ previewText,
13
+ url,
14
+ brand,
15
+ t,
16
+ htmlLang,
17
+ }: {
18
+ previewText: string;
19
+ url: string;
20
+ brand: Brand;
21
+ t?: EmailTranslateFn;
22
+ htmlLang?: string;
23
+ }) {
24
+ const translate = resolveT(t);
25
+
26
+ return (
27
+ <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
28
+ <Heading className="mb-4 text-2xl font-bold text-black">
29
+ {translate("accountDeletion.title")}
30
+ </Heading>
31
+ <Text className="mb-6 text-base text-gray-700">{translate("accountDeletion.body")}</Text>
32
+ <CtaButton href={url}>{translate("accountDeletion.action")}</CtaButton>
33
+ </EmailLayout>
34
+ );
35
+ }
@@ -1,33 +1,34 @@
1
- import { Heading, Text } from "@react-email/components";
2
- import { CtaButton } from "@m5kdev/email/components/CtaButton";
3
- import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
4
- import type { EmailTranslateFn, OrganizationInviteTemplateProps } from "@m5kdev/email/types";
5
-
6
- function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
7
- return t ?? ((key: string) => key);
8
- }
9
-
10
- export default function OrganizationInviteEmail({
11
- previewText,
12
- inviterName,
13
- organizationName,
14
- role,
15
- url,
16
- brand,
17
- t,
18
- htmlLang,
19
- }: OrganizationInviteTemplateProps) {
20
- const translate = resolveT(t);
21
-
22
- return (
23
- <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
24
- <Heading className="mb-4 text-2xl font-bold text-black">
25
- {translate("organizationInvite.title", { organizationName })}
26
- </Heading>
27
- <Text className="mb-6 text-base text-gray-700">
28
- {translate("organizationInvite.body", { inviterName, organizationName, role })}
29
- </Text>
30
- <CtaButton href={url}>{translate("organizationInvite.action")}</CtaButton>
31
- </EmailLayout>
32
- );
33
- }
1
+ /** @jsxRuntime automatic */
2
+ import { Heading, Text } from "@react-email/components";
3
+ import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
+ import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
+ import type { EmailTranslateFn, OrganizationInviteTemplateProps } from "@m5kdev/email/types";
6
+
7
+ function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
8
+ return t ?? ((key: string) => key);
9
+ }
10
+
11
+ export default function OrganizationInviteEmail({
12
+ previewText,
13
+ inviterName,
14
+ organizationName,
15
+ role,
16
+ url,
17
+ brand,
18
+ t,
19
+ htmlLang,
20
+ }: OrganizationInviteTemplateProps) {
21
+ const translate = resolveT(t);
22
+
23
+ return (
24
+ <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
25
+ <Heading className="mb-4 text-2xl font-bold text-black">
26
+ {translate("organizationInvite.title", { organizationName })}
27
+ </Heading>
28
+ <Text className="mb-6 text-base text-gray-700">
29
+ {translate("organizationInvite.body", { inviterName, organizationName, role })}
30
+ </Text>
31
+ <CtaButton href={url}>{translate("organizationInvite.action")}</CtaButton>
32
+ </EmailLayout>
33
+ );
34
+ }
@@ -1,34 +1,35 @@
1
- import { Heading, Text } from "@react-email/components";
2
- import { CtaButton } from "@m5kdev/email/components/CtaButton";
3
- import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
4
- import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
5
-
6
- function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
7
- return t ?? ((key: string) => key);
8
- }
9
-
10
- export default function PasswordResetEmail({
11
- previewText,
12
- url,
13
- brand,
14
- t,
15
- htmlLang,
16
- }: {
17
- previewText: string;
18
- url: string;
19
- brand: Brand;
20
- t?: EmailTranslateFn;
21
- htmlLang?: string;
22
- }) {
23
- const translate = resolveT(t);
24
-
25
- return (
26
- <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
27
- <Heading className="mb-4 text-2xl font-bold text-black">
28
- {translate("passwordReset.title")}
29
- </Heading>
30
- <Text className="mb-6 text-base text-gray-700">{translate("passwordReset.body")}</Text>
31
- <CtaButton href={url}>{translate("passwordReset.action")}</CtaButton>
32
- </EmailLayout>
33
- );
34
- }
1
+ /** @jsxRuntime automatic */
2
+ import { Heading, Text } from "@react-email/components";
3
+ import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
+ import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
+ import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
6
+
7
+ function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
8
+ return t ?? ((key: string) => key);
9
+ }
10
+
11
+ export default function PasswordResetEmail({
12
+ previewText,
13
+ url,
14
+ brand,
15
+ t,
16
+ htmlLang,
17
+ }: {
18
+ previewText: string;
19
+ url: string;
20
+ brand: Brand;
21
+ t?: EmailTranslateFn;
22
+ htmlLang?: string;
23
+ }) {
24
+ const translate = resolveT(t);
25
+
26
+ return (
27
+ <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
28
+ <Heading className="mb-4 text-2xl font-bold text-black">
29
+ {translate("passwordReset.title")}
30
+ </Heading>
31
+ <Text className="mb-6 text-base text-gray-700">{translate("passwordReset.body")}</Text>
32
+ <CtaButton href={url}>{translate("passwordReset.action")}</CtaButton>
33
+ </EmailLayout>
34
+ );
35
+ }
@@ -1,36 +1,37 @@
1
- import { Heading, Text } from "@react-email/components";
2
- import { CtaButton } from "@m5kdev/email/components/CtaButton";
3
- import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
4
- import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
5
-
6
- function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
7
- return t ?? ((key: string) => key);
8
- }
9
-
10
- export default function VerificationEmail({
11
- previewText,
12
- url,
13
- brand,
14
- t,
15
- htmlLang,
16
- }: {
17
- previewText: string;
18
- url: string;
19
- brand: Brand;
20
- t?: EmailTranslateFn;
21
- htmlLang?: string;
22
- }) {
23
- const translate = resolveT(t);
24
-
25
- return (
26
- <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
27
- <Heading className="mb-4 text-2xl font-bold text-black">
28
- {translate("verification.title")}
29
- </Heading>
30
- <Text className="mb-6 text-base text-gray-700">
31
- {translate("verification.body", { appName: "{{APP_NAME}}" })}
32
- </Text>
33
- <CtaButton href={url}>{translate("verification.action")}</CtaButton>
34
- </EmailLayout>
35
- );
36
- }
1
+ /** @jsxRuntime automatic */
2
+ import { Heading, Text } from "@react-email/components";
3
+ import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
+ import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
+ import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
6
+
7
+ function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
8
+ return t ?? ((key: string) => key);
9
+ }
10
+
11
+ export default function VerificationEmail({
12
+ previewText,
13
+ url,
14
+ brand,
15
+ t,
16
+ htmlLang,
17
+ }: {
18
+ previewText: string;
19
+ url: string;
20
+ brand: Brand;
21
+ t?: EmailTranslateFn;
22
+ htmlLang?: string;
23
+ }) {
24
+ const translate = resolveT(t);
25
+
26
+ return (
27
+ <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
28
+ <Heading className="mb-4 text-2xl font-bold text-black">
29
+ {translate("verification.title")}
30
+ </Heading>
31
+ <Text className="mb-6 text-base text-gray-700">
32
+ {translate("verification.body", { appName: "{{APP_NAME}}" })}
33
+ </Text>
34
+ <CtaButton href={url}>{translate("verification.action")}</CtaButton>
35
+ </EmailLayout>
36
+ );
37
+ }
@@ -0,0 +1,87 @@
1
+ /** @jsxRuntime automatic */
2
+ import { Heading, Text } from "@react-email/components";
3
+ import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
+ import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
+ import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
6
+
7
+ function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
8
+ return t ?? ((key: string) => key);
9
+ }
10
+
11
+ interface WaitlistEmailProps {
12
+ previewText: string;
13
+ url?: string;
14
+ brand: Brand;
15
+ t?: EmailTranslateFn;
16
+ htmlLang?: string;
17
+ }
18
+
19
+ function WaitlistActionEmail({
20
+ previewText,
21
+ url,
22
+ brand,
23
+ htmlLang,
24
+ title,
25
+ body,
26
+ action,
27
+ }: WaitlistEmailProps & { title: string; body: string; action: string }) {
28
+ return (
29
+ <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
30
+ <Heading className="mb-4 text-2xl font-bold text-black">{title}</Heading>
31
+ <Text className="mb-6 text-base text-gray-700">{body}</Text>
32
+ {url ? <CtaButton href={url}>{action}</CtaButton> : null}
33
+ </EmailLayout>
34
+ );
35
+ }
36
+
37
+ export function WaitlistConfirmationEmail(props: WaitlistEmailProps) {
38
+ const t = resolveT(props.t);
39
+ return (
40
+ <WaitlistActionEmail
41
+ {...props}
42
+ title={t("waitlistConfirmation.title")}
43
+ body={t("waitlistConfirmation.body")}
44
+ action={t("waitlistConfirmation.action")}
45
+ />
46
+ );
47
+ }
48
+
49
+ export function WaitlistInviteEmail(props: WaitlistEmailProps) {
50
+ const t = resolveT(props.t);
51
+ return (
52
+ <WaitlistActionEmail
53
+ {...props}
54
+ title={t("waitlistInvite.title")}
55
+ body={t("waitlistInvite.body")}
56
+ action={t("waitlistInvite.action")}
57
+ />
58
+ );
59
+ }
60
+
61
+ export function WaitlistUserInviteEmail(
62
+ props: WaitlistEmailProps & { inviter?: string; name?: string }
63
+ ) {
64
+ const t = resolveT(props.t);
65
+ const inviter = props.inviter ?? "A teammate";
66
+ const invitee = props.name ? t("waitlistUserInvite.inviteeSuffix", { name: props.name }) : "";
67
+ return (
68
+ <WaitlistActionEmail
69
+ {...props}
70
+ title={t("waitlistUserInvite.title", { inviter, invitee })}
71
+ body={t("waitlistUserInvite.body")}
72
+ action={t("waitlistUserInvite.action")}
73
+ />
74
+ );
75
+ }
76
+
77
+ export function SystemWaitlistNotificationEmail(props: WaitlistEmailProps) {
78
+ const t = resolveT(props.t);
79
+ return (
80
+ <WaitlistActionEmail
81
+ {...props}
82
+ title={t("systemWaitlistNotification.title")}
83
+ body={t("systemWaitlistNotification.body")}
84
+ action={t("systemWaitlistNotification.action")}
85
+ />
86
+ );
87
+ }
@@ -1,32 +1,72 @@
1
- import type { FunctionComponent } from "react";
2
- import AccountDeletionEmail from "./emails/accountDeletionEmail";
3
- import OrganizationInviteEmail from "./emails/organizationInviteEmail";
4
- import PasswordResetEmail from "./emails/passwordResetEmail";
5
- import VerificationEmail from "./emails/verificationEmail";
6
-
7
- export const templates = {
8
- accountDeletion: {
9
- id: "account-deletion",
10
- subject: "accountDeletion.subject",
11
- previewText: "accountDeletion.previewText",
12
- react: AccountDeletionEmail as FunctionComponent<Record<string, unknown>>,
13
- },
14
- passwordReset: {
15
- id: "password-reset",
16
- subject: "passwordReset.subject",
17
- previewText: "passwordReset.previewText",
18
- react: PasswordResetEmail as FunctionComponent<Record<string, unknown>>,
19
- },
20
- verification: {
21
- id: "verification",
22
- subject: "verification.subject",
23
- previewText: "verification.previewText",
24
- react: VerificationEmail as FunctionComponent<Record<string, unknown>>,
25
- },
26
- organizationInvite: {
27
- id: "organization-invite",
28
- subject: "organizationInvite.subject",
29
- previewText: "organizationInvite.previewText",
30
- react: OrganizationInviteEmail as FunctionComponent<Record<string, unknown>>,
31
- },
32
- };
1
+ import type { FunctionComponent } from "react";
2
+ import AccountDeletionEmail from "./emails/accountDeletionEmail";
3
+ import OrganizationInviteEmail from "./emails/organizationInviteEmail";
4
+ import PasswordResetEmail from "./emails/passwordResetEmail";
5
+ import VerificationEmail from "./emails/verificationEmail";
6
+ import {
7
+ SystemWaitlistNotificationEmail,
8
+ WaitlistConfirmationEmail,
9
+ WaitlistInviteEmail,
10
+ WaitlistUserInviteEmail,
11
+ } from "./emails/waitlistEmails";
12
+
13
+ /**
14
+ * EmailService renders templates with untyped props (they come from send-time
15
+ * data), so the registry erases each template's prop type. The props each
16
+ * template receives are guaranteed by the corresponding EmailService.send*
17
+ * method.
18
+ */
19
+ function asTemplate(component: FunctionComponent<never>): FunctionComponent<Record<string, unknown>> {
20
+ return component as unknown as FunctionComponent<Record<string, unknown>>;
21
+ }
22
+
23
+ export const templates = {
24
+ accountDeletion: {
25
+ id: "account-deletion",
26
+ subject: "accountDeletion.subject",
27
+ previewText: "accountDeletion.previewText",
28
+ react: asTemplate(AccountDeletionEmail),
29
+ },
30
+ passwordReset: {
31
+ id: "password-reset",
32
+ subject: "passwordReset.subject",
33
+ previewText: "passwordReset.previewText",
34
+ react: asTemplate(PasswordResetEmail),
35
+ },
36
+ verification: {
37
+ id: "verification",
38
+ subject: "verification.subject",
39
+ previewText: "verification.previewText",
40
+ react: asTemplate(VerificationEmail),
41
+ },
42
+ organizationInvite: {
43
+ id: "organization-invite",
44
+ subject: "organizationInvite.subject",
45
+ previewText: "organizationInvite.previewText",
46
+ react: asTemplate(OrganizationInviteEmail),
47
+ },
48
+ waitlistConfirmation: {
49
+ id: "waitlist-confirmation",
50
+ subject: "waitlistConfirmation.subject",
51
+ previewText: "waitlistConfirmation.previewText",
52
+ react: asTemplate(WaitlistConfirmationEmail),
53
+ },
54
+ waitlistInvite: {
55
+ id: "waitlist-invite",
56
+ subject: "waitlistInvite.subject",
57
+ previewText: "waitlistInvite.previewText",
58
+ react: asTemplate(WaitlistInviteEmail),
59
+ },
60
+ waitlistUserInvite: {
61
+ id: "waitlist-user-invite",
62
+ subject: "waitlistUserInvite.subject",
63
+ previewText: "waitlistUserInvite.previewText",
64
+ react: asTemplate(WaitlistUserInviteEmail),
65
+ },
66
+ systemWaitlistNotification: {
67
+ id: "system-waitlist-notification",
68
+ subject: "systemWaitlistNotification.subject",
69
+ previewText: "systemWaitlistNotification.previewText",
70
+ react: asTemplate(SystemWaitlistNotificationEmail),
71
+ },
72
+ };
@@ -1,36 +1,65 @@
1
- import type { BackendAppI18nResources } from "@m5kdev/backend/i18n/app-i18n";
2
-
3
- export const emailResources = {
4
- en: {
5
- translation: {
6
- verification: {
7
- subject: "Verify your email",
8
- previewText: "Verify your email address",
9
- title: "Verify your email",
10
- body: "Confirm your email address to finish setting up {{APP_NAME}} and access your editorial workspace.",
11
- action: "Verify account",
12
- },
13
- passwordReset: {
14
- subject: "Reset your password",
15
- previewText: "Reset your password request",
16
- title: "Reset your password",
17
- body: "Use this link to choose a new password for your account.",
18
- action: "Reset password",
19
- },
20
- accountDeletion: {
21
- subject: "Delete your account",
22
- previewText: "Confirm your account deletion",
23
- title: "Confirm account deletion",
24
- body: "Use this link to confirm deleting your account.",
25
- action: "Confirm deletion",
26
- },
27
- organizationInvite: {
28
- subject: "Join the organization",
29
- previewText: "You have been invited to join an organization",
30
- title: "Join {{organizationName}}",
31
- body: "{{inviterName}} invited you to join {{organizationName}} as {{role}}.",
32
- action: "Accept invitation",
33
- },
34
- },
35
- },
36
- } satisfies BackendAppI18nResources;
1
+ import type { BackendAppI18nResources } from "@m5kdev/backend/i18n/app-i18n";
2
+
3
+ export const emailResources = {
4
+ en: {
5
+ translation: {
6
+ verification: {
7
+ subject: "Verify your email",
8
+ previewText: "Verify your email address",
9
+ title: "Verify your email",
10
+ body: "Confirm your email address to finish setting up {{APP_NAME}} and access your editorial workspace.",
11
+ action: "Verify account",
12
+ },
13
+ passwordReset: {
14
+ subject: "Reset your password",
15
+ previewText: "Reset your password request",
16
+ title: "Reset your password",
17
+ body: "Use this link to choose a new password for your account.",
18
+ action: "Reset password",
19
+ },
20
+ accountDeletion: {
21
+ subject: "Delete your account",
22
+ previewText: "Confirm your account deletion",
23
+ title: "Confirm account deletion",
24
+ body: "Use this link to confirm deleting your account.",
25
+ action: "Confirm deletion",
26
+ },
27
+ organizationInvite: {
28
+ subject: "Join the organization",
29
+ previewText: "You have been invited to join an organization",
30
+ title: "Join {{organizationName}}",
31
+ body: "{{inviterName}} invited you to join {{organizationName}} as {{role}}.",
32
+ action: "Accept invitation",
33
+ },
34
+ waitlistConfirmation: {
35
+ subject: "You joined the waitlist",
36
+ previewText: "Your waitlist request was recorded",
37
+ title: "You joined the waitlist",
38
+ body: "Your waitlist request was recorded. We will let you know as soon as a spot opens up.",
39
+ action: "Open app",
40
+ },
41
+ waitlistInvite: {
42
+ subject: "Your invitation is ready",
43
+ previewText: "Create your account",
44
+ title: "Your invitation is ready",
45
+ body: "Use this invitation link to create your account.",
46
+ action: "Create account",
47
+ },
48
+ waitlistUserInvite: {
49
+ subject: "{{inviter}} invited you to join {{brandName}}",
50
+ previewText: "Create your {{brandName}} account today",
51
+ title: "{{inviter}} invited you{{invitee}}",
52
+ body: "This invite lets you skip the waitlist and create your account now.",
53
+ action: "Accept invite",
54
+ inviteeSuffix: " for {{name}}",
55
+ },
56
+ systemWaitlistNotification: {
57
+ subject: "New waitlist signup",
58
+ previewText: "A user joined the waitlist",
59
+ title: "New waitlist signup",
60
+ body: "A user joined the {{APP_NAME}} waitlist.",
61
+ action: "Open admin",
62
+ },
63
+ },
64
+ },
65
+ } satisfies BackendAppI18nResources;