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.
Files changed (88) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/src/app-manifest.mjs +4 -2
  4. package/src/constants.mjs +95 -18
  5. package/src/doctor.mjs +38 -0
  6. package/src/generator.mjs +75 -19
  7. package/template/base/app/(auth)/auth/confirmed/page.tsx +1 -0
  8. package/template/base/app/(auth)/auth/post-login/page.tsx +1 -0
  9. package/template/base/app/(auth)/auth-provider.tsx +25 -0
  10. package/template/base/app/(auth)/forgot-password/page.tsx +1 -0
  11. package/template/base/app/(auth)/invite/[invitationId]/invite-route-mount.tsx +10 -0
  12. package/template/base/app/(auth)/invite/[invitationId]/page.tsx +1 -0
  13. package/template/base/app/(auth)/layout.tsx +5 -0
  14. package/template/base/app/(auth)/login/page.tsx +1 -0
  15. package/template/base/app/(auth)/reset-password/page.tsx +1 -0
  16. package/template/base/app/(auth)/signup/page.tsx +1 -0
  17. package/template/base/app/(shell)/account/page.tsx +5 -0
  18. package/template/base/app/(shell)/dashboard/dashboard-live-mount.tsx +21 -0
  19. package/template/base/app/(shell)/dashboard/page.tsx +1 -0
  20. package/template/base/app/(shell)/layout.tsx +24 -0
  21. package/template/base/app/(shell)/shell-layout-client.tsx +134 -0
  22. package/template/base/app/api/account/route.ts +2 -0
  23. package/template/base/app/auth/callback/route.ts +1 -0
  24. package/template/base/app/auth/cleanup/route.ts +1 -0
  25. package/template/base/app/layout.tsx +8 -2
  26. package/template/base/config/env.ts +98 -0
  27. package/template/module-manifests/marketing/brightweb.module.json +12 -0
  28. package/template/modules/marketing/app/(shell)/marketing/page.tsx +1 -0
  29. package/template/modules/marketing/app/api/marketing/_handlers.ts +144 -0
  30. package/template/modules/marketing/app/api/marketing/analytics/campaigns/[id]/route.ts +2 -0
  31. package/template/modules/marketing/app/api/marketing/analytics/overview/route.ts +2 -0
  32. package/template/modules/marketing/app/api/marketing/analytics/segments/[id]/route.ts +2 -0
  33. package/template/modules/marketing/app/api/marketing/campaigns/[id]/cancel/route.ts +2 -0
  34. package/template/modules/marketing/app/api/marketing/campaigns/[id]/recipients/route.ts +2 -0
  35. package/template/modules/marketing/app/api/marketing/campaigns/[id]/retry/route.ts +2 -0
  36. package/template/modules/marketing/app/api/marketing/campaigns/[id]/route.ts +2 -0
  37. package/template/modules/marketing/app/api/marketing/campaigns/[id]/schedule/route.ts +2 -0
  38. package/template/modules/marketing/app/api/marketing/campaigns/[id]/send/route.ts +2 -0
  39. package/template/modules/marketing/app/api/marketing/campaigns/[id]/test/route.ts +2 -0
  40. package/template/modules/marketing/app/api/marketing/campaigns/route.ts +2 -0
  41. package/template/modules/marketing/app/api/marketing/segments/[id]/preview/route.ts +2 -0
  42. package/template/modules/marketing/app/api/marketing/segments/[id]/route.ts +2 -0
  43. package/template/modules/marketing/app/api/marketing/segments/preview/route.ts +2 -0
  44. package/template/modules/marketing/app/api/marketing/segments/route.ts +2 -0
  45. package/template/modules/marketing/app/api/marketing/topics/route.ts +2 -0
  46. package/template/modules/marketing/app/api/marketing/unsubscribe/[token]/route.ts +2 -0
  47. package/template/modules/marketing/app/api/marketing/webhooks/resend/route.ts +2 -0
  48. package/template/modules/marketing/app/api/marketing/worker/route.ts +2 -0
  49. package/template/modules/marketing/app/api/marketing/workflows/[id]/activate/route.ts +2 -0
  50. package/template/modules/marketing/app/api/marketing/workflows/[id]/nodes/[nodeId]/route.ts +2 -0
  51. package/template/modules/marketing/app/api/marketing/workflows/[id]/nodes/route.ts +2 -0
  52. package/template/modules/marketing/app/api/marketing/workflows/[id]/pause/route.ts +2 -0
  53. package/template/modules/marketing/app/api/marketing/workflows/[id]/route.ts +2 -0
  54. package/template/modules/marketing/app/api/marketing/workflows/[id]/runs/route.ts +2 -0
  55. package/template/modules/marketing/app/api/marketing/workflows/route.ts +2 -0
  56. package/template/modules/projects/app/(shell)/projects/[id]/board/page.tsx +1 -0
  57. package/template/modules/projects/app/(shell)/projects/[id]/page.tsx +1 -0
  58. package/template/modules/projects/app/(shell)/projects/[id]/tasks/page.tsx +1 -0
  59. package/template/modules/projects/app/(shell)/projects/layout.tsx +5 -0
  60. package/template/modules/projects/app/(shell)/projects/page.tsx +1 -0
  61. package/template/modules/projects/app/(shell)/projects/projects-live-mounts.tsx +27 -0
  62. package/template/modules/projects/app/(shell)/projects/projects-server-mounts.tsx +54 -0
  63. package/template/modules/projects/app/api/projects/[id]/activity/route.ts +2 -0
  64. package/template/modules/projects/app/api/projects/[id]/links/[itemId]/route.ts +2 -0
  65. package/template/modules/projects/app/api/projects/[id]/links/route.ts +2 -0
  66. package/template/modules/projects/app/api/projects/[id]/members/route.ts +2 -0
  67. package/template/modules/projects/app/api/projects/[id]/milestones/[itemId]/route.ts +2 -0
  68. package/template/modules/projects/app/api/projects/[id]/milestones/route.ts +2 -0
  69. package/template/modules/projects/app/api/projects/[id]/route.ts +2 -0
  70. package/template/modules/projects/app/api/projects/[id]/tasks/[itemId]/route.ts +2 -0
  71. package/template/modules/projects/app/api/projects/[id]/tasks/route.ts +2 -0
  72. package/template/modules/projects/app/api/projects/_handlers.ts +86 -0
  73. package/template/modules/projects/app/api/projects/organizations/route.ts +2 -0
  74. package/template/modules/projects/app/api/projects/route.ts +2 -0
  75. package/template/modules/projects/app/api/projects/stats/route.ts +2 -0
  76. package/template/supabase/README.md +3 -0
  77. package/template/supabase/module-registry.json +6 -0
  78. package/template/supabase/modules/core/migrations/20260726180000_enable_rate_limit_counters_rls.sql +2 -0
  79. package/template/supabase/modules/marketing/README.md +12 -0
  80. package/template/supabase/modules/marketing/migrations/20260725120000_marketing_v1.sql +118 -0
  81. package/template/supabase/modules/marketing/migrations/20260725130000_marketing_campaigns.sql +133 -0
  82. package/template/supabase/modules/marketing/migrations/20260726120000_marketing_segments.sql +41 -0
  83. package/template/supabase/modules/marketing/migrations/20260726150000_marketing_workflows.sql +174 -0
  84. package/template/supabase/modules/marketing/migrations/20260726170000_marketing_engine_safety.sql +68 -0
  85. /package/template/modules/admin/app/{admin → (shell)/admin}/users/page.tsx +0 -0
  86. /package/template/modules/crm/app/{crm → (shell)/crm}/layout.tsx +0 -0
  87. /package/template/modules/crm/app/{crm → (shell)/crm}/page.tsx +0 -0
  88. /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,2 @@
