create-m5kdev 0.33.0 → 0.35.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.
- package/dist/src/catalog.cjs +21 -1
- package/dist/src/catalog.cjs.map +1 -1
- package/dist/src/catalog.d.cts +3 -1
- package/dist/src/create.cjs +6 -2
- package/dist/src/create.cjs.map +1 -1
- package/dist/src/doctor.cjs.map +1 -1
- package/dist/src/fs.cjs +10 -5
- package/dist/src/fs.cjs.map +1 -1
- package/dist/src/prompts.cjs +32 -0
- package/dist/src/prompts.cjs.map +1 -1
- package/dist/src/prompts.d.cts +7 -1
- package/dist/src/template.cjs +23 -4
- package/dist/src/template.cjs.map +1 -1
- package/dist/src/template.d.cts +17 -3
- package/dist/src/types.d.cts +11 -4
- package/package.json +4 -1
- package/templates/minimal-app/.cursor/rules/frontend-data-guide.mdc +1 -0
- package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +1 -1
- package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
- package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
- package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
- package/templates/minimal-app/.cursor/rules/server-event-emit.mdc +41 -0
- package/templates/minimal-app/.dockerignore.tpl +31 -0
- package/templates/minimal-app/.gitignore.tpl +1 -0
- package/templates/minimal-app/AGENTS.md.tpl +14 -10
- package/templates/minimal-app/CLAUDE.md.tpl +133 -131
- package/templates/minimal-app/README.md.tpl +51 -19
- package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
- package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
- package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
- package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
- package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
- package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
- package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
- package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
- package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
- package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
- package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
- package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
- package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
- package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
- package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
- package/templates/minimal-app/apps/email/package.json.tpl +34 -34
- package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
- package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
- package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
- package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
- package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
- package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
- package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
- package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
- package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
- package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
- package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
- package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
- package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
- package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
- package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
- package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
- package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
- package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
- package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
- package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
- package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
- package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
- package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
- package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
- package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
- package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
- package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
- package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
- package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
- package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
- package/templates/minimal-app/apps/landing/src/App.tsx.tpl +12 -0
- package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +37 -0
- package/templates/minimal-app/apps/landing/src/index.css.tpl +24 -0
- package/templates/minimal-app/apps/landing/src/main.tsx.tpl +16 -0
- package/templates/minimal-app/apps/landing/src/vite-env.d.ts.tpl +11 -0
- package/templates/minimal-app/apps/landing/tsconfig.json.tpl +12 -0
- package/templates/minimal-app/apps/landing/vite.config.ts.tpl +25 -0
- package/templates/minimal-app/apps/server/AGENTS.md.tpl +55 -46
- package/templates/minimal-app/apps/server/{drizzle/seed.e2e.ts.tpl → db.e2e.ts.tpl} +57 -40
- package/templates/minimal-app/apps/server/{drizzle/seed.ts.tpl → db.ts.tpl} +27 -20
- package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +27 -0
- package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +56 -166
- package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +1796 -1910
- package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1818 -1932
- package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2137 -0
- package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +8 -1
- package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
- package/templates/minimal-app/apps/server/package.json.tpl +5 -3
- package/templates/minimal-app/apps/server/src/app.ts.tpl +36 -0
- package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +26 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl +51 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +51 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.trpc.ts.tpl +16 -0
- package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +28 -28
- package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +28 -28
- package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +57 -54
- package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -67
- package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +116 -116
- package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -37
- package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -279
- package/templates/minimal-app/apps/server/src/schema.ts.tpl +31 -20
- package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -12
- package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -1
- package/templates/minimal-app/apps/server/tsup.config.ts.tpl +23 -9
- package/templates/minimal-app/apps/shared/.env.example.tpl +5 -0
- package/templates/minimal-app/apps/shared/.env.production.example.tpl +23 -0
- package/templates/minimal-app/apps/shared/.env.tpl +5 -0
- package/templates/minimal-app/apps/shared/Dockerfile.tpl +48 -0
- package/templates/minimal-app/apps/shared/fly.toml.tpl +24 -0
- package/templates/minimal-app/apps/shared/package.json.tpl +26 -24
- package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
- package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
- package/templates/minimal-app/apps/shared/src/modules/demo-workflow/demo-workflow.constants.ts.tpl +1 -0
- package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
- package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
- package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
- package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
- package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
- package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +49 -44
- package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
- package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
- package/templates/minimal-app/apps/webapp/src/StarterServerEventProvider.tsx.tpl +38 -0
- package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -15
- package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
- package/templates/minimal-app/apps/webapp/src/modules/conversation/ConversationRoute.tsx.tpl +36 -0
- package/templates/minimal-app/apps/webapp/src/modules/files/FilesRoute.tsx.tpl +88 -0
- package/templates/minimal-app/apps/webapp/src/modules/files/hooks/useFilesRoute.ts.tpl +50 -0
- package/templates/minimal-app/apps/webapp/src/modules/workflows/WorkflowsRoute.tsx.tpl +97 -0
- package/templates/minimal-app/apps/webapp/src/modules/workflows/hooks/useWorkflowsRoute.ts.tpl +64 -0
- package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
- package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -10
- package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
- package/templates/minimal-app/apps/webapp/translations/en/app.json.tpl +7 -7
- package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +193 -135
- package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
- package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
- package/templates/minimal-app/biome.json.tpl +17 -12
- package/templates/minimal-app/package.json.tpl +7 -2
- package/templates/minimal-app/pnpm-workspace.yaml.tpl +14 -7
- package/templates/minimal-app/template.manifest.json +93 -2
- package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +0 -26
- package/templates/minimal-app/apps/server/drizzle/guard.ts.tpl +0 -36
- package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +0 -40
- package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +0 -16
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
import { expect, test } from "@playwright/test";
|
|
2
|
-
import {
|
|
3
|
-
clearEmails,
|
|
4
|
-
emailUrl,
|
|
5
|
-
getUserState,
|
|
6
|
-
latestEmail,
|
|
7
|
-
login,
|
|
8
|
-
logout,
|
|
9
|
-
profiles,
|
|
10
|
-
} from "./helpers";
|
|
11
|
-
|
|
12
|
-
const profile = "waitlist" as const;
|
|
13
|
-
|
|
14
|
-
test.beforeEach(async ({ request }) => {
|
|
15
|
-
await clearEmails(request, profile);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("public waitlist join stores confirmation email", async ({ page, request }) => {
|
|
19
|
-
const email = `waitlist.${Date.now()}@auth-e2e.local`;
|
|
20
|
-
|
|
21
|
-
await page.goto("/signup");
|
|
22
|
-
await page.locator('input[type="email"]').fill(email);
|
|
23
|
-
await page.getByRole("button", { name: /join the waitlist/i }).click();
|
|
24
|
-
|
|
25
|
-
const confirmation = await latestEmail(request, profile, {
|
|
26
|
-
to: email,
|
|
27
|
-
templateId: "waitlist-confirmation",
|
|
28
|
-
});
|
|
29
|
-
expect(confirmation.subject).toContain("waitlist");
|
|
30
|
-
|
|
31
|
-
const systemNotification = await latestEmail(request, profile, {
|
|
32
|
-
to: "ops@auth-e2e.local",
|
|
33
|
-
templateId: "system-waitlist-notification",
|
|
34
|
-
});
|
|
35
|
-
expect(systemNotification.props.email).toBe(email);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("admin waitlist invitation creates a signup link that can be accepted", async ({
|
|
39
|
-
page,
|
|
40
|
-
request,
|
|
41
|
-
}) => {
|
|
42
|
-
const email = `invited.${Date.now()}@auth-e2e.local`;
|
|
43
|
-
|
|
44
|
-
await login(page, profiles.waitlist.adminEmail, profiles.waitlist.adminPassword);
|
|
45
|
-
await page.goto("/admin/waitlist");
|
|
46
|
-
await page
|
|
47
|
-
.getByRole("button", { name: /add to waitlist/i })
|
|
48
|
-
.first()
|
|
49
|
-
.click();
|
|
50
|
-
const dialog = page.getByRole("dialog");
|
|
51
|
-
await dialog.locator('input[type="email"]').fill(email);
|
|
52
|
-
await dialog.getByRole("button", { name: /^add to waitlist$/i }).click();
|
|
53
|
-
await expect(page.getByText(email)).toBeVisible();
|
|
54
|
-
const waitlistRow = page.getByRole("row").filter({ hasText: email });
|
|
55
|
-
await waitlistRow.getByLabel("Waitlist row actions").first().click();
|
|
56
|
-
await page.getByRole("menuitem", { name: /send invitation/i }).click();
|
|
57
|
-
|
|
58
|
-
const invite = await latestEmail(request, profile, { to: email, templateId: "waitlist-invite" });
|
|
59
|
-
|
|
60
|
-
await logout(page);
|
|
61
|
-
await page.goto(emailUrl(invite));
|
|
62
|
-
await page.locator('[name="signup-password"]').fill("password1234");
|
|
63
|
-
await page.getByRole("button", { name: /sign up/i }).click();
|
|
64
|
-
await expect(page.getByTestId("session-email")).toContainText(email);
|
|
65
|
-
|
|
66
|
-
const state = await getUserState(request, profile, email);
|
|
67
|
-
expect(state.user.emailVerified).toBe(true);
|
|
68
|
-
expect(state.organizations).toHaveLength(1);
|
|
69
|
-
|
|
70
|
-
await logout(page);
|
|
71
|
-
await page.goto(emailUrl(invite));
|
|
72
|
-
await expect(page.getByText(/invalid|expired|invitation code not found/i)).toBeVisible();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("signed-in user invite skips the waitlist", async ({ page, request }) => {
|
|
76
|
-
const email = `friend.${Date.now()}@auth-e2e.local`;
|
|
77
|
-
|
|
78
|
-
await login(page, profiles.waitlist.adminEmail, profiles.waitlist.adminPassword);
|
|
79
|
-
await page.goto("/user/invite");
|
|
80
|
-
await page.locator('input[type="email"]').fill(email);
|
|
81
|
-
await page.getByRole("button", { name: /send invitation/i }).click();
|
|
82
|
-
|
|
83
|
-
const invite = await latestEmail(request, profile, {
|
|
84
|
-
to: email,
|
|
85
|
-
templateId: "waitlist-user-invite",
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
await logout(page);
|
|
89
|
-
await page.goto(emailUrl(invite));
|
|
90
|
-
await page.locator('[name="signup-password"]').fill("password1234");
|
|
91
|
-
await page.getByRole("button", { name: /sign up/i }).click();
|
|
92
|
-
await expect(page.getByTestId("session-email")).toContainText(email);
|
|
93
|
-
|
|
94
|
-
const state = await getUserState(request, profile, email);
|
|
95
|
-
expect(state.user.emailVerified).toBe(true);
|
|
96
|
-
expect(state.organizations).toHaveLength(1);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("expired waitlist codes are rejected before signup", async ({ page }) => {
|
|
100
|
-
await page.goto("/signup?code=expired-waitlist-code");
|
|
101
|
-
await expect(page.getByText(/expired/i)).toBeVisible();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("unknown waitlist codes are rejected before signup", async ({ page }) => {
|
|
105
|
-
await page.goto("/signup?code=unknown-waitlist-code");
|
|
106
|
-
await expect(page.getByText(/invalid|expired|invitation code not found/i)).toBeVisible();
|
|
107
|
-
});
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import {
|
|
3
|
+
clearEmails,
|
|
4
|
+
emailUrl,
|
|
5
|
+
getUserState,
|
|
6
|
+
latestEmail,
|
|
7
|
+
login,
|
|
8
|
+
logout,
|
|
9
|
+
profiles,
|
|
10
|
+
} from "./helpers";
|
|
11
|
+
|
|
12
|
+
const profile = "waitlist" as const;
|
|
13
|
+
|
|
14
|
+
test.beforeEach(async ({ request }) => {
|
|
15
|
+
await clearEmails(request, profile);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("public waitlist join stores confirmation email", async ({ page, request }) => {
|
|
19
|
+
const email = `waitlist.${Date.now()}@auth-e2e.local`;
|
|
20
|
+
|
|
21
|
+
await page.goto("/signup");
|
|
22
|
+
await page.locator('input[type="email"]').fill(email);
|
|
23
|
+
await page.getByRole("button", { name: /join the waitlist/i }).click();
|
|
24
|
+
|
|
25
|
+
const confirmation = await latestEmail(request, profile, {
|
|
26
|
+
to: email,
|
|
27
|
+
templateId: "waitlist-confirmation",
|
|
28
|
+
});
|
|
29
|
+
expect(confirmation.subject).toContain("waitlist");
|
|
30
|
+
|
|
31
|
+
const systemNotification = await latestEmail(request, profile, {
|
|
32
|
+
to: "ops@auth-e2e.local",
|
|
33
|
+
templateId: "system-waitlist-notification",
|
|
34
|
+
});
|
|
35
|
+
expect(systemNotification.props.email).toBe(email);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("admin waitlist invitation creates a signup link that can be accepted", async ({
|
|
39
|
+
page,
|
|
40
|
+
request,
|
|
41
|
+
}) => {
|
|
42
|
+
const email = `invited.${Date.now()}@auth-e2e.local`;
|
|
43
|
+
|
|
44
|
+
await login(page, profiles.waitlist.adminEmail, profiles.waitlist.adminPassword);
|
|
45
|
+
await page.goto("/admin/waitlist");
|
|
46
|
+
await page
|
|
47
|
+
.getByRole("button", { name: /add to waitlist/i })
|
|
48
|
+
.first()
|
|
49
|
+
.click();
|
|
50
|
+
const dialog = page.getByRole("dialog");
|
|
51
|
+
await dialog.locator('input[type="email"]').fill(email);
|
|
52
|
+
await dialog.getByRole("button", { name: /^add to waitlist$/i }).click();
|
|
53
|
+
await expect(page.getByText(email)).toBeVisible();
|
|
54
|
+
const waitlistRow = page.getByRole("row").filter({ hasText: email });
|
|
55
|
+
await waitlistRow.getByLabel("Waitlist row actions").first().click();
|
|
56
|
+
await page.getByRole("menuitem", { name: /send invitation/i }).click();
|
|
57
|
+
|
|
58
|
+
const invite = await latestEmail(request, profile, { to: email, templateId: "waitlist-invite" });
|
|
59
|
+
|
|
60
|
+
await logout(page);
|
|
61
|
+
await page.goto(emailUrl(invite));
|
|
62
|
+
await page.locator('[name="signup-password"]').fill("password1234");
|
|
63
|
+
await page.getByRole("button", { name: /sign up/i }).click();
|
|
64
|
+
await expect(page.getByTestId("session-email")).toContainText(email);
|
|
65
|
+
|
|
66
|
+
const state = await getUserState(request, profile, email);
|
|
67
|
+
expect(state.user.emailVerified).toBe(true);
|
|
68
|
+
expect(state.organizations).toHaveLength(1);
|
|
69
|
+
|
|
70
|
+
await logout(page);
|
|
71
|
+
await page.goto(emailUrl(invite));
|
|
72
|
+
await expect(page.getByText(/invalid|expired|invitation code not found/i)).toBeVisible();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("signed-in user invite skips the waitlist", async ({ page, request }) => {
|
|
76
|
+
const email = `friend.${Date.now()}@auth-e2e.local`;
|
|
77
|
+
|
|
78
|
+
await login(page, profiles.waitlist.adminEmail, profiles.waitlist.adminPassword);
|
|
79
|
+
await page.goto("/user/invite");
|
|
80
|
+
await page.locator('input[type="email"]').fill(email);
|
|
81
|
+
await page.getByRole("button", { name: /send invitation/i }).click();
|
|
82
|
+
|
|
83
|
+
const invite = await latestEmail(request, profile, {
|
|
84
|
+
to: email,
|
|
85
|
+
templateId: "waitlist-user-invite",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await logout(page);
|
|
89
|
+
await page.goto(emailUrl(invite));
|
|
90
|
+
await page.locator('[name="signup-password"]').fill("password1234");
|
|
91
|
+
await page.getByRole("button", { name: /sign up/i }).click();
|
|
92
|
+
await expect(page.getByTestId("session-email")).toContainText(email);
|
|
93
|
+
|
|
94
|
+
const state = await getUserState(request, profile, email);
|
|
95
|
+
expect(state.user.emailVerified).toBe(true);
|
|
96
|
+
expect(state.organizations).toHaveLength(1);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("expired waitlist codes are rejected before signup", async ({ page }) => {
|
|
100
|
+
await page.goto("/signup?code=expired-waitlist-code");
|
|
101
|
+
await expect(page.getByText(/expired/i)).toBeVisible();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("unknown waitlist codes are rejected before signup", async ({ page }) => {
|
|
105
|
+
await page.goto("/signup?code=unknown-waitlist-code");
|
|
106
|
+
await expect(page.getByText(/invalid|expired|invitation code not found/i)).toBeVisible();
|
|
107
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { clearEmails, login, signUp, verifyLatestEmail } from "./helpers";
|
|
3
|
+
|
|
4
|
+
const profile = "standard" as const;
|
|
5
|
+
|
|
6
|
+
test.beforeEach(async ({ request }) => {
|
|
7
|
+
await clearEmails(request, profile);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("trigger a demo workflow and observe it complete", async ({ page, request }) => {
|
|
11
|
+
const email = `workflow.${Date.now()}@auth-e2e.local`;
|
|
12
|
+
const password = "password1234";
|
|
13
|
+
|
|
14
|
+
await signUp(page, email, password);
|
|
15
|
+
await verifyLatestEmail(request, profile, email);
|
|
16
|
+
await login(page, email, password);
|
|
17
|
+
|
|
18
|
+
await page.goto("/workflows");
|
|
19
|
+
await expect(page.getByTestId("workflows-route")).toBeVisible();
|
|
20
|
+
|
|
21
|
+
await page.getByTestId("workflow-trigger").click();
|
|
22
|
+
await expect(page.getByTestId("workflow-run").first()).toBeVisible();
|
|
23
|
+
await expect(page.getByTestId("workflow-run-status").first()).toHaveText(/completed/i, {
|
|
24
|
+
timeout: 20_000,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "@m5kdev/config/tsconfig.node.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": ".",
|
|
5
|
-
"noEmit": true,
|
|
6
|
-
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
|
7
|
-
},
|
|
8
|
-
"exclude": ["dist", "node_modules", "test-results", "playwright-report"],
|
|
9
|
-
"include": ["playwright.config.ts", "playwright.expo.config.ts", "servers.ts", "tests/**/*.ts"]
|
|
10
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"extends": "@m5kdev/config/tsconfig.node.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": ".",
|
|
5
|
+
"noEmit": true,
|
|
6
|
+
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
|
7
|
+
},
|
|
8
|
+
"exclude": ["dist", "node_modules", "test-results", "playwright-report"],
|
|
9
|
+
"include": ["playwright.config.ts", "playwright.expo.config.ts", "servers.ts", "tests/**/*.ts"]
|
|
10
|
+
}
|
|
@@ -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:m5kdev",
|
|
14
|
-
"@m5kdev/email": "catalog:m5kdev",
|
|
15
|
-
"@react-email/components": "catalog:m5kdev",
|
|
16
|
-
"react": "catalog:m5kdev",
|
|
17
|
-
"react-dom": "catalog:m5kdev"
|
|
18
|
-
},
|
|
19
|
-
"devDependencies": {
|
|
20
|
-
"@m5kdev/backend": "catalog:m5kdev",
|
|
21
|
-
"@m5kdev/config": "catalog:m5kdev",
|
|
22
|
-
"@types/node": "catalog:m5kdev",
|
|
23
|
-
"@types/react": "catalog:m5kdev",
|
|
24
|
-
"@types/react-dom": "catalog:m5kdev",
|
|
25
|
-
"react-email": "catalog:m5kdev",
|
|
26
|
-
"tslib": "catalog:m5kdev",
|
|
27
|
-
"typescript": "catalog:m5kdev"
|
|
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:m5kdev",
|
|
14
|
+
"@m5kdev/email": "catalog:m5kdev",
|
|
15
|
+
"@react-email/components": "catalog:m5kdev",
|
|
16
|
+
"react": "catalog:m5kdev",
|
|
17
|
+
"react-dom": "catalog:m5kdev"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@m5kdev/backend": "catalog:m5kdev",
|
|
21
|
+
"@m5kdev/config": "catalog:m5kdev",
|
|
22
|
+
"@types/node": "catalog:m5kdev",
|
|
23
|
+
"@types/react": "catalog:m5kdev",
|
|
24
|
+
"@types/react-dom": "catalog:m5kdev",
|
|
25
|
+
"react-email": "catalog:m5kdev",
|
|
26
|
+
"tslib": "catalog:m5kdev",
|
|
27
|
+
"typescript": "catalog:m5kdev"
|
|
28
|
+
},
|
|
29
|
+
"main": "./src/index.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./src/index.ts",
|
|
32
|
+
"./resources": "./src/resources.ts"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
|
|
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
|
+
import { Heading, Text } from "@react-email/components";
|
|
7
|
+
|
|
8
|
+
function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
|
|
9
|
+
return t ?? ((key: string) => key);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function TrialEndingEmail({
|
|
13
|
+
previewText,
|
|
14
|
+
url,
|
|
15
|
+
trialEnd,
|
|
16
|
+
brand,
|
|
17
|
+
t,
|
|
18
|
+
htmlLang,
|
|
19
|
+
}: {
|
|
20
|
+
previewText: string;
|
|
21
|
+
url: string;
|
|
22
|
+
trialEnd?: string;
|
|
23
|
+
brand: Brand;
|
|
24
|
+
t?: EmailTranslateFn;
|
|
25
|
+
htmlLang?: string;
|
|
26
|
+
}) {
|
|
27
|
+
const translate = resolveT(t);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
|
|
31
|
+
<Heading className="mb-4 text-2xl font-bold text-black">
|
|
32
|
+
{translate("trialEnding.title")}
|
|
33
|
+
</Heading>
|
|
34
|
+
<Text className="mb-6 text-base text-gray-700">
|
|
35
|
+
{translate("trialEnding.body", { trialEnd })}
|
|
36
|
+
</Text>
|
|
37
|
+
<CtaButton href={url}>{translate("trialEnding.action")}</CtaButton>
|
|
38
|
+
</EmailLayout>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -2,6 +2,7 @@ import type { FunctionComponent } from "react";
|
|
|
2
2
|
import AccountDeletionEmail from "./emails/accountDeletionEmail";
|
|
3
3
|
import OrganizationInviteEmail from "./emails/organizationInviteEmail";
|
|
4
4
|
import PasswordResetEmail from "./emails/passwordResetEmail";
|
|
5
|
+
import TrialEndingEmail from "./emails/trialEndingEmail";
|
|
5
6
|
import VerificationEmail from "./emails/verificationEmail";
|
|
6
7
|
import {
|
|
7
8
|
SystemWaitlistNotificationEmail,
|
|
@@ -41,6 +42,12 @@ export const templates = {
|
|
|
41
42
|
previewText: "verification.previewText",
|
|
42
43
|
react: asTemplate(VerificationEmail),
|
|
43
44
|
},
|
|
45
|
+
trialEnding: {
|
|
46
|
+
id: "trial-ending",
|
|
47
|
+
subject: "trialEnding.subject",
|
|
48
|
+
previewText: "trialEnding.previewText",
|
|
49
|
+
react: asTemplate(TrialEndingEmail),
|
|
50
|
+
},
|
|
44
51
|
organizationInvite: {
|
|
45
52
|
id: "organization-invite",
|
|
46
53
|
subject: "organizationInvite.subject",
|
|
@@ -1,65 +1,72 @@
|
|
|
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
|
-
|
|
21
|
-
subject: "
|
|
22
|
-
previewText: "
|
|
23
|
-
title: "
|
|
24
|
-
body: "
|
|
25
|
-
action: "
|
|
26
|
-
},
|
|
27
|
-
|
|
28
|
-
subject: "
|
|
29
|
-
previewText: "
|
|
30
|
-
title: "
|
|
31
|
-
body: "
|
|
32
|
-
action: "
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
subject: "
|
|
36
|
-
previewText: "
|
|
37
|
-
title: "
|
|
38
|
-
body: "
|
|
39
|
-
action: "
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
subject: "
|
|
43
|
-
previewText: "
|
|
44
|
-
title: "
|
|
45
|
-
body: "
|
|
46
|
-
action: "
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
subject: "
|
|
50
|
-
previewText: "Create your
|
|
51
|
-
title: "
|
|
52
|
-
body: "
|
|
53
|
-
action: "
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
|
|
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
|
+
trialEnding: {
|
|
21
|
+
subject: "Your trial ends soon",
|
|
22
|
+
previewText: "Add a payment method to keep your subscription",
|
|
23
|
+
title: "Your trial is ending",
|
|
24
|
+
body: "Your trial ends on {{trialEnd}}. Without a payment method, your subscription will be canceled.",
|
|
25
|
+
action: "Add a payment method",
|
|
26
|
+
},
|
|
27
|
+
accountDeletion: {
|
|
28
|
+
subject: "Delete your account",
|
|
29
|
+
previewText: "Confirm your account deletion",
|
|
30
|
+
title: "Confirm account deletion",
|
|
31
|
+
body: "Use this link to confirm deleting your account.",
|
|
32
|
+
action: "Confirm deletion",
|
|
33
|
+
},
|
|
34
|
+
organizationInvite: {
|
|
35
|
+
subject: "Join the organization",
|
|
36
|
+
previewText: "You have been invited to join an organization",
|
|
37
|
+
title: "Join {{organizationName}}",
|
|
38
|
+
body: "{{inviterName}} invited you to join {{organizationName}} as {{role}}.",
|
|
39
|
+
action: "Accept invitation",
|
|
40
|
+
},
|
|
41
|
+
waitlistConfirmation: {
|
|
42
|
+
subject: "You joined the waitlist",
|
|
43
|
+
previewText: "Your waitlist request was recorded",
|
|
44
|
+
title: "You joined the waitlist",
|
|
45
|
+
body: "Your waitlist request was recorded. We will let you know as soon as a spot opens up.",
|
|
46
|
+
action: "Open app",
|
|
47
|
+
},
|
|
48
|
+
waitlistInvite: {
|
|
49
|
+
subject: "Your invitation is ready",
|
|
50
|
+
previewText: "Create your account",
|
|
51
|
+
title: "Your invitation is ready",
|
|
52
|
+
body: "Use this invitation link to create your account.",
|
|
53
|
+
action: "Create account",
|
|
54
|
+
},
|
|
55
|
+
waitlistUserInvite: {
|
|
56
|
+
subject: "{{inviter}} invited you to join {{brandName}}",
|
|
57
|
+
previewText: "Create your {{brandName}} account today",
|
|
58
|
+
title: "{{inviter}} invited you{{invitee}}",
|
|
59
|
+
body: "This invite lets you skip the waitlist and create your account now.",
|
|
60
|
+
action: "Accept invite",
|
|
61
|
+
inviteeSuffix: " for {{name}}",
|
|
62
|
+
},
|
|
63
|
+
systemWaitlistNotification: {
|
|
64
|
+
subject: "New waitlist signup",
|
|
65
|
+
previewText: "A user joined the waitlist",
|
|
66
|
+
title: "New waitlist signup",
|
|
67
|
+
body: "A user joined the {{APP_NAME}} waitlist.",
|
|
68
|
+
action: "Open admin",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
} satisfies BackendAppI18nResources;
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
{
|
|
2
|
-
"expo": {
|
|
3
|
-
"name": "Auth E2E Expo",
|
|
4
|
-
"slug": "auth-e2e-expo",
|
|
5
|
-
"version": "1.0.0",
|
|
6
|
-
"orientation": "portrait",
|
|
7
|
-
"icon": "./assets/images/icon.png",
|
|
8
|
-
"scheme": "auth-e2e-expo",
|
|
9
|
-
"userInterfaceStyle": "automatic",
|
|
10
|
-
"ios": {
|
|
11
|
-
"supportsTablet": true
|
|
12
|
-
},
|
|
13
|
-
"android": {
|
|
14
|
-
"adaptiveIcon": {
|
|
15
|
-
"foregroundImage": "./assets/images/adaptive-icon.png",
|
|
16
|
-
"backgroundColor": "#ffffff"
|
|
17
|
-
},
|
|
18
|
-
"predictiveBackGestureEnabled": false
|
|
19
|
-
},
|
|
20
|
-
"plugins": [
|
|
21
|
-
"expo-router",
|
|
22
|
-
[
|
|
23
|
-
"expo-splash-screen",
|
|
24
|
-
{
|
|
25
|
-
"image": "./assets/images/splash-icon.png",
|
|
26
|
-
"resizeMode": "contain",
|
|
27
|
-
"backgroundColor": "#ffffff"
|
|
28
|
-
}
|
|
29
|
-
]
|
|
30
|
-
],
|
|
31
|
-
"experiments": {
|
|
32
|
-
"typedRoutes": true,
|
|
33
|
-
"reactCompiler": true
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"expo": {
|
|
3
|
+
"name": "Auth E2E Expo",
|
|
4
|
+
"slug": "auth-e2e-expo",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"orientation": "portrait",
|
|
7
|
+
"icon": "./assets/images/icon.png",
|
|
8
|
+
"scheme": "auth-e2e-expo",
|
|
9
|
+
"userInterfaceStyle": "automatic",
|
|
10
|
+
"ios": {
|
|
11
|
+
"supportsTablet": true
|
|
12
|
+
},
|
|
13
|
+
"android": {
|
|
14
|
+
"adaptiveIcon": {
|
|
15
|
+
"foregroundImage": "./assets/images/adaptive-icon.png",
|
|
16
|
+
"backgroundColor": "#ffffff"
|
|
17
|
+
},
|
|
18
|
+
"predictiveBackGestureEnabled": false
|
|
19
|
+
},
|
|
20
|
+
"plugins": [
|
|
21
|
+
"expo-router",
|
|
22
|
+
[
|
|
23
|
+
"expo-splash-screen",
|
|
24
|
+
{
|
|
25
|
+
"image": "./assets/images/splash-icon.png",
|
|
26
|
+
"resizeMode": "contain",
|
|
27
|
+
"backgroundColor": "#ffffff"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
],
|
|
31
|
+
"experiments": {
|
|
32
|
+
"typedRoutes": true,
|
|
33
|
+
"reactCompiler": true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const expoConfig = require("eslint-config-expo/flat");
|
|
2
|
-
|
|
3
|
-
module.exports = [
|
|
4
|
-
...expoConfig,
|
|
5
|
-
{
|
|
6
|
-
ignores: ["dist/*", ".expo/*", "node_modules/*", "expo-env.d.ts", "uniwind-types.d.ts"],
|
|
7
|
-
},
|
|
8
|
-
];
|
|
1
|
+
const expoConfig = require("eslint-config-expo/flat");
|
|
2
|
+
|
|
3
|
+
module.exports = [
|
|
4
|
+
...expoConfig,
|
|
5
|
+
{
|
|
6
|
+
ignores: ["dist/*", ".expo/*", "node_modules/*", "expo-env.d.ts", "uniwind-types.d.ts"],
|
|
7
|
+
},
|
|
8
|
+
];
|