create-bw-app 0.13.2 → 0.14.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/README.md +2 -2
- package/package.json +1 -1
- package/src/app-manifest.mjs +4 -2
- package/src/constants.mjs +95 -18
- package/src/doctor.mjs +38 -0
- package/src/generator.mjs +75 -19
- package/template/base/app/(auth)/auth/confirmed/page.tsx +1 -0
- package/template/base/app/(auth)/auth/post-login/page.tsx +1 -0
- package/template/base/app/(auth)/auth-provider.tsx +25 -0
- package/template/base/app/(auth)/forgot-password/page.tsx +1 -0
- package/template/base/app/(auth)/invite/[invitationId]/invite-route-mount.tsx +10 -0
- package/template/base/app/(auth)/invite/[invitationId]/page.tsx +1 -0
- package/template/base/app/(auth)/layout.tsx +5 -0
- package/template/base/app/(auth)/login/page.tsx +1 -0
- package/template/base/app/(auth)/reset-password/page.tsx +1 -0
- package/template/base/app/(auth)/signup/page.tsx +1 -0
- package/template/base/app/(shell)/account/page.tsx +5 -0
- package/template/base/app/(shell)/dashboard/dashboard-live-mount.tsx +21 -0
- package/template/base/app/(shell)/dashboard/page.tsx +1 -0
- package/template/base/app/(shell)/layout.tsx +24 -0
- package/template/base/app/(shell)/shell-layout-client.tsx +134 -0
- package/template/base/app/api/account/route.ts +2 -0
- package/template/base/app/auth/callback/route.ts +1 -0
- package/template/base/app/auth/cleanup/route.ts +1 -0
- package/template/base/app/layout.tsx +8 -2
- package/template/base/config/env.ts +98 -0
- package/template/module-manifests/marketing/brightweb.module.json +12 -0
- package/template/modules/marketing/app/(shell)/marketing/page.tsx +1 -0
- package/template/modules/marketing/app/api/marketing/_handlers.ts +144 -0
- package/template/modules/marketing/app/api/marketing/analytics/campaigns/[id]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/analytics/overview/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/analytics/segments/[id]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/cancel/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/recipients/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/retry/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/schedule/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/send/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/test/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/segments/[id]/preview/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/segments/[id]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/segments/preview/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/segments/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/topics/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/unsubscribe/[token]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/webhooks/resend/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/worker/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/[id]/activate/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/[id]/nodes/[nodeId]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/[id]/nodes/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/[id]/pause/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/[id]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/[id]/runs/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/route.ts +2 -0
- package/template/modules/projects/app/(shell)/projects/[id]/board/page.tsx +1 -0
- package/template/modules/projects/app/(shell)/projects/[id]/page.tsx +1 -0
- package/template/modules/projects/app/(shell)/projects/[id]/tasks/page.tsx +1 -0
- package/template/modules/projects/app/(shell)/projects/layout.tsx +5 -0
- package/template/modules/projects/app/(shell)/projects/page.tsx +1 -0
- package/template/modules/projects/app/(shell)/projects/projects-live-mounts.tsx +27 -0
- package/template/modules/projects/app/(shell)/projects/projects-server-mounts.tsx +54 -0
- package/template/modules/projects/app/api/projects/[id]/activity/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/links/[itemId]/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/links/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/members/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/milestones/[itemId]/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/milestones/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/tasks/[itemId]/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/tasks/route.ts +2 -0
- package/template/modules/projects/app/api/projects/_handlers.ts +86 -0
- package/template/modules/projects/app/api/projects/organizations/route.ts +2 -0
- package/template/modules/projects/app/api/projects/route.ts +2 -0
- package/template/modules/projects/app/api/projects/stats/route.ts +2 -0
- package/template/supabase/README.md +3 -0
- package/template/supabase/module-registry.json +6 -0
- package/template/supabase/modules/core/migrations/20260726180000_enable_rate_limit_counters_rls.sql +2 -0
- package/template/supabase/modules/marketing/README.md +12 -0
- package/template/supabase/modules/marketing/migrations/20260725120000_marketing_v1.sql +118 -0
- package/template/supabase/modules/marketing/migrations/20260725130000_marketing_campaigns.sql +133 -0
- package/template/supabase/modules/marketing/migrations/20260726120000_marketing_segments.sql +41 -0
- package/template/supabase/modules/marketing/migrations/20260726150000_marketing_workflows.sql +174 -0
- package/template/supabase/modules/marketing/migrations/20260726170000_marketing_engine_safety.sql +68 -0
- /package/template/modules/admin/app/{admin → (shell)/admin}/users/page.tsx +0 -0
- /package/template/modules/crm/app/{crm → (shell)/crm}/layout.tsx +0 -0
- /package/template/modules/crm/app/{crm → (shell)/crm}/page.tsx +0 -0
- /package/template/modules/crm/app/{crm → (shell)/crm}/report/page.tsx +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo, type ReactNode } from "react";
|
|
4
|
+
import { usePathname, useRouter } from "next/navigation";
|
|
5
|
+
import { Users } from "lucide-react";
|
|
6
|
+
import {
|
|
7
|
+
AppHeader,
|
|
8
|
+
AppShellFrame,
|
|
9
|
+
DesktopSidebar,
|
|
10
|
+
MobileNav,
|
|
11
|
+
computeInitials,
|
|
12
|
+
getShellNavGroup,
|
|
13
|
+
useShellNavState,
|
|
14
|
+
type NavGroupConfig,
|
|
15
|
+
type ShellNavStateGroup,
|
|
16
|
+
} from "@brightweblabs/app-shell";
|
|
17
|
+
import { createAuthUiClient } from "@brightweblabs/core-auth/ui";
|
|
18
|
+
import { Toaster } from "@brightweblabs/ui";
|
|
19
|
+
import { getStarterShellConfig } from "../../config/shell";
|
|
20
|
+
import "@brightweblabs/app-shell/dashboard.css";
|
|
21
|
+
|
|
22
|
+
export type ShellViewer = {
|
|
23
|
+
email: string | null;
|
|
24
|
+
firstName: string | null;
|
|
25
|
+
lastName: string | null;
|
|
26
|
+
isAdmin: boolean;
|
|
27
|
+
isStaff: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const authClient = createAuthUiClient();
|
|
31
|
+
|
|
32
|
+
export function ShellLayoutClient({
|
|
33
|
+
children,
|
|
34
|
+
viewer,
|
|
35
|
+
}: Readonly<{ children: ReactNode; viewer: ShellViewer }>) {
|
|
36
|
+
const pathname = usePathname() ?? "";
|
|
37
|
+
const router = useRouter();
|
|
38
|
+
const { shellPreview: config, toolbarRoutes, toolbarActions } = useMemo(
|
|
39
|
+
() => getStarterShellConfig(),
|
|
40
|
+
[],
|
|
41
|
+
);
|
|
42
|
+
const shellGroups = useMemo<ShellNavStateGroup[]>(
|
|
43
|
+
() => [
|
|
44
|
+
{ key: config.toolsSection.key, items: config.toolsSection.items },
|
|
45
|
+
...config.moduleGroups.map((group) => ({ key: group.key, items: group.children })),
|
|
46
|
+
],
|
|
47
|
+
[config],
|
|
48
|
+
);
|
|
49
|
+
const { isSidebarCollapsed, isGroupOpen, toggleGroup, toggleSidebar } =
|
|
50
|
+
useShellNavState({ pathname, groups: shellGroups });
|
|
51
|
+
const registeredCrmNavGroup = getShellNavGroup(config, "crm");
|
|
52
|
+
const crmNavGroup = registeredCrmNavGroup ?? ({
|
|
53
|
+
label: "CRM",
|
|
54
|
+
icon: Users,
|
|
55
|
+
children: [],
|
|
56
|
+
} satisfies NavGroupConfig);
|
|
57
|
+
const crmGroupKey = registeredCrmNavGroup?.key ?? "crm";
|
|
58
|
+
const isAdminSurface = pathname === "/admin" || pathname.startsWith("/admin/");
|
|
59
|
+
const isActive = (href: string) =>
|
|
60
|
+
pathname === href || (isAdminSurface && pathname.startsWith(`${href}/`));
|
|
61
|
+
const displayName =
|
|
62
|
+
[viewer.firstName, viewer.lastName].filter(Boolean).join(" ") ||
|
|
63
|
+
viewer.email ||
|
|
64
|
+
"Conta";
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<AppShellFrame
|
|
68
|
+
collapsed={isSidebarCollapsed}
|
|
69
|
+
sidebar={
|
|
70
|
+
<DesktopSidebar
|
|
71
|
+
brand={config.brand}
|
|
72
|
+
isSidebarCollapsed={isSidebarCollapsed}
|
|
73
|
+
isToolActive={config.toolsSection.items.some((item) => isActive(item.href))}
|
|
74
|
+
toolsExpanded={isGroupOpen(config.toolsSection.key)}
|
|
75
|
+
visiblePrimaryNav={config.primaryNav}
|
|
76
|
+
adminNavItem={viewer.isAdmin ? config.adminNavItem : null}
|
|
77
|
+
visibleToolNav={config.toolsSection.items}
|
|
78
|
+
crmNavGroup={crmNavGroup}
|
|
79
|
+
crmGroupExpanded={isGroupOpen(crmGroupKey)}
|
|
80
|
+
isCrmGroupActive={crmNavGroup.children.some(
|
|
81
|
+
(item) => pathname === item.href || pathname.startsWith(`${item.href}/`),
|
|
82
|
+
)}
|
|
83
|
+
isNavItemActive={isActive}
|
|
84
|
+
isToolLinkActive={isActive}
|
|
85
|
+
isCrmChildActive={isActive}
|
|
86
|
+
onToggleSidebar={toggleSidebar}
|
|
87
|
+
onToggleTools={() => toggleGroup(config.toolsSection.key)}
|
|
88
|
+
onToggleCrmGroup={() => toggleGroup(crmGroupKey)}
|
|
89
|
+
account={{
|
|
90
|
+
displayName,
|
|
91
|
+
isStaff: viewer.isStaff,
|
|
92
|
+
onSignOut: async () => {
|
|
93
|
+
await authClient.signOutLocal();
|
|
94
|
+
router.replace("/login");
|
|
95
|
+
router.refresh();
|
|
96
|
+
},
|
|
97
|
+
user: {
|
|
98
|
+
email: viewer.email,
|
|
99
|
+
user_metadata: {
|
|
100
|
+
first_name: viewer.firstName,
|
|
101
|
+
last_name: viewer.lastName,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
userInitials: computeInitials(
|
|
105
|
+
viewer.email,
|
|
106
|
+
viewer.firstName ?? undefined,
|
|
107
|
+
viewer.lastName ?? undefined,
|
|
108
|
+
),
|
|
109
|
+
}}
|
|
110
|
+
/>
|
|
111
|
+
}
|
|
112
|
+
header={
|
|
113
|
+
<AppHeader
|
|
114
|
+
pathname={pathname}
|
|
115
|
+
toolbarRoutes={toolbarRoutes}
|
|
116
|
+
toolbarActions={toolbarActions}
|
|
117
|
+
/>
|
|
118
|
+
}
|
|
119
|
+
mobileNav={
|
|
120
|
+
<MobileNav
|
|
121
|
+
toolsExpanded={isGroupOpen(config.toolsSection.key)}
|
|
122
|
+
visiblePrimaryNav={config.primaryNav}
|
|
123
|
+
visibleToolNav={config.toolsSection.items}
|
|
124
|
+
isNavItemActive={isActive}
|
|
125
|
+
isToolLinkActive={isActive}
|
|
126
|
+
onToggleTools={() => toggleGroup(config.toolsSection.key)}
|
|
127
|
+
/>
|
|
128
|
+
}
|
|
129
|
+
>
|
|
130
|
+
{children}
|
|
131
|
+
{isAdminSurface || pathname === "/account" ? <Toaster /> : null}
|
|
132
|
+
</AppShellFrame>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { handleAuthCallbackRequest as GET } from "@brightweblabs/core-auth/routes";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { handleAuthCleanupRequest as GET } from "@brightweblabs/core-auth/routes";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { starterBrandConfig } from "../config/brand";
|
|
3
|
+
import { ThemeProvider, ThemeScript } from "@brightweblabs/app-shell";
|
|
3
4
|
import { geistMono, geistSans } from "./fonts";
|
|
4
5
|
import "./globals.css";
|
|
5
6
|
|
|
@@ -10,9 +11,14 @@ export const metadata = {
|
|
|
10
11
|
|
|
11
12
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
12
13
|
return (
|
|
13
|
-
<html lang="en" className={`${geistSans.variable} ${geistMono.variable}`}>
|
|
14
|
+
<html lang="en" className={`${geistSans.variable} ${geistMono.variable}`} suppressHydrationWarning>
|
|
15
|
+
<head>
|
|
16
|
+
<ThemeScript defaultTheme="light" />
|
|
17
|
+
</head>
|
|
14
18
|
<body>
|
|
15
|
-
|
|
19
|
+
<ThemeProvider defaultTheme="light" disableTransitionOnChange>
|
|
20
|
+
{children}
|
|
21
|
+
</ThemeProvider>
|
|
16
22
|
</body>
|
|
17
23
|
</html>
|
|
18
24
|
);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export type StarterEnvScope = "public" | "server";
|
|
2
|
+
|
|
3
|
+
export type StarterEnvRequirement = {
|
|
4
|
+
key: string;
|
|
5
|
+
scope: StarterEnvScope;
|
|
6
|
+
description: string;
|
|
7
|
+
requiredFor: string[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type StarterEnvStatus = StarterEnvRequirement & {
|
|
11
|
+
present: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const starterEnvRequirements: StarterEnvRequirement[] = [
|
|
15
|
+
{
|
|
16
|
+
key: "NEXT_PUBLIC_APP_URL",
|
|
17
|
+
scope: "public",
|
|
18
|
+
description: "Canonical browser-visible URL used by auth callbacks and platform links.",
|
|
19
|
+
requiredFor: ["core-auth"],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: "PUBLIC_APP_URL",
|
|
23
|
+
scope: "server",
|
|
24
|
+
description: "Canonical server-side URL used to build Marketing unsubscribe and callback links.",
|
|
25
|
+
requiredFor: ["marketing"],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
key: "NEXT_PUBLIC_SUPABASE_URL",
|
|
29
|
+
scope: "public",
|
|
30
|
+
description: "Supabase project URL for client-side and server-side API access.",
|
|
31
|
+
requiredFor: ["crm", "marketing", "projects", "admin"],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY",
|
|
35
|
+
scope: "public",
|
|
36
|
+
description: "Supabase publishable key for browser auth and RPC calls.",
|
|
37
|
+
requiredFor: ["crm", "marketing", "projects", "admin"],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
key: "SUPABASE_SECRET_DEFAULT_KEY",
|
|
41
|
+
scope: "server",
|
|
42
|
+
description: "Supabase secret key for privileged Admin, CRM, Marketing, and Projects actions.",
|
|
43
|
+
requiredFor: ["crm", "marketing", "projects", "admin"],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: "RESEND_API_KEY",
|
|
47
|
+
scope: "server",
|
|
48
|
+
description: "Resend API key injected into Marketing email delivery.",
|
|
49
|
+
requiredFor: ["marketing"],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: "RESEND_WEBHOOK_SECRET",
|
|
53
|
+
scope: "server",
|
|
54
|
+
description: "Svix signing secret used to verify inbound Resend event payloads.",
|
|
55
|
+
requiredFor: ["marketing"],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: "MARKETING_WORKER_SECRET",
|
|
59
|
+
scope: "server",
|
|
60
|
+
description: "Bearer secret required to invoke the internal Marketing worker endpoint.",
|
|
61
|
+
requiredFor: ["marketing"],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: "MARKETING_FROM_EMAIL",
|
|
65
|
+
scope: "server",
|
|
66
|
+
description: "Verified sender address for Marketing campaigns; falls back to RESEND_FROM_EMAIL.",
|
|
67
|
+
requiredFor: ["marketing"],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: "MARKETING_FROM_NAME",
|
|
71
|
+
scope: "server",
|
|
72
|
+
description: "Display name for Marketing campaign email.",
|
|
73
|
+
requiredFor: ["marketing"],
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
export function getStarterEnvStatus() {
|
|
78
|
+
return starterEnvRequirements.map(
|
|
79
|
+
(requirement) =>
|
|
80
|
+
({
|
|
81
|
+
...requirement,
|
|
82
|
+
present:
|
|
83
|
+
typeof process.env[requirement.key] === "string"
|
|
84
|
+
&& process.env[requirement.key]!.trim().length > 0,
|
|
85
|
+
}) satisfies StarterEnvStatus,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function isStarterEnvReady(requiredModules: string[]) {
|
|
90
|
+
const relevant = getStarterEnvStatus().filter((item) =>
|
|
91
|
+
item.requiredFor.some((moduleKey) => requiredModules.includes(moduleKey)),
|
|
92
|
+
);
|
|
93
|
+
return {
|
|
94
|
+
allReady: relevant.every((item) => item.present),
|
|
95
|
+
items: relevant,
|
|
96
|
+
missing: relevant.filter((item) => !item.present),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MarketingPage as default } from "@brightweblabs/module-marketing";
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMarketingCampaignHandlers,
|
|
3
|
+
createNoopEmailSender,
|
|
4
|
+
createResendEmailSender,
|
|
5
|
+
handleMarketingUnsubscribeGetRequest,
|
|
6
|
+
handleMarketingUnsubscribePostRequest,
|
|
7
|
+
} from "@brightweblabs/module-marketing";
|
|
8
|
+
|
|
9
|
+
const apiKey = process.env.RESEND_API_KEY ?? "";
|
|
10
|
+
const defaultFromEmail =
|
|
11
|
+
process.env.MARKETING_FROM_EMAIL ?? process.env.RESEND_FROM_EMAIL ?? "";
|
|
12
|
+
const defaultFromName =
|
|
13
|
+
process.env.MARKETING_FROM_NAME ?? process.env.RESEND_FROM_NAME ?? "BrightWeb";
|
|
14
|
+
|
|
15
|
+
const sender = apiKey && defaultFromEmail
|
|
16
|
+
? createResendEmailSender({
|
|
17
|
+
apiKey,
|
|
18
|
+
defaultFromEmail,
|
|
19
|
+
defaultFromName,
|
|
20
|
+
})
|
|
21
|
+
: createNoopEmailSender();
|
|
22
|
+
|
|
23
|
+
const handlers = createMarketingCampaignHandlers({
|
|
24
|
+
sender,
|
|
25
|
+
workerSecret: process.env.MARKETING_WORKER_SECRET ?? "",
|
|
26
|
+
webhookSecret: process.env.RESEND_WEBHOOK_SECRET ?? "",
|
|
27
|
+
publicAppUrl:
|
|
28
|
+
process.env.PUBLIC_APP_URL
|
|
29
|
+
?? process.env.NEXT_PUBLIC_APP_URL
|
|
30
|
+
?? "http://localhost:3000",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
type IdRouteContext = { params: Promise<{ id: string }> };
|
|
34
|
+
type NodeRouteContext = { params: Promise<{ id: string; nodeId: string }> };
|
|
35
|
+
type TokenRouteContext = { params: Promise<{ token: string }> };
|
|
36
|
+
|
|
37
|
+
export function marketingUnsubscribeGet(request: Request, context: TokenRouteContext) {
|
|
38
|
+
return handleMarketingUnsubscribeGetRequest(request, context);
|
|
39
|
+
}
|
|
40
|
+
export function marketingUnsubscribePost(request: Request, context: TokenRouteContext) {
|
|
41
|
+
return handleMarketingUnsubscribePostRequest(request, context);
|
|
42
|
+
}
|
|
43
|
+
export function marketingAnalyticsOverviewGet(request: Request) {
|
|
44
|
+
return handlers.analyticsOverviewGet(request);
|
|
45
|
+
}
|
|
46
|
+
export function marketingCampaignsGet(request: Request) {
|
|
47
|
+
return handlers.campaignsGet(request);
|
|
48
|
+
}
|
|
49
|
+
export function marketingCampaignsPost(request: Request) {
|
|
50
|
+
return handlers.campaignsPost(request);
|
|
51
|
+
}
|
|
52
|
+
export function marketingSegmentsGet(request: Request) {
|
|
53
|
+
return handlers.segmentsGet(request);
|
|
54
|
+
}
|
|
55
|
+
export function marketingSegmentsPost(request: Request) {
|
|
56
|
+
return handlers.segmentsPost(request);
|
|
57
|
+
}
|
|
58
|
+
export function marketingTopicsGet(request: Request) {
|
|
59
|
+
return handlers.topicsGet(request);
|
|
60
|
+
}
|
|
61
|
+
export function marketingWebhookPost(request: Request) {
|
|
62
|
+
return handlers.webhookPost(request);
|
|
63
|
+
}
|
|
64
|
+
export function marketingWorkerPost(request: Request) {
|
|
65
|
+
return handlers.workerPost(request);
|
|
66
|
+
}
|
|
67
|
+
export function marketingWorkflowsGet(request: Request) {
|
|
68
|
+
return handlers.workflowsGet(request);
|
|
69
|
+
}
|
|
70
|
+
export function marketingWorkflowsPost(request: Request) {
|
|
71
|
+
return handlers.workflowsPost(request);
|
|
72
|
+
}
|
|
73
|
+
export function marketingAnalyticsCampaignGet(request: Request, context: IdRouteContext) {
|
|
74
|
+
return handlers.analyticsCampaignGet(request, context);
|
|
75
|
+
}
|
|
76
|
+
export function marketingAnalyticsSegmentGet(request: Request, context: IdRouteContext) {
|
|
77
|
+
return handlers.analyticsSegmentGet(request, context);
|
|
78
|
+
}
|
|
79
|
+
export function marketingCampaignDelete(request: Request, context: IdRouteContext) {
|
|
80
|
+
return handlers.campaignDelete(request, context);
|
|
81
|
+
}
|
|
82
|
+
export function marketingCampaignGet(request: Request, context: IdRouteContext) {
|
|
83
|
+
return handlers.campaignGet(request, context);
|
|
84
|
+
}
|
|
85
|
+
export function marketingCampaignPatch(request: Request, context: IdRouteContext) {
|
|
86
|
+
return handlers.campaignPatch(request, context);
|
|
87
|
+
}
|
|
88
|
+
export function marketingCampaignCancelPost(request: Request, context: IdRouteContext) {
|
|
89
|
+
return handlers.cancelPost(request, context);
|
|
90
|
+
}
|
|
91
|
+
export function marketingCampaignRecipientsGet(request: Request, context: IdRouteContext) {
|
|
92
|
+
return handlers.recipientsGet(request, context);
|
|
93
|
+
}
|
|
94
|
+
export function marketingCampaignRetryPost(request: Request, context: IdRouteContext) {
|
|
95
|
+
return handlers.retryPost(request, context);
|
|
96
|
+
}
|
|
97
|
+
export function marketingCampaignSchedulePost(request: Request, context: IdRouteContext) {
|
|
98
|
+
return handlers.schedulePost(request, context);
|
|
99
|
+
}
|
|
100
|
+
export function marketingCampaignSendPost(request: Request, context: IdRouteContext) {
|
|
101
|
+
return handlers.sendPost(request, context);
|
|
102
|
+
}
|
|
103
|
+
export function marketingCampaignTestPost(request: Request, context: IdRouteContext) {
|
|
104
|
+
return handlers.testPost(request, context);
|
|
105
|
+
}
|
|
106
|
+
export function marketingSegmentDelete(request: Request, context: IdRouteContext) {
|
|
107
|
+
return handlers.segmentDelete(request, context);
|
|
108
|
+
}
|
|
109
|
+
export function marketingSegmentGet(request: Request, context: IdRouteContext) {
|
|
110
|
+
return handlers.segmentGet(request, context);
|
|
111
|
+
}
|
|
112
|
+
export function marketingSegmentPatch(request: Request, context: IdRouteContext) {
|
|
113
|
+
return handlers.segmentPatch(request, context);
|
|
114
|
+
}
|
|
115
|
+
export function marketingSegmentPreviewByIdPost(request: Request, context: IdRouteContext) {
|
|
116
|
+
return handlers.segmentPreviewPost(request, context);
|
|
117
|
+
}
|
|
118
|
+
export function marketingSegmentPreviewPost(request: Request) {
|
|
119
|
+
return handlers.segmentPreviewPost(request);
|
|
120
|
+
}
|
|
121
|
+
export function marketingWorkflowActivatePost(request: Request, context: IdRouteContext) {
|
|
122
|
+
return handlers.workflowActivatePost(request, context);
|
|
123
|
+
}
|
|
124
|
+
export function marketingWorkflowDelete(request: Request, context: IdRouteContext) {
|
|
125
|
+
return handlers.workflowDelete(request, context);
|
|
126
|
+
}
|
|
127
|
+
export function marketingWorkflowGet(request: Request, context: IdRouteContext) {
|
|
128
|
+
return handlers.workflowGet(request, context);
|
|
129
|
+
}
|
|
130
|
+
export function marketingWorkflowNodeDelete(request: Request, context: NodeRouteContext) {
|
|
131
|
+
return handlers.workflowNodeDelete(request, context);
|
|
132
|
+
}
|
|
133
|
+
export function marketingWorkflowNodesPut(request: Request, context: IdRouteContext) {
|
|
134
|
+
return handlers.workflowNodesPut(request, context);
|
|
135
|
+
}
|
|
136
|
+
export function marketingWorkflowPatch(request: Request, context: IdRouteContext) {
|
|
137
|
+
return handlers.workflowPatch(request, context);
|
|
138
|
+
}
|
|
139
|
+
export function marketingWorkflowPausePost(request: Request, context: IdRouteContext) {
|
|
140
|
+
return handlers.workflowPausePost(request, context);
|
|
141
|
+
}
|
|
142
|
+
export function marketingWorkflowRunsGet(request: Request, context: IdRouteContext) {
|
|
143
|
+
return handlers.workflowRunsGet(request, context);
|
|
144
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ProjectBoardServerMount as default } from "../../projects-server-mounts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ProjectDetailServerMount as default } from "../projects-server-mounts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ProjectTasksServerMount as default } from "../../projects-server-mounts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ProjectsServerMount as default } from "./projects-server-mounts";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ProjectBoardPage,
|
|
5
|
+
ProjectDetailPage,
|
|
6
|
+
ProjectsPage,
|
|
7
|
+
ProjectTasksPage,
|
|
8
|
+
type ProjectDetailPageProps,
|
|
9
|
+
type ProjectsPageProps,
|
|
10
|
+
type ProjectTasksPageProps,
|
|
11
|
+
} from "@brightweblabs/module-projects/ui";
|
|
12
|
+
|
|
13
|
+
export function ProjectsPageLiveMount(props: ProjectsPageProps) {
|
|
14
|
+
return <ProjectsPage {...props} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ProjectDetailPageLiveMount(props: ProjectDetailPageProps) {
|
|
18
|
+
return <ProjectDetailPage {...props} />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function ProjectBoardPageLiveMount(props: ProjectTasksPageProps) {
|
|
22
|
+
return <ProjectBoardPage {...props} />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ProjectTasksPageLiveMount(props: ProjectTasksPageProps) {
|
|
26
|
+
return <ProjectTasksPage {...props} />;
|
|
27
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { requireServerPageAccess } from "@brightweblabs/core-auth/server";
|
|
2
|
+
import {
|
|
3
|
+
getProjectDashboard,
|
|
4
|
+
getProjectsPortfolioPageData,
|
|
5
|
+
} from "@brightweblabs/module-projects";
|
|
6
|
+
import {
|
|
7
|
+
ProjectBoardPageLiveMount,
|
|
8
|
+
ProjectDetailPageLiveMount,
|
|
9
|
+
ProjectsPageLiveMount,
|
|
10
|
+
ProjectTasksPageLiveMount,
|
|
11
|
+
} from "./projects-live-mounts";
|
|
12
|
+
|
|
13
|
+
const navigation = {
|
|
14
|
+
listHref: "/projects",
|
|
15
|
+
detailHref: (id: string) => `/projects/${id}`,
|
|
16
|
+
boardHref: (id: string) => `/projects/${id}/tasks`,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export async function ProjectsServerMount() {
|
|
20
|
+
const { organizationOptions, portfolioStats, result } = await getProjectsPortfolioPageData();
|
|
21
|
+
const attentionSummary = {
|
|
22
|
+
total: result.total,
|
|
23
|
+
overdue: result.items.filter((project) => project.taskStats.overdue > 0).length,
|
|
24
|
+
atRisk: result.items.filter((project) => project.health === "at_risk").length,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<ProjectsPageLiveMount
|
|
29
|
+
initialData={{ ...result, attentionSummary }}
|
|
30
|
+
initialUpdatedAt={new Date().toISOString()}
|
|
31
|
+
portfolioStats={portfolioStats}
|
|
32
|
+
organizations={organizationOptions}
|
|
33
|
+
navigation={navigation}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function getInitialProject(params: Promise<{ id: string }>) {
|
|
39
|
+
const { id } = await params;
|
|
40
|
+
const { supabase } = await requireServerPageAccess();
|
|
41
|
+
return getProjectDashboard(supabase, id);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function ProjectDetailServerMount({ params }: { params: Promise<{ id: string }> }) {
|
|
45
|
+
return <ProjectDetailPageLiveMount initialData={await getInitialProject(params)} navigation={navigation} />;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function ProjectBoardServerMount({ params }: { params: Promise<{ id: string }> }) {
|
|
49
|
+
return <ProjectBoardPageLiveMount initialData={await getInitialProject(params)} navigation={navigation} />;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function ProjectTasksServerMount({ params }: { params: Promise<{ id: string }> }) {
|
|
53
|
+
return <ProjectTasksPageLiveMount initialData={await getInitialProject(params)} navigation={navigation} />;
|
|
54
|
+
}
|