create-m5kdev 0.21.6 → 0.25.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/package.json +1 -1
- package/templates/minimal-app/AGENTS.md.tpl +4 -0
- package/templates/minimal-app/apps/email/package.json.tpl +8 -1
- package/templates/minimal-app/apps/email/src/emails/accountDeletionEmail.tsx.tpl +25 -21
- package/templates/minimal-app/apps/email/src/emails/organizationInviteEmail.tsx.tpl +24 -22
- package/templates/minimal-app/apps/email/src/emails/passwordResetEmail.tsx.tpl +25 -16
- package/templates/minimal-app/apps/email/src/emails/verificationEmail.tsx.tpl +27 -17
- package/templates/minimal-app/apps/email/src/index.ts.tpl +8 -8
- package/templates/minimal-app/apps/email/src/resources.ts.tpl +36 -0
- package/templates/minimal-app/apps/server/src/app.ts.tpl +12 -1
- package/templates/minimal-app/apps/shared/src/modules/app/locale.constants.ts.tpl +7 -0
- package/templates/minimal-app/apps/shared/src/modules/app/roles.constants.ts.tpl +17 -0
- package/templates/minimal-app/apps/webapp/src/Providers.tsx.tpl +4 -0
- package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +5 -3
- package/templates/minimal-app/apps/webapp/translations/app.json.tpl +7 -0
- package/templates/minimal-app/apps/email/src/components/BaseEmail.tsx.tpl +0 -117
package/package.json
CHANGED
|
@@ -26,5 +26,9 @@
|
|
|
26
26
|
- Keep the app shell shape: `NuqsAdapter` + `BrowserRouter` + `Providers`.
|
|
27
27
|
- Compose global providers only in `apps/webapp/src/Providers.tsx`.
|
|
28
28
|
- Use `AppConfigProvider` and `AppTrpcQueryProvider` from `@m5kdev/frontend`; use `@m5kdev/web-ui` for web-only UI.
|
|
29
|
+
- Define app roles in `apps/shared/src/modules/app/roles.constants.ts` and pass `roles: APP_ROLES_CONFIG` through `AppConfigProvider` and `createBackendApp` metadata.
|
|
30
|
+
- Add role display labels in `apps/webapp/translations/app.json` using keys like `organization.role.editor` (resolved as `app:organization.role.editor`).
|
|
31
|
+
- When adding custom roles, also extend module grants (for example `*.grants.ts`) so permissions match the new role keys.
|
|
32
|
+
- For upgrading existing apps, see the docs site guide `apps/docs/docs/guides/custom-app-roles-migration.md`.
|
|
29
33
|
- Use `nuqs` for URL state, React Router for routing, HeroUI for UI primitives, and Tailwind v4 for styling.
|
|
30
34
|
- Prefer shared framework utilities from `@m5kdev/frontend` and `@m5kdev/web-ui` before adding local duplicates.
|
|
@@ -10,11 +10,14 @@
|
|
|
10
10
|
"email:dev": "react-email dev --dir src/emails"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"@m5kdev/commons": "workspace:*",
|
|
14
|
+
"@m5kdev/email": "workspace:*",
|
|
13
15
|
"@react-email/components": "catalog:",
|
|
14
16
|
"react": "catalog:",
|
|
15
17
|
"react-dom": "catalog:"
|
|
16
18
|
},
|
|
17
19
|
"devDependencies": {
|
|
20
|
+
"@m5kdev/backend": "workspace:*",
|
|
18
21
|
"@m5kdev/config": "catalog:",
|
|
19
22
|
"@types/node": "catalog:",
|
|
20
23
|
"@types/react": "catalog:",
|
|
@@ -23,5 +26,9 @@
|
|
|
23
26
|
"tslib": "catalog:",
|
|
24
27
|
"typescript": "catalog:"
|
|
25
28
|
},
|
|
26
|
-
"main": "./src/index.ts"
|
|
29
|
+
"main": "./src/index.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./src/index.ts",
|
|
32
|
+
"./resources": "./src/resources.ts"
|
|
33
|
+
}
|
|
27
34
|
}
|
|
@@ -1,30 +1,34 @@
|
|
|
1
|
-
import { Text } from "@react-email/components";
|
|
2
|
-
import {
|
|
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
|
+
}
|
|
3
9
|
|
|
4
10
|
export default function AccountDeletionEmail({
|
|
5
|
-
previewText
|
|
11
|
+
previewText,
|
|
6
12
|
url,
|
|
13
|
+
brand,
|
|
14
|
+
t,
|
|
15
|
+
htmlLang,
|
|
7
16
|
}: {
|
|
8
|
-
previewText
|
|
17
|
+
previewText: string;
|
|
9
18
|
url: string;
|
|
19
|
+
brand: Brand;
|
|
20
|
+
t?: EmailTranslateFn;
|
|
21
|
+
htmlLang?: string;
|
|
10
22
|
}) {
|
|
23
|
+
const translate = resolveT(t);
|
|
24
|
+
|
|
11
25
|
return (
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<>
|
|
20
|
-
<Text style={{ margin: "0" }}>
|
|
21
|
-
We received a request to delete your {{APP_NAME}} account.
|
|
22
|
-
</Text>
|
|
23
|
-
<Text style={{ margin: "0" }}>
|
|
24
|
-
If that was you, use the link below to confirm the action.
|
|
25
|
-
</Text>
|
|
26
|
-
</>
|
|
27
|
-
}
|
|
28
|
-
/>
|
|
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>
|
|
29
33
|
);
|
|
30
34
|
}
|
|
@@ -1,31 +1,33 @@
|
|
|
1
|
-
import { Text } from "@react-email/components";
|
|
2
|
-
import {
|
|
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
|
+
}
|
|
3
9
|
|
|
4
10
|
export default function OrganizationInviteEmail({
|
|
5
|
-
previewText
|
|
11
|
+
previewText,
|
|
6
12
|
inviterName,
|
|
7
13
|
organizationName,
|
|
8
14
|
role,
|
|
9
15
|
url,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}) {
|
|
16
|
+
brand,
|
|
17
|
+
t,
|
|
18
|
+
htmlLang,
|
|
19
|
+
}: OrganizationInviteTemplateProps) {
|
|
20
|
+
const translate = resolveT(t);
|
|
21
|
+
|
|
17
22
|
return (
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
</Text>
|
|
28
|
-
}
|
|
29
|
-
/>
|
|
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>
|
|
30
32
|
);
|
|
31
33
|
}
|
|
@@ -1,25 +1,34 @@
|
|
|
1
|
-
import { Text } from "@react-email/components";
|
|
2
|
-
import {
|
|
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
|
+
}
|
|
3
9
|
|
|
4
10
|
export default function PasswordResetEmail({
|
|
5
|
-
previewText
|
|
11
|
+
previewText,
|
|
6
12
|
url,
|
|
13
|
+
brand,
|
|
14
|
+
t,
|
|
15
|
+
htmlLang,
|
|
7
16
|
}: {
|
|
8
|
-
previewText
|
|
17
|
+
previewText: string;
|
|
9
18
|
url: string;
|
|
19
|
+
brand: Brand;
|
|
20
|
+
t?: EmailTranslateFn;
|
|
21
|
+
htmlLang?: string;
|
|
10
22
|
}) {
|
|
23
|
+
const translate = resolveT(t);
|
|
24
|
+
|
|
11
25
|
return (
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<Text style={{ margin: "0" }}>
|
|
20
|
-
Use the secure link below to choose a new password for your {{APP_NAME}} account.
|
|
21
|
-
</Text>
|
|
22
|
-
}
|
|
23
|
-
/>
|
|
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>
|
|
24
33
|
);
|
|
25
34
|
}
|
|
@@ -1,26 +1,36 @@
|
|
|
1
|
-
import { Text } from "@react-email/components";
|
|
2
|
-
import {
|
|
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
|
+
}
|
|
3
9
|
|
|
4
10
|
export default function VerificationEmail({
|
|
5
|
-
previewText
|
|
11
|
+
previewText,
|
|
6
12
|
url,
|
|
13
|
+
brand,
|
|
14
|
+
t,
|
|
15
|
+
htmlLang,
|
|
7
16
|
}: {
|
|
8
|
-
previewText
|
|
17
|
+
previewText: string;
|
|
9
18
|
url: string;
|
|
19
|
+
brand: Brand;
|
|
20
|
+
t?: EmailTranslateFn;
|
|
21
|
+
htmlLang?: string;
|
|
10
22
|
}) {
|
|
23
|
+
const translate = resolveT(t);
|
|
24
|
+
|
|
11
25
|
return (
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
workspace.
|
|
22
|
-
</Text>
|
|
23
|
-
}
|
|
24
|
-
/>
|
|
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>
|
|
25
35
|
);
|
|
26
36
|
}
|
|
@@ -7,26 +7,26 @@ import VerificationEmail from "./emails/verificationEmail";
|
|
|
7
7
|
export const templates = {
|
|
8
8
|
accountDeletion: {
|
|
9
9
|
id: "account-deletion",
|
|
10
|
-
subject: "
|
|
11
|
-
previewText: "
|
|
10
|
+
subject: "accountDeletion.subject",
|
|
11
|
+
previewText: "accountDeletion.previewText",
|
|
12
12
|
react: AccountDeletionEmail as FunctionComponent<Record<string, unknown>>,
|
|
13
13
|
},
|
|
14
14
|
passwordReset: {
|
|
15
15
|
id: "password-reset",
|
|
16
|
-
subject: "
|
|
17
|
-
previewText: "
|
|
16
|
+
subject: "passwordReset.subject",
|
|
17
|
+
previewText: "passwordReset.previewText",
|
|
18
18
|
react: PasswordResetEmail as FunctionComponent<Record<string, unknown>>,
|
|
19
19
|
},
|
|
20
20
|
verification: {
|
|
21
21
|
id: "verification",
|
|
22
|
-
subject: "
|
|
23
|
-
previewText: "
|
|
22
|
+
subject: "verification.subject",
|
|
23
|
+
previewText: "verification.previewText",
|
|
24
24
|
react: VerificationEmail as FunctionComponent<Record<string, unknown>>,
|
|
25
25
|
},
|
|
26
26
|
organizationInvite: {
|
|
27
27
|
id: "organization-invite",
|
|
28
|
-
subject: "
|
|
29
|
-
previewText: "
|
|
28
|
+
subject: "organizationInvite.subject",
|
|
29
|
+
previewText: "organizationInvite.previewText",
|
|
30
30
|
react: OrganizationInviteEmail as FunctionComponent<Record<string, unknown>>,
|
|
31
31
|
},
|
|
32
32
|
};
|
|
@@ -0,0 +1,36 @@
|
|
|
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;
|
|
@@ -5,7 +5,11 @@ import { EmailModule } from "@m5kdev/backend/modules/email/email.module";
|
|
|
5
5
|
import { NotificationModule } from "@m5kdev/backend/modules/notification/notification.module";
|
|
6
6
|
import { WorkflowModule } from "@m5kdev/backend/modules/workflow/workflow.module";
|
|
7
7
|
import { templates } from "{{PACKAGE_SCOPE}}/email";
|
|
8
|
+
import { emailResources } from "{{PACKAGE_SCOPE}}/email/resources";
|
|
8
9
|
import { APP_NAME } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
|
|
10
|
+
import { AUTH_LOCALE_CONFIG } from "{{PACKAGE_SCOPE}}/shared/modules/app/locale.constants";
|
|
11
|
+
import { APP_ROLES_CONFIG } from "{{PACKAGE_SCOPE}}/shared/modules/app/roles.constants";
|
|
12
|
+
import { USER_LOCALE_HEADER } from "@m5kdev/commons/modules/auth/auth.constants";
|
|
9
13
|
import cors from "cors";
|
|
10
14
|
import express from "express";
|
|
11
15
|
import { schema } from "./generated/schema";
|
|
@@ -46,6 +50,7 @@ app.use(
|
|
|
46
50
|
"Waitlist-Invitation-Code",
|
|
47
51
|
"Organization-Invitation-Code",
|
|
48
52
|
"Admin-Create-Verified-User",
|
|
53
|
+
USER_LOCALE_HEADER,
|
|
49
54
|
],
|
|
50
55
|
})
|
|
51
56
|
);
|
|
@@ -61,6 +66,11 @@ export const builtBackendApp = createBackendApp(
|
|
|
61
66
|
web: appUrl,
|
|
62
67
|
api: serverUrl,
|
|
63
68
|
},
|
|
69
|
+
locales: AUTH_LOCALE_CONFIG,
|
|
70
|
+
roles: APP_ROLES_CONFIG,
|
|
71
|
+
},
|
|
72
|
+
i18n: {
|
|
73
|
+
resources: emailResources,
|
|
64
74
|
},
|
|
65
75
|
redis: {
|
|
66
76
|
url: redisUrl,
|
|
@@ -74,7 +84,7 @@ export const builtBackendApp = createBackendApp(
|
|
|
74
84
|
outputDirectory: ".emails",
|
|
75
85
|
},
|
|
76
86
|
auth: {
|
|
77
|
-
factory({ db, services, appConfig }) {
|
|
87
|
+
factory({ db, services, appConfig, i18n }) {
|
|
78
88
|
return createBetterAuth({
|
|
79
89
|
orm: db.orm as never,
|
|
80
90
|
schema: db.schema as never,
|
|
@@ -82,6 +92,7 @@ export const builtBackendApp = createBackendApp(
|
|
|
82
92
|
email: services.email.email,
|
|
83
93
|
},
|
|
84
94
|
app: appConfig,
|
|
95
|
+
i18n,
|
|
85
96
|
config: {
|
|
86
97
|
waitlist: enableWaitlist,
|
|
87
98
|
provisionedAccountEmailDomain: "provisioned.{{APP_SLUG}}.local",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineAuthRoles } from "@m5kdev/commons/modules/auth/auth.roles";
|
|
2
|
+
|
|
3
|
+
export const APP_ROLES_CONFIG = defineAuthRoles({
|
|
4
|
+
user: {
|
|
5
|
+
roles: ["user", "admin"],
|
|
6
|
+
},
|
|
7
|
+
organization: {
|
|
8
|
+
roles: ["member", "admin", "owner"],
|
|
9
|
+
managerRoles: ["admin", "owner"],
|
|
10
|
+
assignableRoles: ["member", "admin", "owner"],
|
|
11
|
+
defaultRole: "member",
|
|
12
|
+
},
|
|
13
|
+
team: {
|
|
14
|
+
roles: ["owner"],
|
|
15
|
+
defaultRole: "owner",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -5,6 +5,8 @@ import { DialogProvider } from "@m5kdev/web-ui/components/DialogProvider";
|
|
|
5
5
|
import { ThemeProvider } from "@m5kdev/web-ui/components/theme-provider";
|
|
6
6
|
import { AppLoader } from "@m5kdev/web-ui/modules/app/components/AppLoader";
|
|
7
7
|
import { APP_NAME } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
|
|
8
|
+
import { AUTH_LOCALE_CONFIG } from "{{PACKAGE_SCOPE}}/shared/modules/app/locale.constants";
|
|
9
|
+
import { APP_ROLES_CONFIG } from "{{PACKAGE_SCOPE}}/shared/modules/app/roles.constants";
|
|
8
10
|
import { Toaster } from "sonner";
|
|
9
11
|
import { Router } from "./Router";
|
|
10
12
|
|
|
@@ -15,6 +17,8 @@ export function Providers() {
|
|
|
15
17
|
appName: APP_NAME,
|
|
16
18
|
appUrl: import.meta.env.VITE_APP_URL,
|
|
17
19
|
serverUrl: import.meta.env.VITE_SERVER_URL,
|
|
20
|
+
locales: AUTH_LOCALE_CONFIG,
|
|
21
|
+
roles: APP_ROLES_CONFIG,
|
|
18
22
|
}}
|
|
19
23
|
>
|
|
20
24
|
<ThemeProvider defaultTheme="light" storageKey="m5kdev-theme">
|
|
@@ -5,6 +5,7 @@ import { AuthOrganizationChildOrganizationsRoute } from "@m5kdev/web-ui/modules/
|
|
|
5
5
|
import { AuthOrganizationMembersRoute } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationMembersRoute";
|
|
6
6
|
import { AuthOrganizationPreferences } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationPreferences";
|
|
7
7
|
import { AuthPublicRouter } from "@m5kdev/web-ui/modules/auth/components/AuthPublicRouter";
|
|
8
|
+
import { syncI18nLocale } from "@m5kdev/frontend/modules/app/utils/locale";
|
|
8
9
|
import { AuthUserRouter } from "@m5kdev/web-ui/modules/auth/components/AuthUserRouter";
|
|
9
10
|
import { APP_NAME } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
|
|
10
11
|
import type { ReactNode } from "react";
|
|
@@ -65,6 +66,7 @@ export function Router() {
|
|
|
65
66
|
{AuthPublicRouter({
|
|
66
67
|
header: <AuthHeader />,
|
|
67
68
|
waitlist: isWaitlist,
|
|
69
|
+
onLocaleChange: syncI18nLocale,
|
|
68
70
|
})}
|
|
69
71
|
|
|
70
72
|
<Route
|
|
@@ -78,11 +80,11 @@ export function Router() {
|
|
|
78
80
|
<Route path="posts" element={<PostsRoute />} />
|
|
79
81
|
<Route
|
|
80
82
|
path="organization/members"
|
|
81
|
-
element={<AuthOrganizationMembersRoute
|
|
83
|
+
element={<AuthOrganizationMembersRoute />}
|
|
82
84
|
/>
|
|
83
85
|
<Route
|
|
84
86
|
path="organization/manage"
|
|
85
|
-
element={<AuthOrganizationChildOrganizationsRoute
|
|
87
|
+
element={<AuthOrganizationChildOrganizationsRoute />}
|
|
86
88
|
/>
|
|
87
89
|
<Route
|
|
88
90
|
path="organization/preferences"
|
|
@@ -90,13 +92,13 @@ export function Router() {
|
|
|
90
92
|
<AuthOrganizationPreferences
|
|
91
93
|
schema={preferenceSchema}
|
|
92
94
|
controls={preferenceControls}
|
|
93
|
-
managerRoles={["admin", "owner"]}
|
|
94
95
|
/>
|
|
95
96
|
}
|
|
96
97
|
/>
|
|
97
98
|
{AuthUserRouter({
|
|
98
99
|
schema: preferenceSchema,
|
|
99
100
|
controls: preferenceControls,
|
|
101
|
+
onLocaleChange: syncI18nLocale,
|
|
100
102
|
})}
|
|
101
103
|
{AuthAdminRouter({
|
|
102
104
|
enableWaitlist: true,
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Body,
|
|
3
|
-
Button,
|
|
4
|
-
Container,
|
|
5
|
-
Head,
|
|
6
|
-
Heading,
|
|
7
|
-
Hr,
|
|
8
|
-
Html,
|
|
9
|
-
Preview,
|
|
10
|
-
Section,
|
|
11
|
-
Text,
|
|
12
|
-
} from "@react-email/components";
|
|
13
|
-
import type { ReactNode } from "react";
|
|
14
|
-
|
|
15
|
-
interface BaseEmailProps {
|
|
16
|
-
previewText: string;
|
|
17
|
-
title: string;
|
|
18
|
-
eyebrow: string;
|
|
19
|
-
body: ReactNode;
|
|
20
|
-
ctaLabel?: string;
|
|
21
|
-
ctaUrl?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function BaseEmail({ previewText, title, eyebrow, body, ctaLabel, ctaUrl }: BaseEmailProps) {
|
|
25
|
-
return (
|
|
26
|
-
<Html>
|
|
27
|
-
<Head />
|
|
28
|
-
<Preview>{previewText}</Preview>
|
|
29
|
-
<Body
|
|
30
|
-
style={{
|
|
31
|
-
backgroundColor: "#f3efe6",
|
|
32
|
-
fontFamily: "'Manrope', 'Helvetica Neue', Arial, sans-serif",
|
|
33
|
-
margin: "0",
|
|
34
|
-
padding: "32px 0",
|
|
35
|
-
}}
|
|
36
|
-
>
|
|
37
|
-
<Container
|
|
38
|
-
style={{
|
|
39
|
-
backgroundColor: "#fffaf1",
|
|
40
|
-
border: "1px solid #d6c7ab",
|
|
41
|
-
borderRadius: "24px",
|
|
42
|
-
margin: "0 auto",
|
|
43
|
-
maxWidth: "620px",
|
|
44
|
-
padding: "40px",
|
|
45
|
-
}}
|
|
46
|
-
>
|
|
47
|
-
<Text
|
|
48
|
-
style={{
|
|
49
|
-
color: "#8b5e34",
|
|
50
|
-
fontSize: "12px",
|
|
51
|
-
fontWeight: "700",
|
|
52
|
-
letterSpacing: "0.18em",
|
|
53
|
-
margin: "0 0 16px",
|
|
54
|
-
textTransform: "uppercase",
|
|
55
|
-
}}
|
|
56
|
-
>
|
|
57
|
-
{eyebrow}
|
|
58
|
-
</Text>
|
|
59
|
-
<Heading
|
|
60
|
-
style={{
|
|
61
|
-
color: "#171314",
|
|
62
|
-
fontFamily: "'Cormorant Garamond', Georgia, serif",
|
|
63
|
-
fontSize: "34px",
|
|
64
|
-
lineHeight: "1.1",
|
|
65
|
-
margin: "0 0 20px",
|
|
66
|
-
}}
|
|
67
|
-
>
|
|
68
|
-
{title}
|
|
69
|
-
</Heading>
|
|
70
|
-
<Section
|
|
71
|
-
style={{
|
|
72
|
-
color: "#463630",
|
|
73
|
-
fontSize: "16px",
|
|
74
|
-
lineHeight: "1.7",
|
|
75
|
-
}}
|
|
76
|
-
>
|
|
77
|
-
{body}
|
|
78
|
-
</Section>
|
|
79
|
-
{ctaLabel && ctaUrl ? (
|
|
80
|
-
<Section style={{ marginTop: "28px" }}>
|
|
81
|
-
<Button
|
|
82
|
-
href={ctaUrl}
|
|
83
|
-
style={{
|
|
84
|
-
backgroundColor: "#1f4f46",
|
|
85
|
-
borderRadius: "999px",
|
|
86
|
-
color: "#fdf9f1",
|
|
87
|
-
fontSize: "15px",
|
|
88
|
-
fontWeight: "700",
|
|
89
|
-
padding: "14px 24px",
|
|
90
|
-
textDecoration: "none",
|
|
91
|
-
}}
|
|
92
|
-
>
|
|
93
|
-
{ctaLabel}
|
|
94
|
-
</Button>
|
|
95
|
-
</Section>
|
|
96
|
-
) : null}
|
|
97
|
-
<Hr
|
|
98
|
-
style={{
|
|
99
|
-
borderColor: "#e5dccd",
|
|
100
|
-
margin: "28px 0",
|
|
101
|
-
}}
|
|
102
|
-
/>
|
|
103
|
-
<Text
|
|
104
|
-
style={{
|
|
105
|
-
color: "#7f6b5d",
|
|
106
|
-
fontSize: "13px",
|
|
107
|
-
lineHeight: "1.6",
|
|
108
|
-
margin: "0",
|
|
109
|
-
}}
|
|
110
|
-
>
|
|
111
|
-
Sent by {{APP_NAME}}.
|
|
112
|
-
</Text>
|
|
113
|
-
</Container>
|
|
114
|
-
</Body>
|
|
115
|
-
</Html>
|
|
116
|
-
);
|
|
117
|
-
}
|