lytx 0.3.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/.env.example +37 -0
- package/README.md +486 -0
- package/alchemy.run.ts +155 -0
- package/cli/bootstrap-admin.ts +284 -0
- package/cli/deploy-staging.ts +692 -0
- package/cli/import-events.ts +628 -0
- package/cli/import-sites.ts +518 -0
- package/cli/index.ts +609 -0
- package/cli/init-db.ts +269 -0
- package/cli/migrate-to-durable-objects.ts +564 -0
- package/cli/migration-worker.ts +300 -0
- package/cli/performance-test.ts +588 -0
- package/cli/pg/client.ts +4 -0
- package/cli/pg/new-site.ts +153 -0
- package/cli/rollback-durable-objects.ts +622 -0
- package/cli/seed-data.ts +459 -0
- package/cli/setup.js +18 -0
- package/cli/setup.ts +463 -0
- package/cli/validate-migration.ts +200 -0
- package/cli/wrangler-migration.jsonc +28 -0
- package/db/adapter.ts +166 -0
- package/db/analytics_engine/client.ts +0 -0
- package/db/analytics_engine/sites.ts +0 -0
- package/db/client.ts +16 -0
- package/db/d1/client.ts +8 -0
- package/db/d1/drizzle.config.ts +35 -0
- package/db/d1/migrations/0000_true_maelstrom.sql +165 -0
- package/db/d1/migrations/0001_wonderful_bloodaxe.sql +12 -0
- package/db/d1/migrations/0002_late_frightful_four.sql +1 -0
- package/db/d1/migrations/0003_cuddly_obadiah_stane.sql +16 -0
- package/db/d1/migrations/0004_mute_stardust.sql +1 -0
- package/db/d1/migrations/0005_awesome_silvermane.sql +3 -0
- package/db/d1/migrations/0006_volatile_shriek.sql +2 -0
- package/db/d1/migrations/0007_superb_lila_cheney.sql +1 -0
- package/db/d1/migrations/0008_bitter_longshot.sql +17 -0
- package/db/d1/migrations/0009_wonderful_madame_masque.sql +28 -0
- package/db/d1/migrations/meta/0000_snapshot.json +1112 -0
- package/db/d1/migrations/meta/0001_snapshot.json +1187 -0
- package/db/d1/migrations/meta/0002_snapshot.json +1194 -0
- package/db/d1/migrations/meta/0003_snapshot.json +1296 -0
- package/db/d1/migrations/meta/0004_snapshot.json +1303 -0
- package/db/d1/migrations/meta/0005_snapshot.json +1325 -0
- package/db/d1/migrations/meta/0006_snapshot.json +1339 -0
- package/db/d1/migrations/meta/0007_snapshot.json +1347 -0
- package/db/d1/migrations/meta/0008_snapshot.json +1464 -0
- package/db/d1/migrations/meta/0009_snapshot.json +1648 -0
- package/db/d1/migrations/meta/_journal.json +76 -0
- package/db/d1/schema.ts +407 -0
- package/db/d1/sites.ts +374 -0
- package/db/d1/teamAiUsage.ts +101 -0
- package/db/d1/teams.ts +127 -0
- package/db/durable/drizzle.config.ts +8 -0
- package/db/durable/durableObjectClient.ts +480 -0
- package/db/durable/events.ts +100 -0
- package/db/durable/migrations/0000_fair_bucky.sql +38 -0
- package/db/durable/migrations/meta/0000_snapshot.json +278 -0
- package/db/durable/migrations/meta/_journal.json +13 -0
- package/db/durable/migrations/migrations.js +10 -0
- package/db/durable/schema.ts +5 -0
- package/db/durable/siteDurableObject.ts +1352 -0
- package/db/durable/types.ts +53 -0
- package/db/postgres/client.ts +13 -0
- package/db/postgres/drizzle.config.ts +12 -0
- package/db/postgres/migrations/0000_brainy_sprite.sql +116 -0
- package/db/postgres/migrations/meta/0000_snapshot.json +681 -0
- package/db/postgres/migrations/meta/_journal.json +13 -0
- package/db/postgres/schema.ts +145 -0
- package/db/postgres/sites.ts +118 -0
- package/db/tranformReports.ts +595 -0
- package/db/types.ts +55 -0
- package/endpoints/api_worker.tsx +1854 -0
- package/endpoints/site_do_worker.ts +11 -0
- package/index.d.ts +63 -0
- package/index.ts +83 -0
- package/lib/auth.ts +279 -0
- package/lib/geojson/world_countries.json +45307 -0
- package/lib/random_name.ts +41 -0
- package/lib/sendMail.ts +252 -0
- package/package.json +142 -0
- package/public/favicon.ico +0 -0
- package/public/images/android-chrome-192x192.png +0 -0
- package/public/images/android-chrome-512x512.png +0 -0
- package/public/images/apple-touch-icon.png +0 -0
- package/public/images/favicon-16x16.png +0 -0
- package/public/images/favicon-32x32.png +0 -0
- package/public/images/lytx_dark_dashboard.png +0 -0
- package/public/images/lytx_light_dashboard.png +0 -0
- package/public/images/safari-pinned-tab.svg +4 -0
- package/public/logo.png +0 -0
- package/public/site.webmanifest +26 -0
- package/public/sw.js +107 -0
- package/src/Document.tsx +86 -0
- package/src/api/ai_api.ts +1156 -0
- package/src/api/authMiddleware.ts +45 -0
- package/src/api/auth_api.ts +465 -0
- package/src/api/event_labels_api.ts +193 -0
- package/src/api/events_api.ts +210 -0
- package/src/api/queueWorker.ts +303 -0
- package/src/api/reports_api.ts +278 -0
- package/src/api/seed_api.ts +288 -0
- package/src/api/sites_api.ts +904 -0
- package/src/api/tag_api.ts +458 -0
- package/src/api/tag_api_v2.ts +289 -0
- package/src/api/team_api.ts +456 -0
- package/src/app/Dashboard.tsx +1339 -0
- package/src/app/Events.tsx +974 -0
- package/src/app/Explore.tsx +312 -0
- package/src/app/Layout.tsx +58 -0
- package/src/app/Settings.tsx +1302 -0
- package/src/app/components/DashboardCard.tsx +118 -0
- package/src/app/components/EditableCell.tsx +123 -0
- package/src/app/components/EventForm.tsx +93 -0
- package/src/app/components/MarketingFooter.tsx +49 -0
- package/src/app/components/MarketingNav.tsx +150 -0
- package/src/app/components/Nav.tsx +755 -0
- package/src/app/components/NewSiteSetup.tsx +298 -0
- package/src/app/components/SQLEditor.tsx +740 -0
- package/src/app/components/SiteSelector.tsx +126 -0
- package/src/app/components/SiteTag.tsx +42 -0
- package/src/app/components/SiteTagInstallCard.tsx +241 -0
- package/src/app/components/WorldMapCard.tsx +337 -0
- package/src/app/components/charts/ChartComponents.tsx +1481 -0
- package/src/app/components/charts/EventFunnel.tsx +45 -0
- package/src/app/components/charts/EventSummary.tsx +194 -0
- package/src/app/components/charts/SankeyFlows.tsx +72 -0
- package/src/app/components/marketing/CheckIcon.tsx +16 -0
- package/src/app/components/marketing/MarketingLayout.tsx +23 -0
- package/src/app/components/marketing/SectionHeading.tsx +35 -0
- package/src/app/components/reports/AskAiWorkspace.tsx +371 -0
- package/src/app/components/reports/CreateReportStarter.tsx +74 -0
- package/src/app/components/reports/DashboardRouteFiltersContext.tsx +14 -0
- package/src/app/components/reports/DashboardToolbar.tsx +154 -0
- package/src/app/components/reports/DashboardWorkspaceLayout.tsx +63 -0
- package/src/app/components/reports/DashboardWorkspaceShell.tsx +118 -0
- package/src/app/components/reports/ReportBuilderWorkspace.tsx +76 -0
- package/src/app/components/reports/custom/CustomReportBuilderPage.tsx +1667 -0
- package/src/app/components/reports/custom/ReportWidgetChart.tsx +297 -0
- package/src/app/components/reports/custom/buildWidgetSql.ts +151 -0
- package/src/app/components/reports/custom/chartPalettes.ts +18 -0
- package/src/app/components/reports/custom/types.ts +50 -0
- package/src/app/components/reports/reportBuilderMenuItems.ts +17 -0
- package/src/app/components/reports/useDashboardToolbarControls.tsx +235 -0
- package/src/app/components/ui/AlertBanner.tsx +101 -0
- package/src/app/components/ui/Button.tsx +55 -0
- package/src/app/components/ui/Card.tsx +80 -0
- package/src/app/components/ui/Input.tsx +72 -0
- package/src/app/components/ui/Link.tsx +23 -0
- package/src/app/components/ui/ReportBuilderMenu.tsx +246 -0
- package/src/app/components/ui/ThemeToggle.tsx +54 -0
- package/src/app/constants.ts +6 -0
- package/src/app/headers.ts +33 -0
- package/src/app/providers/AuthProvider.tsx +189 -0
- package/src/app/providers/ClientProviders.tsx +18 -0
- package/src/app/providers/QueryProvider.tsx +23 -0
- package/src/app/providers/ThemeProvider.tsx +88 -0
- package/src/app/utils/chartThemes.ts +146 -0
- package/src/app/utils/keybinds.ts +96 -0
- package/src/app/utils/media.tsx +24 -0
- package/src/client.tsx +114 -0
- package/src/config/createLytxAppConfig.ts +252 -0
- package/src/config/resourceNames.ts +88 -0
- package/src/db/index.ts +67 -0
- package/src/index.css +285 -0
- package/src/lib/featureFlags.ts +69 -0
- package/src/pages/GetStarted.tsx +290 -0
- package/src/pages/Home.tsx +268 -0
- package/src/pages/Login.tsx +283 -0
- package/src/pages/PrivacyPolicy.tsx +120 -0
- package/src/pages/Signup.tsx +267 -0
- package/src/pages/TermsOfService.tsx +126 -0
- package/src/pages/VerifyEmail.tsx +56 -0
- package/src/session/durableObject.ts +7 -0
- package/src/session/siteSchema.ts +86 -0
- package/src/session/types.ts +36 -0
- package/src/templates/README.md +80 -0
- package/src/templates/cleanFunctions.js +44 -0
- package/src/templates/embedFunctions.js +52 -0
- package/src/templates/lytx-shared.ts +662 -0
- package/src/templates/lytxpixel-core.ts +144 -0
- package/src/templates/lytxpixel.ts +267 -0
- package/src/templates/lytxpixelBrowser.js +634 -0
- package/src/templates/lytxpixelBrowser.mjs +634 -0
- package/src/templates/parseData.js +12 -0
- package/src/templates/script.ts +31 -0
- package/src/templates/template.tsx +50 -0
- package/src/templates/test.js +3 -0
- package/src/templates/trackWebEvents.ts +177 -0
- package/src/templates/vendors/clickcease.ts +8 -0
- package/src/templates/vendors/google.ts +174 -0
- package/src/templates/vendors/linkedin.ts +23 -0
- package/src/templates/vendors/meta.ts +56 -0
- package/src/templates/vendors/quantcast.ts +22 -0
- package/src/templates/vendors/simplfi.ts +7 -0
- package/src/types/app-context.ts +16 -0
- package/src/utilities/dashboardParams.ts +188 -0
- package/src/utilities/dashboardQueries.ts +537 -0
- package/src/utilities/dashboardTransforms.ts +167 -0
- package/src/utilities/dataValidation.ts +414 -0
- package/src/utilities/detector.ts +73 -0
- package/src/utilities/encrypt.ts +103 -0
- package/src/utilities/index.ts +13 -0
- package/src/utilities/parser.ts +117 -0
- package/src/utilities/performanceMonitoring.ts +570 -0
- package/src/utilities/route_interuptors.ts +24 -0
- package/src/worker.tsx +675 -0
- package/tsconfig.json +78 -0
- package/types/env.d.ts +16 -0
- package/types/rw.d.ts +7 -0
- package/types/shims.d.ts +53 -0
- package/types/vite.d.ts +19 -0
- package/vite/vite-plugin-pixel-bundle.ts +126 -0
- package/vite.config.ts +53 -0
- package/worker-configuration.d.ts +8401 -0
package/src/worker.tsx
ADDED
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
import { defineApp } from "rwsdk/worker";
|
|
2
|
+
import { route, render, prefix, layout } from "rwsdk/router";
|
|
3
|
+
import { Document } from "@/Document";
|
|
4
|
+
import { DashboardPage } from "@/app/Dashboard";
|
|
5
|
+
import { EventsPage } from "@/app/Events";
|
|
6
|
+
import { ExplorePage } from "@/app/Explore";
|
|
7
|
+
import { AppLayout } from "@/app/Layout";
|
|
8
|
+
import { eventsApi } from "@api/events_api";
|
|
9
|
+
import { seedApi } from "@api/seed_api";
|
|
10
|
+
import { team_dashboard_endpoints } from "@api/team_api";
|
|
11
|
+
import { world_countries, getCurrentVisitorsRoute, getDashboardDataCore, getDashboardDataRoute, siteEventsSqlRoute, siteEventsSchemaRoute } from "@api/sites_api";
|
|
12
|
+
import { aiChatRoute, aiConfigRoute, aiTagSuggestRoute, getAiConfig } from "@api/ai_api";
|
|
13
|
+
import { resendVerificationEmailRoute, userApiRoutes } from "@api/auth_api";
|
|
14
|
+
import { eventLabelsApi } from "@api/event_labels_api";
|
|
15
|
+
import { reportsApi } from "@api/reports_api";
|
|
16
|
+
import {
|
|
17
|
+
legacyContainerRoute,
|
|
18
|
+
newSiteSetup,
|
|
19
|
+
} from "@api/tag_api";
|
|
20
|
+
import { lytxTag, trackWebEvent } from "@api/tag_api_v2";
|
|
21
|
+
import { authMiddleware, sessionMiddleware } from "@api/authMiddleware";
|
|
22
|
+
import { getAuth, getAuthProviderAvailability, setAuthRuntimeConfig } from "@lib/auth";
|
|
23
|
+
import { Signup } from "@/pages/Signup";
|
|
24
|
+
import { Login } from "@/pages/Login";
|
|
25
|
+
import { VerifyEmail } from "@/pages/VerifyEmail";
|
|
26
|
+
import { SettingsPage } from "@/app/Settings";
|
|
27
|
+
import { NewSiteSetup } from "@/app/components/NewSiteSetup";
|
|
28
|
+
import { DashboardWorkspaceLayout } from "@/app/components/reports/DashboardWorkspaceLayout";
|
|
29
|
+
import { ReportBuilderWorkspace } from "@/app/components/reports/ReportBuilderWorkspace";
|
|
30
|
+
import { CustomReportBuilderPage } from "@/app/components/reports/custom/CustomReportBuilderPage";
|
|
31
|
+
import { checkIfTeamSetupSites, onlyAllowGetPost } from "@utilities/route_interuptors";
|
|
32
|
+
import type { DBAdapter } from "@db/types";
|
|
33
|
+
import { IS_DEV } from "rwsdk/constants";
|
|
34
|
+
import type { AppContext, AppRequestInfo } from "@/types/app-context";
|
|
35
|
+
import { handleQueueMessage } from "@/api/queueWorker";
|
|
36
|
+
import {
|
|
37
|
+
isAiFeatureEnabled,
|
|
38
|
+
isAskAiEnabled,
|
|
39
|
+
isAuthEnabled,
|
|
40
|
+
isDashboardEnabled,
|
|
41
|
+
isEventsEnabled,
|
|
42
|
+
isReportBuilderEnabled,
|
|
43
|
+
isTagScriptEnabled,
|
|
44
|
+
} from "@/lib/featureFlags";
|
|
45
|
+
import { parseCreateLytxAppConfig } from "@/config/createLytxAppConfig";
|
|
46
|
+
import type { CreateLytxAppConfig } from "@/config/createLytxAppConfig";
|
|
47
|
+
import { setEmailFromAddress } from "@lib/sendMail";
|
|
48
|
+
import type { DashboardResponseData } from "@db/tranformReports";
|
|
49
|
+
import { parseDateParam } from "@/utilities/dashboardParams";
|
|
50
|
+
import { getTeamSettings } from "@db/d1/teams";
|
|
51
|
+
import { d1_client } from "@db/d1/client";
|
|
52
|
+
import { user } from "@db/d1/schema";
|
|
53
|
+
import { eq } from "drizzle-orm";
|
|
54
|
+
export { SyncDurableObject } from "@/session/durableObject";
|
|
55
|
+
export { SiteDurableObject } from "@db/durable/siteDurableObject";
|
|
56
|
+
|
|
57
|
+
//TODO: Define things on context and create a middleware function where users can set adapters and override defaults
|
|
58
|
+
export type { AppContext };
|
|
59
|
+
export type { CreateLytxAppConfig } from "@/config/createLytxAppConfig";
|
|
60
|
+
|
|
61
|
+
const DEFAULT_TAG_DB_ADAPTER: DBAdapter = "sqlite";
|
|
62
|
+
const DEFAULT_TAG_SCRIPT_PATH = "/lytx.v2.js";
|
|
63
|
+
const DEFAULT_LEGACY_TAG_SCRIPT_PATH = "/lytx.js";
|
|
64
|
+
const DEFAULT_TRACK_WEB_EVENT_PATH = "/trackWebEvent.v2";
|
|
65
|
+
const DEFAULT_LEGACY_TRACK_WEB_EVENT_PATH = "/trackWebEvent";
|
|
66
|
+
|
|
67
|
+
const normalizeRoutePrefix = (value?: string): string => {
|
|
68
|
+
if (!value) return "";
|
|
69
|
+
const trimmed = value.trim();
|
|
70
|
+
if (trimmed.length === 0 || trimmed === "/") return "";
|
|
71
|
+
return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const withRoutePrefix = (prefix: string, routePath: string): string => {
|
|
75
|
+
if (!prefix) return routePath;
|
|
76
|
+
if (routePath === prefix || routePath.startsWith(`${prefix}/`)) return routePath;
|
|
77
|
+
const normalizedPath = routePath.startsWith("/") ? routePath : `/${routePath}`;
|
|
78
|
+
return `${prefix}${normalizedPath}`;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
type ToolbarSiteOption = {
|
|
82
|
+
site_id: number;
|
|
83
|
+
name: string;
|
|
84
|
+
tag_id: string;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const getInitialToolbarState = (ctx: AppContext) => {
|
|
88
|
+
const initialSites: ToolbarSiteOption[] = (ctx.sites ?? []).map((site) => ({
|
|
89
|
+
site_id: site.site_id,
|
|
90
|
+
name: site.name || `Site ${site.site_id}`,
|
|
91
|
+
tag_id: site.tag_id,
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
const preferredSiteId = ctx.session?.last_site_id ?? null;
|
|
95
|
+
const initialSiteId = initialSites.some((site) => site.site_id === preferredSiteId)
|
|
96
|
+
? preferredSiteId
|
|
97
|
+
: (initialSites[0]?.site_id ?? null);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
initialSites,
|
|
101
|
+
initialSiteId,
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const resolvePreferredTimeZone = (value: unknown): string => {
|
|
106
|
+
if (typeof value !== "string" || value.trim().length === 0) return "UTC";
|
|
107
|
+
try {
|
|
108
|
+
Intl.DateTimeFormat(undefined, { timeZone: value.trim() });
|
|
109
|
+
return value.trim();
|
|
110
|
+
} catch {
|
|
111
|
+
return "UTC";
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const resolveUserTimeZoneForServerRender = async (
|
|
116
|
+
ctx: AppContext,
|
|
117
|
+
fallbackTimeZone?: unknown,
|
|
118
|
+
): Promise<string> => {
|
|
119
|
+
try {
|
|
120
|
+
const dbUser = await d1_client
|
|
121
|
+
.select({ timezone: user.timezone })
|
|
122
|
+
.from(user)
|
|
123
|
+
.where(eq(user.id, ctx.session.user.id))
|
|
124
|
+
.limit(1);
|
|
125
|
+
|
|
126
|
+
if (dbUser[0]?.timezone) {
|
|
127
|
+
return resolvePreferredTimeZone(dbUser[0].timezone);
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (IS_DEV) {
|
|
131
|
+
console.log("🔥🔥🔥 failed to resolve user timezone for server render", error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const sessionTimeZone = ctx.session?.timezone;
|
|
136
|
+
const candidate =
|
|
137
|
+
typeof sessionTimeZone === "string" && sessionTimeZone.trim().length > 0
|
|
138
|
+
? sessionTimeZone
|
|
139
|
+
: fallbackTimeZone;
|
|
140
|
+
|
|
141
|
+
return resolvePreferredTimeZone(candidate);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const getDateStringInTimeZone = (date: Date, timeZone: string): string => {
|
|
145
|
+
const formatter = new Intl.DateTimeFormat("en-CA", {
|
|
146
|
+
timeZone,
|
|
147
|
+
year: "numeric",
|
|
148
|
+
month: "2-digit",
|
|
149
|
+
day: "2-digit",
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const parts = formatter.formatToParts(date);
|
|
153
|
+
const year = parts.find((part) => part.type === "year")?.value ?? "1970";
|
|
154
|
+
const month = parts.find((part) => part.type === "month")?.value ?? "01";
|
|
155
|
+
const day = parts.find((part) => part.type === "day")?.value ?? "01";
|
|
156
|
+
return `${year}-${month}-${day}`;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const appRoute = <TPath extends string>(
|
|
160
|
+
path: TPath,
|
|
161
|
+
handlers: Parameters<typeof route<TPath, AppRequestInfo>>[1],
|
|
162
|
+
) => route<TPath, AppRequestInfo>(path, handlers);
|
|
163
|
+
export function createLytxApp(config: CreateLytxAppConfig = {}) {
|
|
164
|
+
const parsed_config = parseCreateLytxAppConfig(config);
|
|
165
|
+
setAuthRuntimeConfig(parsed_config.auth);
|
|
166
|
+
setEmailFromAddress(parsed_config.env?.EMAIL_FROM);
|
|
167
|
+
const authProviders = getAuthProviderAvailability();
|
|
168
|
+
const emailPasswordEnabled = parsed_config.auth?.emailPasswordEnabled ?? true;
|
|
169
|
+
if (!emailPasswordEnabled && !authProviders.google && !authProviders.github) {
|
|
170
|
+
throw new Error("Invalid auth configuration: at least one auth method must be enabled");
|
|
171
|
+
}
|
|
172
|
+
const enableRequestLogging = parsed_config.enableRequestLogging ?? IS_DEV;
|
|
173
|
+
const authEnabled = parsed_config.features?.auth ?? isAuthEnabled();
|
|
174
|
+
const dashboardEnabled = authEnabled && (parsed_config.features?.dashboard ?? isDashboardEnabled());
|
|
175
|
+
const eventsEnabled = parsed_config.features?.events ?? isEventsEnabled();
|
|
176
|
+
const aiEnabled = dashboardEnabled && (parsed_config.features?.ai ?? isAiFeatureEnabled());
|
|
177
|
+
const tagScriptEnabled = parsed_config.features?.tagScript ?? isTagScriptEnabled();
|
|
178
|
+
const tagRouteDbAdapter = parsed_config.db?.dbAdapter ?? parsed_config.dbAdapter ?? DEFAULT_TAG_DB_ADAPTER;
|
|
179
|
+
const tagRouteEventStore = parsed_config.db?.eventStore ?? "durable_objects";
|
|
180
|
+
const tagRouteQueueIngestionEnabled = parsed_config.useQueueIngestion ?? (tagRouteEventStore === "durable_objects");
|
|
181
|
+
const includeLegacyTagRoutes = parsed_config.includeLegacyTagRoutes ?? true;
|
|
182
|
+
const trackingRoutePrefix = normalizeRoutePrefix(parsed_config.trackingRoutePrefix);
|
|
183
|
+
const tagScriptPath = withRoutePrefix(
|
|
184
|
+
trackingRoutePrefix,
|
|
185
|
+
parsed_config.tagRoutes?.scriptPath ?? DEFAULT_TAG_SCRIPT_PATH,
|
|
186
|
+
);
|
|
187
|
+
const legacyTagScriptPath = withRoutePrefix(
|
|
188
|
+
trackingRoutePrefix,
|
|
189
|
+
parsed_config.tagRoutes?.legacyScriptPath ?? DEFAULT_LEGACY_TAG_SCRIPT_PATH,
|
|
190
|
+
);
|
|
191
|
+
const trackWebEventPath = withRoutePrefix(
|
|
192
|
+
trackingRoutePrefix,
|
|
193
|
+
parsed_config.tagRoutes?.eventPath ?? DEFAULT_TRACK_WEB_EVENT_PATH,
|
|
194
|
+
);
|
|
195
|
+
const legacyTrackWebEventPath = withRoutePrefix(
|
|
196
|
+
trackingRoutePrefix,
|
|
197
|
+
parsed_config.tagRoutes?.legacyEventPath ?? DEFAULT_LEGACY_TRACK_WEB_EVENT_PATH,
|
|
198
|
+
);
|
|
199
|
+
const reportBuilderEnabled =
|
|
200
|
+
dashboardEnabled && (parsed_config.features?.reportBuilderEnabled ?? isReportBuilderEnabled());
|
|
201
|
+
const askAiEnabled =
|
|
202
|
+
aiEnabled && (parsed_config.features?.askAiEnabled ?? (reportBuilderEnabled && isAskAiEnabled()));
|
|
203
|
+
|
|
204
|
+
const tagRoutes: Array<
|
|
205
|
+
typeof legacyContainerRoute | ReturnType<typeof lytxTag> | ReturnType<typeof trackWebEvent>
|
|
206
|
+
> = [];
|
|
207
|
+
|
|
208
|
+
if (includeLegacyTagRoutes && tagScriptEnabled) {
|
|
209
|
+
tagRoutes.push(legacyContainerRoute);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (tagScriptEnabled) {
|
|
213
|
+
tagRoutes.push(lytxTag(tagRouteDbAdapter, tagScriptPath));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (eventsEnabled) {
|
|
217
|
+
tagRoutes.push(trackWebEvent(tagRouteDbAdapter, trackWebEventPath, { useQueue: tagRouteQueueIngestionEnabled }));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (includeLegacyTagRoutes) {
|
|
221
|
+
if (tagScriptEnabled && legacyTagScriptPath !== tagScriptPath) {
|
|
222
|
+
tagRoutes.push(lytxTag(tagRouteDbAdapter, legacyTagScriptPath));
|
|
223
|
+
}
|
|
224
|
+
if (eventsEnabled && legacyTrackWebEventPath !== trackWebEventPath) {
|
|
225
|
+
tagRoutes.push(
|
|
226
|
+
trackWebEvent(tagRouteDbAdapter, legacyTrackWebEventPath, { useQueue: tagRouteQueueIngestionEnabled }),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const app = defineApp<AppRequestInfo>([
|
|
232
|
+
({ request }) => {
|
|
233
|
+
if (enableRequestLogging) console.log("🔥🔥🔥", request.method, request.url);
|
|
234
|
+
},
|
|
235
|
+
//NOTE: API ROUTES / no component or html rendering
|
|
236
|
+
...tagRoutes,
|
|
237
|
+
...(eventsEnabled ? [eventsApi] : []),
|
|
238
|
+
seedApi,
|
|
239
|
+
...(authEnabled
|
|
240
|
+
? [
|
|
241
|
+
route("/api/auth/*", (r) => authMiddleware(r)),
|
|
242
|
+
resendVerificationEmailRoute,
|
|
243
|
+
userApiRoutes,
|
|
244
|
+
]
|
|
245
|
+
: []),
|
|
246
|
+
render<AppRequestInfo>(Document, [
|
|
247
|
+
route("/", [
|
|
248
|
+
onlyAllowGetPost, ({ request }) => {
|
|
249
|
+
return Response.redirect(new URL("/login", request.url).toString(), 308);
|
|
250
|
+
},
|
|
251
|
+
]),
|
|
252
|
+
...(authEnabled
|
|
253
|
+
? [
|
|
254
|
+
route("/signup", [
|
|
255
|
+
onlyAllowGetPost, () => {
|
|
256
|
+
return <Signup authProviders={authProviders} emailPasswordEnabled={emailPasswordEnabled} />;
|
|
257
|
+
},
|
|
258
|
+
]),
|
|
259
|
+
]
|
|
260
|
+
: []),
|
|
261
|
+
...(authEnabled
|
|
262
|
+
? [
|
|
263
|
+
route("/login", [
|
|
264
|
+
onlyAllowGetPost,
|
|
265
|
+
() => {
|
|
266
|
+
return <Login authProviders={authProviders} emailPasswordEnabled={emailPasswordEnabled} />;
|
|
267
|
+
},
|
|
268
|
+
]),
|
|
269
|
+
route("/verify-email", [
|
|
270
|
+
onlyAllowGetPost,
|
|
271
|
+
async ({ request }) => {
|
|
272
|
+
const requestId = crypto.randomUUID();
|
|
273
|
+
const url = new URL(request.url);
|
|
274
|
+
const token = url.searchParams.get("token") || "";
|
|
275
|
+
|
|
276
|
+
const callbackURL = url.searchParams.get("callbackURL") || undefined;
|
|
277
|
+
const safeCallbackURL =
|
|
278
|
+
callbackURL && callbackURL.startsWith("/") && !callbackURL.includes("://")
|
|
279
|
+
? callbackURL
|
|
280
|
+
: undefined;
|
|
281
|
+
|
|
282
|
+
if (!token) {
|
|
283
|
+
if (IS_DEV) console.warn("Email verification failed: missing token", { requestId });
|
|
284
|
+
return (
|
|
285
|
+
<VerifyEmail
|
|
286
|
+
status={{
|
|
287
|
+
type: "error",
|
|
288
|
+
message:
|
|
289
|
+
"That verification link is missing a token. Please request a new verification email.",
|
|
290
|
+
callbackURL: safeCallbackURL,
|
|
291
|
+
}}
|
|
292
|
+
/>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const auth = getAuth();
|
|
298
|
+
await auth.api.verifyEmail({
|
|
299
|
+
query: {
|
|
300
|
+
token,
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<VerifyEmail
|
|
306
|
+
status={{
|
|
307
|
+
type: "success",
|
|
308
|
+
message: "Your email has been verified. You can continue.",
|
|
309
|
+
callbackURL: safeCallbackURL,
|
|
310
|
+
}}
|
|
311
|
+
/>
|
|
312
|
+
);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
const message = error instanceof Error ? error.message : "";
|
|
315
|
+
const normalized = message.toLowerCase();
|
|
316
|
+
|
|
317
|
+
const friendlyMessage =
|
|
318
|
+
normalized.includes("token_expired") || normalized.includes("expired")
|
|
319
|
+
? "That verification link has expired. Please request a new verification email."
|
|
320
|
+
: normalized.includes("invalid_token") || normalized.includes("invalid")
|
|
321
|
+
? "That verification link is invalid. Please request a new verification email."
|
|
322
|
+
: normalized.includes("user_not_found")
|
|
323
|
+
? "We couldn't find an account for that link. Please request a new verification email."
|
|
324
|
+
: "We couldn't verify your email. Please request a new verification email.";
|
|
325
|
+
|
|
326
|
+
if (IS_DEV) console.warn("Email verification failed", {
|
|
327
|
+
requestId,
|
|
328
|
+
error: error instanceof Error ? { name: error.name, message: error.message } : error,
|
|
329
|
+
tokenPresent: Boolean(token),
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<VerifyEmail
|
|
334
|
+
status={{
|
|
335
|
+
type: "error",
|
|
336
|
+
message: friendlyMessage,
|
|
337
|
+
callbackURL: safeCallbackURL,
|
|
338
|
+
}}
|
|
339
|
+
/>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
]),
|
|
344
|
+
]
|
|
345
|
+
: []),
|
|
346
|
+
...(dashboardEnabled
|
|
347
|
+
? [layout(AppLayout, [
|
|
348
|
+
sessionMiddleware,
|
|
349
|
+
//PERF: This API PREFIX REQUIRES AUTHENTICATION
|
|
350
|
+
prefix<"/api", AppRequestInfo>("/api", [
|
|
351
|
+
world_countries,
|
|
352
|
+
getDashboardDataRoute,
|
|
353
|
+
getCurrentVisitorsRoute,
|
|
354
|
+
...(aiEnabled ? [aiConfigRoute, aiChatRoute, aiTagSuggestRoute] : []),
|
|
355
|
+
siteEventsSqlRoute,
|
|
356
|
+
siteEventsSchemaRoute,
|
|
357
|
+
eventLabelsApi,
|
|
358
|
+
...(reportBuilderEnabled ? [reportsApi] : []),
|
|
359
|
+
///api/sites
|
|
360
|
+
//PERF: Add method to api prefix outside of sessionMiddleware loop
|
|
361
|
+
newSiteSetup(),
|
|
362
|
+
team_dashboard_endpoints
|
|
363
|
+
]),
|
|
364
|
+
onlyAllowGetPost,
|
|
365
|
+
route("/dashboard", [
|
|
366
|
+
checkIfTeamSetupSites,
|
|
367
|
+
async ({ request, ctx }) => {
|
|
368
|
+
const pathname = new URL(request.url).pathname;
|
|
369
|
+
if (pathname === "/dashboard/") {
|
|
370
|
+
return Response.redirect(new URL("/dashboard", request.url).toString(), 308);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const toolbarState = getInitialToolbarState(ctx);
|
|
374
|
+
const timezone = await resolveUserTimeZoneForServerRender(
|
|
375
|
+
ctx,
|
|
376
|
+
(request as Request & { cf?: { timezone?: string } }).cf?.timezone,
|
|
377
|
+
);
|
|
378
|
+
const today = getDateStringInTimeZone(new Date(), timezone);
|
|
379
|
+
const todayStart = parseDateParam(today, { timeZone: timezone, boundary: "start" });
|
|
380
|
+
const todayEnd = parseDateParam(today, { timeZone: timezone, boundary: "end" });
|
|
381
|
+
|
|
382
|
+
let initialDashboardData: DashboardResponseData | null = null;
|
|
383
|
+
if (toolbarState.initialSiteId && todayStart && todayEnd) {
|
|
384
|
+
try {
|
|
385
|
+
const dashboardDataResult = await getDashboardDataCore({
|
|
386
|
+
ctx,
|
|
387
|
+
requestId: crypto.randomUUID(),
|
|
388
|
+
siteIdValue: toolbarState.initialSiteId,
|
|
389
|
+
dateStartValue: todayStart,
|
|
390
|
+
dateEndValue: todayEnd,
|
|
391
|
+
rawDateEnd: today,
|
|
392
|
+
normalizedTimezone: timezone,
|
|
393
|
+
normalizedDeviceType: null,
|
|
394
|
+
normalizedCountry: null,
|
|
395
|
+
normalizedSource: null,
|
|
396
|
+
normalizedPageUrl: null,
|
|
397
|
+
normalizedCity: null,
|
|
398
|
+
normalizedRegion: null,
|
|
399
|
+
normalizedEventName: null,
|
|
400
|
+
normalizedEventSummaryLimit: 50,
|
|
401
|
+
normalizedEventSummaryOffset: 0,
|
|
402
|
+
normalizedEventSummaryType: "all",
|
|
403
|
+
normalizedEventSummaryAction: "all",
|
|
404
|
+
normalizedEventSummarySortBy: "count",
|
|
405
|
+
normalizedEventSummarySortDirection: "desc",
|
|
406
|
+
eventSummarySearch: "",
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
if (dashboardDataResult.ok) {
|
|
410
|
+
initialDashboardData = dashboardDataResult.data;
|
|
411
|
+
}
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (IS_DEV) {
|
|
414
|
+
console.log("🔥🔥🔥 failed to prefetch today dashboard", error);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<DashboardPage
|
|
421
|
+
activeReportBuilderItemId="create-report"
|
|
422
|
+
reportBuilderEnabled={reportBuilderEnabled}
|
|
423
|
+
askAiEnabled={askAiEnabled}
|
|
424
|
+
initialToolbarSites={toolbarState.initialSites}
|
|
425
|
+
initialToolbarSiteId={toolbarState.initialSiteId}
|
|
426
|
+
initialDashboardDateRange={{
|
|
427
|
+
start: today,
|
|
428
|
+
end: today,
|
|
429
|
+
preset: "Today",
|
|
430
|
+
}}
|
|
431
|
+
initialTimezone={timezone}
|
|
432
|
+
initialDashboardData={initialDashboardData}
|
|
433
|
+
/>
|
|
434
|
+
);
|
|
435
|
+
},
|
|
436
|
+
]),
|
|
437
|
+
layout<AppRequestInfo>(DashboardWorkspaceLayout, (reportBuilderEnabled
|
|
438
|
+
? [
|
|
439
|
+
appRoute("/dashboard/reports", [
|
|
440
|
+
({ request }) => {
|
|
441
|
+
return Response.redirect(new URL("/dashboard/reports/create-report", request.url).toString(), 308);
|
|
442
|
+
},
|
|
443
|
+
]),
|
|
444
|
+
appRoute("/dashboard/reports/custom/new", [
|
|
445
|
+
checkIfTeamSetupSites,
|
|
446
|
+
({ request }) => {
|
|
447
|
+
const url = new URL(request.url);
|
|
448
|
+
const template = url.searchParams.get("template");
|
|
449
|
+
return <CustomReportBuilderPage initialTemplate={template} />;
|
|
450
|
+
},
|
|
451
|
+
]),
|
|
452
|
+
appRoute("/dashboard/reports/custom/*", [
|
|
453
|
+
checkIfTeamSetupSites,
|
|
454
|
+
({ request }) => {
|
|
455
|
+
const pathname = new URL(request.url).pathname;
|
|
456
|
+
const marker = "/dashboard/reports/custom/";
|
|
457
|
+
const reportUuid = pathname.includes(marker)
|
|
458
|
+
? decodeURIComponent(pathname.slice(pathname.indexOf(marker) + marker.length))
|
|
459
|
+
: "";
|
|
460
|
+
|
|
461
|
+
if (!reportUuid || reportUuid.includes("/") || reportUuid === "new") {
|
|
462
|
+
return Response.redirect(new URL("/dashboard/reports/create-report", request.url).toString(), 308);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return <CustomReportBuilderPage reportUuid={reportUuid} />;
|
|
466
|
+
},
|
|
467
|
+
]),
|
|
468
|
+
appRoute("/dashboard/reports/create-report", [
|
|
469
|
+
checkIfTeamSetupSites,
|
|
470
|
+
(_info) => {
|
|
471
|
+
return <ReportBuilderWorkspace activeReportBuilderItemId="create-report" />;
|
|
472
|
+
},
|
|
473
|
+
]),
|
|
474
|
+
appRoute("/dashboard/reports/create-reference", [
|
|
475
|
+
checkIfTeamSetupSites,
|
|
476
|
+
(_info) => {
|
|
477
|
+
return <ReportBuilderWorkspace activeReportBuilderItemId="create-reference" />;
|
|
478
|
+
},
|
|
479
|
+
]),
|
|
480
|
+
appRoute("/dashboard/reports/ask-ai", [
|
|
481
|
+
checkIfTeamSetupSites,
|
|
482
|
+
({ ctx, request }) => {
|
|
483
|
+
if (!askAiEnabled) {
|
|
484
|
+
return Response.redirect(new URL("/dashboard/reports/create-report", request.url).toString(), 308);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const aiConfig = getAiConfig(ctx.team.id);
|
|
488
|
+
const askAiWorkspaceProps = {
|
|
489
|
+
activeReportBuilderItemId: "ask-ai" as const,
|
|
490
|
+
initialAiConfigured: Boolean(aiConfig),
|
|
491
|
+
initialAiModel: aiConfig?.model ?? "",
|
|
492
|
+
} as const;
|
|
493
|
+
|
|
494
|
+
return <ReportBuilderWorkspace {...askAiWorkspaceProps} />;
|
|
495
|
+
},
|
|
496
|
+
]),
|
|
497
|
+
appRoute("/dashboard/reports/create-dashboard", [
|
|
498
|
+
checkIfTeamSetupSites,
|
|
499
|
+
(_info) => {
|
|
500
|
+
return <ReportBuilderWorkspace activeReportBuilderItemId="create-dashboard" />;
|
|
501
|
+
},
|
|
502
|
+
]),
|
|
503
|
+
appRoute("/dashboard/reports/create-notification", [
|
|
504
|
+
checkIfTeamSetupSites,
|
|
505
|
+
(_info) => {
|
|
506
|
+
return <ReportBuilderWorkspace activeReportBuilderItemId="create-notification" />;
|
|
507
|
+
},
|
|
508
|
+
]),
|
|
509
|
+
]
|
|
510
|
+
: [
|
|
511
|
+
appRoute("/dashboard/reports", [
|
|
512
|
+
({ request }) => {
|
|
513
|
+
return Response.redirect(new URL("/dashboard", request.url).toString(), 308);
|
|
514
|
+
},
|
|
515
|
+
]),
|
|
516
|
+
appRoute("/dashboard/reports/*", [
|
|
517
|
+
({ request }) => {
|
|
518
|
+
return Response.redirect(new URL("/dashboard", request.url).toString(), 308);
|
|
519
|
+
},
|
|
520
|
+
]),
|
|
521
|
+
])),
|
|
522
|
+
...(eventsEnabled
|
|
523
|
+
? [
|
|
524
|
+
appRoute("/dashboard/events", [
|
|
525
|
+
checkIfTeamSetupSites,
|
|
526
|
+
async (_info) => {
|
|
527
|
+
return <EventsPage />;
|
|
528
|
+
},
|
|
529
|
+
]),
|
|
530
|
+
]
|
|
531
|
+
: []),
|
|
532
|
+
appRoute("/dashboard/new-site", [
|
|
533
|
+
(_info) => {
|
|
534
|
+
return <NewSiteSetup />;
|
|
535
|
+
},
|
|
536
|
+
]),
|
|
537
|
+
appRoute("/dashboard/settings", [
|
|
538
|
+
async ({ ctx }) => {
|
|
539
|
+
const toolbarState = getInitialToolbarState(ctx);
|
|
540
|
+
const initialCurrentSite = toolbarState.initialSites.find(
|
|
541
|
+
(site) => site.site_id === toolbarState.initialSiteId,
|
|
542
|
+
) ?? null;
|
|
543
|
+
|
|
544
|
+
const sessionUserSites = Array.isArray(ctx.session.userSites)
|
|
545
|
+
? ctx.session.userSites
|
|
546
|
+
: [];
|
|
547
|
+
|
|
548
|
+
const initialUserSites = sessionUserSites.length > 0
|
|
549
|
+
? sessionUserSites.map((site) => ({
|
|
550
|
+
site_id: site.site_id,
|
|
551
|
+
name: site.name ?? null,
|
|
552
|
+
domain: site.domain ?? null,
|
|
553
|
+
tag_id: site.tag_id,
|
|
554
|
+
createdAt: site.createdAt ?? null,
|
|
555
|
+
}))
|
|
556
|
+
: (ctx.sites ?? []).map((site) => ({
|
|
557
|
+
site_id: site.site_id,
|
|
558
|
+
name: site.name ?? null,
|
|
559
|
+
domain: site.domain ?? null,
|
|
560
|
+
tag_id: site.tag_id,
|
|
561
|
+
createdAt: null,
|
|
562
|
+
}));
|
|
563
|
+
|
|
564
|
+
const sessionTeam = ctx.session.team;
|
|
565
|
+
|
|
566
|
+
let initialTimezone: string | null =
|
|
567
|
+
ctx.session.timezone && typeof ctx.session.timezone === "string"
|
|
568
|
+
? ctx.session.timezone
|
|
569
|
+
: null;
|
|
570
|
+
|
|
571
|
+
if (!initialTimezone) {
|
|
572
|
+
try {
|
|
573
|
+
const dbUser = await d1_client
|
|
574
|
+
.select({ timezone: user.timezone })
|
|
575
|
+
.from(user)
|
|
576
|
+
.where(eq(user.id, ctx.session.user.id))
|
|
577
|
+
.limit(1);
|
|
578
|
+
initialTimezone = dbUser[0]?.timezone ?? null;
|
|
579
|
+
} catch (error) {
|
|
580
|
+
if (IS_DEV) {
|
|
581
|
+
console.log("🔥🔥🔥 failed to prefetch user timezone", error);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const [teamSettingsResult] = await Promise.allSettled([
|
|
587
|
+
getTeamSettings(ctx.team.id),
|
|
588
|
+
]);
|
|
589
|
+
|
|
590
|
+
const initialTeamSettings =
|
|
591
|
+
teamSettingsResult.status === "fulfilled" ? teamSettingsResult.value : null;
|
|
592
|
+
if (teamSettingsResult.status === "rejected" && IS_DEV) {
|
|
593
|
+
console.log("🔥🔥🔥 failed to prefetch team settings", teamSettingsResult.reason);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return (
|
|
597
|
+
<SettingsPage
|
|
598
|
+
initialSession={{
|
|
599
|
+
user: {
|
|
600
|
+
name: ctx.session.user?.name ?? null,
|
|
601
|
+
email: ctx.session.user?.email ?? null,
|
|
602
|
+
},
|
|
603
|
+
team: {
|
|
604
|
+
id: sessionTeam?.id ?? ctx.team.id,
|
|
605
|
+
name: sessionTeam?.name ?? ctx.team.name ?? null,
|
|
606
|
+
external_id: sessionTeam?.external_id ?? ctx.team.external_id ?? null,
|
|
607
|
+
},
|
|
608
|
+
role: ctx.user_role,
|
|
609
|
+
timezone: initialTimezone,
|
|
610
|
+
userSites: initialUserSites,
|
|
611
|
+
}}
|
|
612
|
+
initialCurrentSite={initialCurrentSite
|
|
613
|
+
? {
|
|
614
|
+
id: initialCurrentSite.site_id,
|
|
615
|
+
name: initialCurrentSite.name,
|
|
616
|
+
tag_id: initialCurrentSite.tag_id,
|
|
617
|
+
}
|
|
618
|
+
: null}
|
|
619
|
+
initialSites={toolbarState.initialSites}
|
|
620
|
+
initialTeamSettings={initialTeamSettings}
|
|
621
|
+
/>
|
|
622
|
+
);
|
|
623
|
+
},
|
|
624
|
+
]),
|
|
625
|
+
appRoute("/dashboard/explore", [
|
|
626
|
+
checkIfTeamSetupSites,
|
|
627
|
+
({ ctx }) => {
|
|
628
|
+
const toolbarState = getInitialToolbarState(ctx);
|
|
629
|
+
return (
|
|
630
|
+
<ExplorePage
|
|
631
|
+
initialSites={toolbarState.initialSites}
|
|
632
|
+
initialSiteId={toolbarState.initialSiteId}
|
|
633
|
+
/>
|
|
634
|
+
);
|
|
635
|
+
},
|
|
636
|
+
]),
|
|
637
|
+
...(eventsEnabled
|
|
638
|
+
? [
|
|
639
|
+
appRoute("/admin/events", [
|
|
640
|
+
({ request }) => {
|
|
641
|
+
return Response.redirect(new URL("/dashboard/events", request.url).toString(), 308);
|
|
642
|
+
},
|
|
643
|
+
]),
|
|
644
|
+
]
|
|
645
|
+
: []),
|
|
646
|
+
appRoute("/new-site", [
|
|
647
|
+
({ request }) => {
|
|
648
|
+
return Response.redirect(new URL("/dashboard/new-site", request.url).toString(), 308);
|
|
649
|
+
},
|
|
650
|
+
]),
|
|
651
|
+
appRoute("/settings", [
|
|
652
|
+
({ request }) => {
|
|
653
|
+
return Response.redirect(new URL("/dashboard/settings", request.url).toString(), 308);
|
|
654
|
+
},
|
|
655
|
+
]),
|
|
656
|
+
appRoute("/explore", [
|
|
657
|
+
({ request }) => {
|
|
658
|
+
return Response.redirect(new URL("/dashboard/explore", request.url).toString(), 308);
|
|
659
|
+
},
|
|
660
|
+
]),
|
|
661
|
+
])]
|
|
662
|
+
: []),
|
|
663
|
+
|
|
664
|
+
]),
|
|
665
|
+
]);
|
|
666
|
+
|
|
667
|
+
return {
|
|
668
|
+
fetch: app.fetch,
|
|
669
|
+
queue: handleQueueMessage,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const defaultApp = createLytxApp();
|
|
674
|
+
|
|
675
|
+
export default defaultApp;
|