1
+ export const dynamic = "force-dynamic";
2
+ export { handleAccountGetRequest as GET, handleAccountUpdateRequest as PATCH } from "@brightweblabs/core-auth";
@@ -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
- {children}
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,12 @@
1
+ {
2
+ "contractVersion": 1,
3
+ "key": "marketing",
4
+ "requires": {
5
+ "core": ">=0.3",
6
+ "admin": ">=0.3",
7
+ "orgs": ">=0.1",
8
+ "crm": ">=0.6"
9
+ },
10
+ "env": [],
11
+ "tokens": "./tokens.css"
12
+ }
@@ -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,2 @@
1
+ export { marketingAnalyticsCampaignGet as GET } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingAnalyticsOverviewGet as GET } from "../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingAnalyticsSegmentGet as GET } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingCampaignCancelPost as POST } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingCampaignRecipientsGet as GET } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingCampaignRetryPost as POST } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingCampaignDelete as DELETE, marketingCampaignGet as GET, marketingCampaignPatch as PATCH } from "../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingCampaignSchedulePost as POST } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingCampaignSendPost as POST } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingCampaignTestPost as POST } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingCampaignsGet as GET, marketingCampaignsPost as POST } from "../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingSegmentPreviewByIdPost as POST } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingSegmentDelete as DELETE, marketingSegmentGet as GET, marketingSegmentPatch as PATCH } from "../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingSegmentPreviewPost as POST } from "../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingSegmentsGet as GET, marketingSegmentsPost as POST } from "../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingTopicsGet as GET } from "../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingUnsubscribeGet as GET, marketingUnsubscribePost as POST } from "../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingWebhookPost as POST } from "../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingWorkerPost as POST } from "../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingWorkflowActivatePost as POST } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingWorkflowNodeDelete as DELETE } from "../../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingWorkflowNodesPut as PUT } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingWorkflowPausePost as POST } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingWorkflowDelete as DELETE, marketingWorkflowGet as GET, marketingWorkflowPatch as PATCH } from "../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingWorkflowRunsGet as GET } from "../../../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,2 @@
1
+ export { marketingWorkflowsGet as GET, marketingWorkflowsPost as POST } from "../_handlers";
2
+ export const dynamic = "force-dynamic";
@@ -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,5 @@
1
+ import "@brightweblabs/module-projects/tokens.css";
2
+
3
+ export default function ProjectsLayout({ children }: Readonly<{ children: React.ReactNode }>) {
4
+ return children;
5
+ }
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ export const dynamic = "force-dynamic";
2
+ export { projectActivityGet as GET } from "../../_handlers";
@@ -0,0 +1,2 @@
1
+ export const dynamic = "force-dynamic";
2
+ export { projectLinkPatch as PATCH, projectLinkDelete as DELETE } from "../../../_handlers";
@@ -0,0 +1,2 @@
1
+ export const dynamic = "force-dynamic";
2
+ export { projectLinksPost as POST } from "../../_handlers";