create-m5kdev 0.35.0 → 0.36.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/apps/e2e/servers.ts.tpl +3 -0
- package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +2 -34
- package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +225 -5
- package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +47 -1
- package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +290 -8
- package/templates/minimal-app/apps/expo/app.json.tpl +1 -0
- package/templates/minimal-app/apps/expo/package.json.tpl +4 -0
- package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +4 -1
- package/templates/minimal-app/apps/expo/src/app/notifications.tsx.tpl +70 -0
- package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +8 -16
- package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +4 -1
- package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +11 -2
- package/templates/minimal-app/apps/landing/vite.config.ts.tpl +2 -3
- package/templates/minimal-app/apps/server/db.e2e.ts.tpl +5 -40
- package/templates/minimal-app/apps/server/db.ts.tpl +5 -42
- package/templates/minimal-app/apps/server/drizzle/{0000_wealthy_absorbing_man.sql → 0000_whole_echo.sql} +102 -3
- package/templates/minimal-app/apps/server/drizzle/0001_yummy_spectrum.sql +26 -0
- package/templates/minimal-app/apps/server/drizzle/0002_crazy_lilith.sql +47 -0
- package/templates/minimal-app/apps/server/drizzle/0003_freezing_fantastic_four.sql +4 -0
- package/templates/minimal-app/apps/server/drizzle/0004_slow_ezekiel_stane.sql +99 -0
- package/templates/minimal-app/apps/server/drizzle/0005_wonderful_the_professor.sql +2 -0
- package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +2625 -1780
- package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +2671 -1796
- package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +2716 -1818
- package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +715 -164
- package/templates/minimal-app/apps/server/drizzle/meta/0004_snapshot.json.tpl +2498 -0
- package/templates/minimal-app/apps/server/drizzle/meta/0005_snapshot.json.tpl +2484 -0
- package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +23 -9
- package/templates/minimal-app/apps/server/src/app.ts.tpl +4 -1
- package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +2 -2
- package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +1 -1
- package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +13 -36
- package/templates/minimal-app/apps/server/src/schema.ts.tpl +3 -2
- package/templates/minimal-app/apps/shared/package.json.tpl +1 -0
- package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +1 -1
- package/templates/minimal-app/apps/shared/src/modules/notification/notification.constants.ts.tpl +5 -0
- package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +1 -1
- package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +17 -1
- package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +10 -8
- package/templates/minimal-app/apps/webapp/src/modules/notification/NotificationPreferencesRoute.tsx.tpl +29 -0
- package/templates/minimal-app/apps/webapp/src/modules/notification/StarterWebPush.tsx.tpl +44 -0
- package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +8 -0
- package/templates/minimal-app/pnpm-workspace.yaml.tpl +12 -11
- package/templates/minimal-app/apps/server/drizzle/0001_third_ink.sql +0 -1
- package/templates/minimal-app/apps/server/drizzle/0002_concerned_proudstar.sql +0 -3
- package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +0 -27
|
@@ -5,30 +5,44 @@
|
|
|
5
5
|
{
|
|
6
6
|
"idx": 0,
|
|
7
7
|
"version": "6",
|
|
8
|
-
"when":
|
|
9
|
-
"tag": "
|
|
8
|
+
"when": 1787759631723,
|
|
9
|
+
"tag": "0000_whole_echo",
|
|
10
10
|
"breakpoints": true
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"idx": 1,
|
|
14
14
|
"version": "6",
|
|
15
|
-
"when":
|
|
16
|
-
"tag": "
|
|
15
|
+
"when": 1788197370969,
|
|
16
|
+
"tag": "0001_yummy_spectrum",
|
|
17
17
|
"breakpoints": true
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
"idx": 2,
|
|
21
21
|
"version": "6",
|
|
22
|
-
"when":
|
|
23
|
-
"tag": "
|
|
22
|
+
"when": 1788197859737,
|
|
23
|
+
"tag": "0002_crazy_lilith",
|
|
24
24
|
"breakpoints": true
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"idx": 3,
|
|
28
28
|
"version": "6",
|
|
29
|
-
"when":
|
|
30
|
-
"tag": "
|
|
29
|
+
"when": 1788198203749,
|
|
30
|
+
"tag": "0003_freezing_fantastic_four",
|
|
31
|
+
"breakpoints": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"idx": 4,
|
|
35
|
+
"version": "6",
|
|
36
|
+
"when": 1788198833389,
|
|
37
|
+
"tag": "0004_slow_ezekiel_stane",
|
|
38
|
+
"breakpoints": true
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"idx": 5,
|
|
42
|
+
"version": "6",
|
|
43
|
+
"when": 1788253467922,
|
|
44
|
+
"tag": "0005_wonderful_the_professor",
|
|
31
45
|
"breakpoints": true
|
|
32
46
|
}
|
|
33
47
|
]
|
|
34
|
-
}
|
|
48
|
+
}
|
|
@@ -23,6 +23,9 @@ import {
|
|
|
23
23
|
APP_NAME,
|
|
24
24
|
APP_ROLES_CONFIG,
|
|
25
25
|
} from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
|
|
26
|
+
// m5k:notifications:start
|
|
27
|
+
import { NOTIFICATION_KINDS } from "{{PACKAGE_SCOPE}}/shared/modules/notification/notification.constants";
|
|
28
|
+
// m5k:notifications:end
|
|
26
29
|
// m5k:ai:start
|
|
27
30
|
import { mastra, openrouter } from "./modules/conversation/conversation.mastra";
|
|
28
31
|
// m5k:ai:end
|
|
@@ -138,7 +141,7 @@ export const builtBackendApp = createBackendApp(
|
|
|
138
141
|
new DemoWorkflowModule(),
|
|
139
142
|
// m5k:workflows:end
|
|
140
143
|
// m5k:notifications:start
|
|
141
|
-
new NotificationModule(),
|
|
144
|
+
new NotificationModule({ kinds: NOTIFICATION_KINDS }),
|
|
142
145
|
// m5k:notifications:end
|
|
143
146
|
new PostsModule(),
|
|
144
147
|
new EmailPreviewModule({ allowDelete: process.env.NODE_ENV !== "production" }),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { members, organizations,
|
|
1
|
+
import { members, organizations, users } from "@m5kdev/backend/modules/auth/auth.db";
|
|
2
2
|
import { integer, sqliteTable as table, text } from "drizzle-orm/sqlite-core";
|
|
3
3
|
import { v4 as uuidv4 } from "uuid";
|
|
4
4
|
|
|
@@ -13,7 +13,7 @@ export const posts = table("posts", {
|
|
|
13
13
|
organizationId: text("organization_id").references(() => organizations.id, {
|
|
14
14
|
onDelete: "set null",
|
|
15
15
|
}),
|
|
16
|
-
teamId: text("team_id")
|
|
16
|
+
teamId: text("team_id"),
|
|
17
17
|
title: text("title").notNull(),
|
|
18
18
|
slug: text("slug").notNull().unique(),
|
|
19
19
|
excerpt: text("excerpt"),
|
|
@@ -50,7 +50,7 @@ export class PostsService extends BasePermissionService<
|
|
|
50
50
|
authorUserId: ctx.user.id,
|
|
51
51
|
memberId: ctx.actor.memberId ?? null,
|
|
52
52
|
organizationId: ctx.session.activeOrganizationId ?? null,
|
|
53
|
-
teamId:
|
|
53
|
+
teamId: null,
|
|
54
54
|
title: input.title.trim(),
|
|
55
55
|
slug: uniqueSlug.value,
|
|
56
56
|
excerpt: createExcerpt(input.excerpt, input.content),
|
package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type ModuleExpressContext,
|
|
4
4
|
type TableMap,
|
|
5
5
|
} from "@m5kdev/backend/modules/base/base.module";
|
|
6
|
-
import { and, desc, eq, isNotNull } from "drizzle-orm";
|
|
6
|
+
import { and, desc, eq, isNotNull, isNull } from "drizzle-orm";
|
|
7
7
|
import type { Request, Response } from "express";
|
|
8
8
|
import { v4 as uuidv4 } from "uuid";
|
|
9
9
|
|
|
@@ -77,13 +77,14 @@ export class TestHarnessModule extends BaseModule<
|
|
|
77
77
|
type: db.schema.organizations.type,
|
|
78
78
|
parentId: db.schema.organizations.parentId,
|
|
79
79
|
role: db.schema.members.role,
|
|
80
|
+
memberId: db.schema.members.id,
|
|
80
81
|
})
|
|
81
82
|
.from(db.schema.members)
|
|
82
83
|
.innerJoin(
|
|
83
84
|
db.schema.organizations,
|
|
84
85
|
eq(db.schema.members.organizationId, db.schema.organizations.id)
|
|
85
86
|
)
|
|
86
|
-
.where(eq(db.schema.members.userId, user.id));
|
|
87
|
+
.where(and(eq(db.schema.members.userId, user.id), isNull(db.schema.members.deletedAt)));
|
|
87
88
|
|
|
88
89
|
const [credentialAccount] = await db.orm
|
|
89
90
|
.select({ id: db.schema.accounts.id })
|
|
@@ -99,21 +100,16 @@ export class TestHarnessModule extends BaseModule<
|
|
|
99
100
|
|
|
100
101
|
const accountClaims = await db.orm
|
|
101
102
|
.select({
|
|
102
|
-
id: db.schema.
|
|
103
|
-
status: db.schema.
|
|
104
|
-
claimUserId: db.schema.
|
|
105
|
-
claimedEmail: db.schema.
|
|
106
|
-
claimedAt: db.schema.
|
|
107
|
-
expiresAt: db.schema.
|
|
103
|
+
id: db.schema.accountClaims.id,
|
|
104
|
+
status: db.schema.accountClaims.status,
|
|
105
|
+
claimUserId: db.schema.accountClaims.claimUserId,
|
|
106
|
+
claimedEmail: db.schema.accountClaims.claimedEmail,
|
|
107
|
+
claimedAt: db.schema.accountClaims.claimedAt,
|
|
108
|
+
expiresAt: db.schema.accountClaims.expiresAt,
|
|
108
109
|
})
|
|
109
|
-
.from(db.schema.
|
|
110
|
-
.where(
|
|
111
|
-
|
|
112
|
-
eq(db.schema.waitlist.type, "ACCOUNT_CLAIM"),
|
|
113
|
-
eq(db.schema.waitlist.claimUserId, user.id)
|
|
114
|
-
)
|
|
115
|
-
)
|
|
116
|
-
.orderBy(desc(db.schema.waitlist.createdAt));
|
|
110
|
+
.from(db.schema.accountClaims)
|
|
111
|
+
.where(eq(db.schema.accountClaims.claimUserId, user.id))
|
|
112
|
+
.orderBy(desc(db.schema.accountClaims.createdAt));
|
|
117
113
|
|
|
118
114
|
const [latestSession] = await db.orm
|
|
119
115
|
.select({
|
|
@@ -123,7 +119,6 @@ export class TestHarnessModule extends BaseModule<
|
|
|
123
119
|
activeOrganizationRole: db.schema.sessions.activeOrganizationRole,
|
|
124
120
|
activeOrganizationMemberId: db.schema.sessions.activeOrganizationMemberId,
|
|
125
121
|
activeOrganizationType: db.schema.sessions.activeOrganizationType,
|
|
126
|
-
activeTeamId: db.schema.sessions.activeTeamId,
|
|
127
122
|
impersonatedBy: db.schema.sessions.impersonatedBy,
|
|
128
123
|
createdAt: db.schema.sessions.createdAt,
|
|
129
124
|
})
|
|
@@ -172,7 +167,6 @@ export class TestHarnessModule extends BaseModule<
|
|
|
172
167
|
const now = new Date();
|
|
173
168
|
const userId = uuidv4();
|
|
174
169
|
const organizationId = uuidv4();
|
|
175
|
-
const teamId = uuidv4();
|
|
176
170
|
const claimId = createClaim ? uuidv4() : null;
|
|
177
171
|
|
|
178
172
|
await db.orm.transaction(async (tx) => {
|
|
@@ -202,26 +196,9 @@ export class TestHarnessModule extends BaseModule<
|
|
|
202
196
|
createdAt: now,
|
|
203
197
|
});
|
|
204
198
|
|
|
205
|
-
await tx.insert(db.schema.teams).values({
|
|
206
|
-
id: teamId,
|
|
207
|
-
name: "Editorial",
|
|
208
|
-
organizationId,
|
|
209
|
-
createdAt: now,
|
|
210
|
-
updatedAt: now,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
await tx.insert(db.schema.teamMembers).values({
|
|
214
|
-
id: uuidv4(),
|
|
215
|
-
teamId,
|
|
216
|
-
userId,
|
|
217
|
-
role: "owner",
|
|
218
|
-
createdAt: now,
|
|
219
|
-
});
|
|
220
|
-
|
|
221
199
|
if (claimId) {
|
|
222
|
-
await tx.insert(db.schema.
|
|
200
|
+
await tx.insert(db.schema.accountClaims).values({
|
|
223
201
|
id: claimId,
|
|
224
|
-
type: "ACCOUNT_CLAIM",
|
|
225
202
|
status: "INVITED",
|
|
226
203
|
claimUserId: userId,
|
|
227
204
|
code: uuidv4(),
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// biome-ignore-all assist/source/organizeImports: feature-gated table exports stay in marker blocks
|
|
2
2
|
export {
|
|
3
3
|
accountClaimMagicLinks,
|
|
4
|
+
accountClaims,
|
|
4
5
|
accounts,
|
|
5
6
|
apikeys,
|
|
6
7
|
invitations,
|
|
7
8
|
members,
|
|
8
9
|
organizations,
|
|
9
10
|
sessions,
|
|
10
|
-
teamMembers,
|
|
11
|
-
teams,
|
|
12
11
|
users,
|
|
13
12
|
verifications,
|
|
14
13
|
waitlist,
|
|
@@ -22,7 +21,9 @@ export { files } from "@m5kdev/backend/modules/file/file.db";
|
|
|
22
21
|
// m5k:notifications:start
|
|
23
22
|
export {
|
|
24
23
|
notificationDevices,
|
|
24
|
+
notificationPreferences,
|
|
25
25
|
notificationSendLogs,
|
|
26
|
+
notifications,
|
|
26
27
|
} from "@m5kdev/backend/modules/notification/notification.db";
|
|
27
28
|
// m5k:notifications:end
|
|
28
29
|
// m5k:workflows:start
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"./modules/app/*": "./src/modules/app/*.ts",
|
|
22
22
|
"./modules/conversation/*": "./src/modules/conversation/*.ts",
|
|
23
23
|
"./modules/demo-workflow/*": "./src/modules/demo-workflow/*.ts",
|
|
24
|
+
"./modules/notification/*": "./src/modules/notification/*.ts",
|
|
24
25
|
"./modules/posts/*": "./src/modules/posts/*.ts"
|
|
25
26
|
}
|
|
26
27
|
}
|
|
@@ -18,7 +18,7 @@ export const APP_ROLES_CONFIG = defineAuthRoles({
|
|
|
18
18
|
organization: {
|
|
19
19
|
roles: ["member", "admin", "owner"],
|
|
20
20
|
managerRoles: ["admin", "owner"],
|
|
21
|
-
assignableRoles: ["member", "admin"
|
|
21
|
+
assignableRoles: ["member", "admin"],
|
|
22
22
|
defaultRole: "member",
|
|
23
23
|
},
|
|
24
24
|
team: {
|
package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
/** Re-export shared
|
|
1
|
+
/** Re-export shared Notification contracts from commons. */
|
|
2
2
|
export * from "@m5kdev/commons/modules/notification/notification.constants";
|
|
3
3
|
export * from "@m5kdev/commons/modules/notification/notification.schema";
|
|
@@ -7,8 +7,11 @@ import { AppSidebarHeader } from "@m5kdev/web-ui/modules/app/components/AppSideb
|
|
|
7
7
|
import { AppSidebarUser } from "@m5kdev/web-ui/modules/app/components/AppSidebarUser";
|
|
8
8
|
import { AuthOrganizationSelect } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationSelect";
|
|
9
9
|
import { AuthUtilityImpersonationBanner } from "@m5kdev/web-ui/modules/auth/components/AuthUtilityImpersonationBanner";
|
|
10
|
+
// m5k:notifications:start
|
|
11
|
+
import { NotificationSidebarInbox } from "@m5kdev/web-ui/modules/notification/components/NotificationSidebarInbox";
|
|
12
|
+
// m5k:notifications:end
|
|
10
13
|
import { APP_NAME } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
|
|
11
|
-
import { FileIcon, UsersIcon } from "lucide-react";
|
|
14
|
+
import { BellIcon, FileIcon, UsersIcon } from "lucide-react";
|
|
12
15
|
// m5k:ai:start
|
|
13
16
|
import { SparklesIcon } from "lucide-react";
|
|
14
17
|
// m5k:ai:end
|
|
@@ -21,6 +24,9 @@ import { WorkflowIcon } from "lucide-react";
|
|
|
21
24
|
import { useState } from "react";
|
|
22
25
|
import { useTranslation } from "react-i18next";
|
|
23
26
|
import { useNavigate } from "react-router";
|
|
27
|
+
// m5k:notifications:start
|
|
28
|
+
import { StarterWebPush } from "./modules/notification/StarterWebPush";
|
|
29
|
+
// m5k:notifications:end
|
|
24
30
|
|
|
25
31
|
export function Layout() {
|
|
26
32
|
const { data: session } = useSession();
|
|
@@ -42,6 +48,9 @@ export function Layout() {
|
|
|
42
48
|
header: <AppSidebarHeader logo={{ src: "/logo.svg", alt: APP_NAME }} title={APP_NAME} />,
|
|
43
49
|
content: (
|
|
44
50
|
<>
|
|
51
|
+
{/* m5k:notifications:start */}
|
|
52
|
+
<NotificationSidebarInbox enablePush={<StarterWebPush />} />
|
|
53
|
+
{/* m5k:notifications:end */}
|
|
45
54
|
<div className="px-2 group-data-[collapsible=icon]:hidden">
|
|
46
55
|
<AuthOrganizationSelect />
|
|
47
56
|
</div>
|
|
@@ -73,6 +82,13 @@ export function Layout() {
|
|
|
73
82
|
link: "/workflows",
|
|
74
83
|
},
|
|
75
84
|
// m5k:workflows:end
|
|
85
|
+
// m5k:notifications:start
|
|
86
|
+
{
|
|
87
|
+
label: t("layout.navigation.notifications"),
|
|
88
|
+
icon: <BellIcon />,
|
|
89
|
+
link: "/notifications",
|
|
90
|
+
},
|
|
91
|
+
// m5k:notifications:end
|
|
76
92
|
{
|
|
77
93
|
label: t("layout.navigation.members"),
|
|
78
94
|
icon: <UsersIcon />,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// biome-ignore-all assist/source/organizeImports: feature-gated route imports stay in marker blocks
|
|
2
2
|
import { syncI18nLocale } from "@m5kdev/frontend/modules/app/utils/locale";
|
|
3
3
|
import { AuthAdminRouter } from "@m5kdev/web-ui/modules/auth/components/AuthAdminRouter";
|
|
4
|
-
import { AuthOrganizationAcceptInvitationRoute } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationAcceptInvitationRoute";
|
|
5
4
|
import { AuthOrganizationRouter } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationRouter";
|
|
6
5
|
import { AuthPublicRouter } from "@m5kdev/web-ui/modules/auth/components/AuthPublicRouter";
|
|
7
6
|
import { AuthUserRouter } from "@m5kdev/web-ui/modules/auth/components/AuthUserRouter";
|
|
@@ -20,6 +19,9 @@ import { FilesRoute } from "@/modules/files/FilesRoute";
|
|
|
20
19
|
// m5k:workflows:start
|
|
21
20
|
import { WorkflowsRoute } from "@/modules/workflows/WorkflowsRoute";
|
|
22
21
|
// m5k:workflows:end
|
|
22
|
+
// m5k:notifications:start
|
|
23
|
+
import { NotificationPreferencesRoute } from "@/modules/notification/NotificationPreferencesRoute";
|
|
24
|
+
// m5k:notifications:end
|
|
23
25
|
import { Layout } from "./Layout";
|
|
24
26
|
|
|
25
27
|
const preferenceSchema = z.object({
|
|
@@ -68,13 +70,6 @@ export function Router() {
|
|
|
68
70
|
onLocaleChange: syncI18nLocale,
|
|
69
71
|
})}
|
|
70
72
|
|
|
71
|
-
{/* public: a logged-out invitee must reach this route so it can send
|
|
72
|
-
them to signup — inside ProtectedRoutes they'd bounce to /login */}
|
|
73
|
-
<Route
|
|
74
|
-
path="/organization/accept-invitation"
|
|
75
|
-
element={<AuthOrganizationAcceptInvitationRoute />}
|
|
76
|
-
/>
|
|
77
|
-
|
|
78
73
|
<Route
|
|
79
74
|
element={
|
|
80
75
|
<ProtectedRoutes>
|
|
@@ -93,6 +88,13 @@ export function Router() {
|
|
|
93
88
|
// m5k:workflows:start
|
|
94
89
|
<Route key="workflows" path="workflows" element={<WorkflowsRoute />} />,
|
|
95
90
|
// m5k:workflows:end
|
|
91
|
+
// m5k:notifications:start
|
|
92
|
+
<Route
|
|
93
|
+
key="notifications"
|
|
94
|
+
path="notifications"
|
|
95
|
+
element={<NotificationPreferencesRoute />}
|
|
96
|
+
/>,
|
|
97
|
+
// m5k:notifications:end
|
|
96
98
|
]}
|
|
97
99
|
|
|
98
100
|
{AuthUserRouter({
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Card } from "@heroui/react";
|
|
2
|
+
import { NotificationPreferences } from "@m5kdev/web-ui/modules/notification/components/NotificationPreferences";
|
|
3
|
+
import { NOTIFICATION_KINDS } from "{{PACKAGE_SCOPE}}/shared/modules/notification/notification.constants";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
import { StarterWebPush } from "./StarterWebPush";
|
|
6
|
+
|
|
7
|
+
export function NotificationPreferencesRoute() {
|
|
8
|
+
const { t } = useTranslation("{{APP_SLUG}}");
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="grid gap-6 p-4" data-testid="notification-preferences-route">
|
|
12
|
+
<div className="justify-self-start">
|
|
13
|
+
<StarterWebPush />
|
|
14
|
+
</div>
|
|
15
|
+
<Card>
|
|
16
|
+
<Card.Header className="flex flex-col gap-1">
|
|
17
|
+
<p className="text-xs font-medium uppercase tracking-wide text-muted">
|
|
18
|
+
{t("notifications.hero.eyebrow")}
|
|
19
|
+
</p>
|
|
20
|
+
<Card.Title>{t("notifications.hero.title")}</Card.Title>
|
|
21
|
+
<Card.Description>{t("notifications.hero.body")}</Card.Description>
|
|
22
|
+
</Card.Header>
|
|
23
|
+
<Card.Content>
|
|
24
|
+
<NotificationPreferences kinds={NOTIFICATION_KINDS} offerEmail />
|
|
25
|
+
</Card.Content>
|
|
26
|
+
</Card>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Button } from "@heroui/react";
|
|
2
|
+
import { useSession } from "@m5kdev/frontend/modules/auth/hooks/useSession";
|
|
3
|
+
import { useWebPush } from "@m5kdev/web-ui/hooks/useWebPush";
|
|
4
|
+
import { BellRingIcon } from "lucide-react";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import { useTranslation } from "react-i18next";
|
|
7
|
+
import { useTRPC } from "../../utils/trpc";
|
|
8
|
+
|
|
9
|
+
export function StarterWebPush(): ReactNode {
|
|
10
|
+
const { t } = useTranslation("{{APP_SLUG}}");
|
|
11
|
+
const { data: session } = useSession();
|
|
12
|
+
const trpc = useTRPC();
|
|
13
|
+
const webPush = useWebPush({
|
|
14
|
+
enabled: Boolean(session?.user?.id),
|
|
15
|
+
messages: {
|
|
16
|
+
unsupported: t("layout.push.unsupported"),
|
|
17
|
+
denied: t("layout.push.denied"),
|
|
18
|
+
noVapid: t("layout.push.noVapid"),
|
|
19
|
+
badSubscription: t("layout.push.badSubscription"),
|
|
20
|
+
failed: t("layout.push.failed"),
|
|
21
|
+
enabled: t("layout.push.enabled"),
|
|
22
|
+
},
|
|
23
|
+
vapidPublicKeyQuery: trpc.notification.vapidPublicKey.queryOptions(),
|
|
24
|
+
registerDeviceMutation: trpc.notification.registerDevice.mutationOptions(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (!session?.user?.id || webPush.flowStatus === "done") return null;
|
|
28
|
+
|
|
29
|
+
const label = webPush.permission === "denied" ? t("layout.push.denied") : t("layout.push.cta");
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Button
|
|
33
|
+
size="sm"
|
|
34
|
+
variant="secondary"
|
|
35
|
+
isDisabled={!webPush.canSubscribe || webPush.isWorking}
|
|
36
|
+
onPress={() => {
|
|
37
|
+
void webPush.subscribe();
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<BellRingIcon className="size-4" />
|
|
41
|
+
{label}
|
|
42
|
+
</Button>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"organization": "Organization",
|
|
20
20
|
"account": "Account",
|
|
21
21
|
"members": "Members",
|
|
22
|
+
"notifications": "Notifications",
|
|
22
23
|
"childOrgs": "Child orgs",
|
|
23
24
|
"orgPreferences": "Org preferences",
|
|
24
25
|
"profile": "Profile",
|
|
@@ -184,6 +185,13 @@
|
|
|
184
185
|
"failed": "Upload failed."
|
|
185
186
|
}
|
|
186
187
|
},
|
|
188
|
+
"notifications": {
|
|
189
|
+
"hero": {
|
|
190
|
+
"eyebrow": "This membership",
|
|
191
|
+
"title": "Notification preferences",
|
|
192
|
+
"body": "Mute channels for this organization. Other organizations keep their own settings."
|
|
193
|
+
}
|
|
194
|
+
},
|
|
187
195
|
"auth": {
|
|
188
196
|
"header": {
|
|
189
197
|
"eyebrow": "Framework Starter",
|
|
@@ -7,17 +7,18 @@ catalogs:
|
|
|
7
7
|
"@heroui/react": 3.0.3
|
|
8
8
|
"@heroui/styles": 3.0.3
|
|
9
9
|
"@libsql/client": 0.17.4
|
|
10
|
-
"@m5kdev/backend": 0.
|
|
11
|
-
"@m5kdev/commons": 0.
|
|
12
|
-
"@m5kdev/config": 0.
|
|
13
|
-
"@m5kdev/email": 0.
|
|
14
|
-
"@m5kdev/
|
|
15
|
-
"@m5kdev/
|
|
16
|
-
"@m5kdev/module-
|
|
17
|
-
"@m5kdev/module-
|
|
18
|
-
"@m5kdev/module-
|
|
19
|
-
"@m5kdev/module-
|
|
20
|
-
"@m5kdev/
|
|
10
|
+
"@m5kdev/backend": 0.36.0
|
|
11
|
+
"@m5kdev/commons": 0.36.0
|
|
12
|
+
"@m5kdev/config": 0.36.0
|
|
13
|
+
"@m5kdev/email": 0.36.0
|
|
14
|
+
"@m5kdev/expo": 0.36.0
|
|
15
|
+
"@m5kdev/frontend": 0.36.0
|
|
16
|
+
"@m5kdev/module-clay": 0.36.0
|
|
17
|
+
"@m5kdev/module-docx": 0.36.0
|
|
18
|
+
"@m5kdev/module-pdf": 0.36.0
|
|
19
|
+
"@m5kdev/module-social": 0.36.0
|
|
20
|
+
"@m5kdev/module-video": 0.36.0
|
|
21
|
+
"@m5kdev/web-ui": 0.36.0
|
|
21
22
|
"@mastra/core": 1.51.0
|
|
22
23
|
"@openrouter/ai-sdk-provider": 3.0.0
|
|
23
24
|
"@opentelemetry/api": 1.9.0
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
ALTER TABLE `posts` ADD `member_id` text REFERENCES members(id);
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
CREATE TABLE `files` (
|
|
2
|
-
`id` text PRIMARY KEY NOT NULL,
|
|
3
|
-
`created_at` integer NOT NULL,
|
|
4
|
-
`updated_at` integer,
|
|
5
|
-
`deleted_at` integer,
|
|
6
|
-
`user_id` text,
|
|
7
|
-
`member_id` text,
|
|
8
|
-
`organization_id` text,
|
|
9
|
-
`team_id` text,
|
|
10
|
-
`bucket` text NOT NULL,
|
|
11
|
-
`key` text NOT NULL,
|
|
12
|
-
`original_name` text NOT NULL,
|
|
13
|
-
`original_extension` text,
|
|
14
|
-
`content_type` text NOT NULL,
|
|
15
|
-
`size_bytes` integer,
|
|
16
|
-
`etag` text,
|
|
17
|
-
`checksum_sha256` text,
|
|
18
|
-
`metadata` text,
|
|
19
|
-
`status` text NOT NULL,
|
|
20
|
-
`uploaded_at` integer,
|
|
21
|
-
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE set null,
|
|
22
|
-
FOREIGN KEY (`member_id`) REFERENCES `members`(`id`) ON UPDATE no action ON DELETE set null,
|
|
23
|
-
FOREIGN KEY (`organization_id`) REFERENCES `organizations`(`id`) ON UPDATE no action ON DELETE set null,
|
|
24
|
-
FOREIGN KEY (`team_id`) REFERENCES `teams`(`id`) ON UPDATE no action ON DELETE set null
|
|
25
|
-
);
|
|
26
|
-
--> statement-breakpoint
|
|
27
|
-
CREATE UNIQUE INDEX `files_bucket_key_unique` ON `files` (`bucket`,`key`);
|