lytx 0.3.12 → 0.3.15
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 +99 -1
- package/cli/seed-data.ts +160 -4
- package/db/tranformReports.ts +98 -9
- package/index.d.ts +13 -0
- package/index.ts +13 -0
- package/lib/auth.ts +4 -4
- package/package.json +7 -1
- package/src/Document.tsx +1 -1
- package/src/api/authMiddleware.ts +1 -1
- package/src/api/demo_middleware.ts +91 -0
- package/src/api/seed_api.ts +10 -7
- package/src/app/Dashboard.tsx +29 -21
- package/src/app/Layout.tsx +5 -4
- package/src/app/components/Nav.tsx +30 -19
- package/src/app/components/SiteSelector.tsx +4 -1
- package/src/app/components/SiteTagInstallCard.tsx +2 -2
- package/src/app/components/WorldMapCard.tsx +12 -3
- package/src/app/components/charts/ChartComponents.tsx +25 -5
- package/src/app/providers/AuthProvider.tsx +76 -46
- package/src/app/providers/ClientProviders.tsx +5 -2
- package/src/config/createLytxAppConfig.ts +53 -2
- package/src/config/routeUiOverrides.ts +222 -0
- package/src/index.css +2 -0
- package/src/worker.tsx +243 -178
- package/tsconfig.json +1 -0
- package/vite/index.ts +5 -0
- package/vite/vite-plugin-consumer.ts +112 -0
- package/vite/vite-plugin-pixel-bundle.ts +6 -2
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ The supported public API surface for `lytx` is documented in `core/docs/oss-cont
|
|
|
9
9
|
- Contract doc: [`docs/oss-contract.md`](./docs/oss-contract.md)
|
|
10
10
|
- Self-host quickstart: [`docs/self-host-quickstart.md`](./docs/self-host-quickstart.md)
|
|
11
11
|
- Semver/release policy: [`docs/release-policy.md`](./docs/release-policy.md)
|
|
12
|
+
- Changelog: [`docs/changelog.md`](./docs/changelog.md)
|
|
12
13
|
- Upgrade/migration guide: [`docs/migration-guide.md`](./docs/migration-guide.md)
|
|
13
14
|
- Read this first before relying on any non-root or deep import path.
|
|
14
15
|
|
|
@@ -62,6 +63,34 @@ export { SyncDurableObject, SiteDurableObject };
|
|
|
62
63
|
export default app satisfies ExportedHandler<Env>;
|
|
63
64
|
```
|
|
64
65
|
|
|
66
|
+
Add the Vite plugin preset so Redwood can resolve Lytx internals without manual alias setup:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// vite.config.ts
|
|
70
|
+
import { defineConfig } from "vite";
|
|
71
|
+
import alchemy from "alchemy/cloudflare/redwood";
|
|
72
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
73
|
+
import { lytxConsumerVitePlugin } from "lytx/vite";
|
|
74
|
+
|
|
75
|
+
export default defineConfig({
|
|
76
|
+
plugins: [...lytxConsumerVitePlugin(), alchemy(), tailwindcss()],
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`lytxConsumerVitePlugin()` uses built-in `lytx` defaults for both document and client entry, so consumers do not need local `src/Document.tsx` or `src/client.tsx`. To customize the document wrapper, use `createLytxApp({ routes: { document } })`.
|
|
81
|
+
|
|
82
|
+
For custom document wrappers, import the public stylesheet entrypoint:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import styles from "lytx/styles.css?url";
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
When providing a custom document, render `{children}` directly (do not wrap it in another `hydrate-root` container):
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
{children}
|
|
92
|
+
```
|
|
93
|
+
|
|
65
94
|
`createLytxApp` supports:
|
|
66
95
|
|
|
67
96
|
- `features.dashboard`, `features.events`, `features.auth`, `features.ai`, `features.tagScript`
|
|
@@ -72,7 +101,7 @@ export default app satisfies ExportedHandler<Env>;
|
|
|
72
101
|
- `trackingRoutePrefix` (prefix all tracking routes, e.g. `/collect`)
|
|
73
102
|
- `tagRoutes.scriptPath` + `tagRoutes.eventPath` (custom v2 route paths)
|
|
74
103
|
- `auth.emailPasswordEnabled`, `auth.requireEmailVerification`, `auth.socialProviders.google`, `auth.socialProviders.github`
|
|
75
|
-
- `auth.signupMode` (`"open" | "bootstrap_then_invite" | "invite_only"`; default is `"bootstrap_then_invite"`)
|
|
104
|
+
- `auth.signupMode` (`"open" | "bootstrap_then_invite" | "invite_only" | "demo"`; default is `"bootstrap_then_invite"`)
|
|
76
105
|
- `ai.provider`, `ai.model`, `ai.baseURL`, `ai.apiKey`, `ai.accountId` (runtime AI vendor/model overrides; blank values are ignored; provider/model include preset autocomplete values)
|
|
77
106
|
- `features.reportBuilderEnabled` + `features.askAiEnabled`
|
|
78
107
|
- `names.*` (typed resource binding names for D1/KV/Queue/DO)
|
|
@@ -80,6 +109,72 @@ export default app satisfies ExportedHandler<Env>;
|
|
|
80
109
|
- `startupValidation.*` + `env.*` (startup env requirement checks with field-level errors)
|
|
81
110
|
- `env.AI_PROVIDER`, `env.AI_BASE_URL`, `env.AI_MODEL` (AI vendor/model routing overrides)
|
|
82
111
|
- `env.EMAIL_FROM` (optional factory override for outgoing email sender)
|
|
112
|
+
- `routes.ui.dashboard`, `routes.ui.events`, `routes.ui.explore` (typed per-route UI overrides with route-specific `info`/props)
|
|
113
|
+
- `routes.document` (typed RedwoodSDK `Document` override for render wrapper)
|
|
114
|
+
- `routes.additionalRoutes` (typed RedwoodSDK route entries appended to core route tree)
|
|
115
|
+
|
|
116
|
+
### Route UI overrides
|
|
117
|
+
|
|
118
|
+
Use `routes.ui` when you want to keep core routing/middleware but swap page UI for specific routes:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import {
|
|
122
|
+
DashboardPage,
|
|
123
|
+
EventsPage,
|
|
124
|
+
ExplorePage,
|
|
125
|
+
createLytxApp,
|
|
126
|
+
type DashboardPageProps,
|
|
127
|
+
} from "lytx";
|
|
128
|
+
import { route, type DocumentProps } from "rwsdk/router";
|
|
129
|
+
|
|
130
|
+
function CustomDocument({ children }: DocumentProps) {
|
|
131
|
+
return (
|
|
132
|
+
<html lang="en">
|
|
133
|
+
<body data-app="custom-document">{children}</body>
|
|
134
|
+
</html>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const app = createLytxApp({
|
|
139
|
+
routes: {
|
|
140
|
+
document: CustomDocument,
|
|
141
|
+
additionalRoutes: [
|
|
142
|
+
route("/dashboard/custom", ({ ctx }) => {
|
|
143
|
+
return new Response(`Hello team ${ctx.team.id}`);
|
|
144
|
+
}),
|
|
145
|
+
],
|
|
146
|
+
ui: {
|
|
147
|
+
dashboard: ({ info, defaultProps, helpers }) => {
|
|
148
|
+
// Keep calling the same APIs/helpers core expects for this route.
|
|
149
|
+
// See /api docs before replacing route UI behavior.
|
|
150
|
+
const _teamId = info.ctx.team.id;
|
|
151
|
+
const _dashboardFetcher = helpers.getDashboardDataCore;
|
|
152
|
+
const _initialData = defaultProps.reportData.initialDashboardData;
|
|
153
|
+
|
|
154
|
+
const { reportData, ...pageProps } = defaultProps;
|
|
155
|
+
|
|
156
|
+
const customProps: DashboardPageProps = {
|
|
157
|
+
...pageProps,
|
|
158
|
+
...reportData,
|
|
159
|
+
activeReportBuilderItemId: "create-report",
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return <DashboardPage {...customProps} />;
|
|
163
|
+
},
|
|
164
|
+
events: ({ info }) => {
|
|
165
|
+
const _userId = info.ctx.session.user.id;
|
|
166
|
+
return <EventsPage />;
|
|
167
|
+
},
|
|
168
|
+
explore: ({ defaultProps }) => {
|
|
169
|
+
return <ExplorePage {...defaultProps} />;
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
TypeScript autocomplete is route-specific. For example, `routes.ui.dashboard` exposes dashboard defaults/helpers, while `routes.ui.explore` only exposes explore defaults.
|
|
177
|
+
`routes.additionalRoutes` enforces RedwoodSDK route entry types.
|
|
83
178
|
|
|
84
179
|
For deployment scripts, use `resolveLytxResourceNames(...)` from `lytx/resource-names` to derive deterministic Cloudflare resource names with optional stage-based prefix/suffix strategy.
|
|
85
180
|
|
|
@@ -369,10 +464,13 @@ createLytxApp({
|
|
|
369
464
|
signupMode: "bootstrap_then_invite",
|
|
370
465
|
// signupMode: "invite_only", // never allow public signup
|
|
371
466
|
// signupMode: "open", // always allow public signup
|
|
467
|
+
// signupMode: "demo", // DISABLES auth and makes dashboard/app routes publicly accessible
|
|
372
468
|
},
|
|
373
469
|
});
|
|
374
470
|
```
|
|
375
471
|
|
|
472
|
+
`"demo"` mode is intentionally unsafe for production. It bypasses login/session requirements for dashboard and app routes so anyone with the URL can access the product experience.
|
|
473
|
+
|
|
376
474
|
If you need to bootstrap an admin user without public signup, use the CLI:
|
|
377
475
|
|
|
378
476
|
```bash
|
package/cli/seed-data.ts
CHANGED
|
@@ -28,6 +28,7 @@ Requires the dev server to be running (bun run dev).
|
|
|
28
28
|
Options:
|
|
29
29
|
-t, --team-id <id> Team ID to create sites for (required)
|
|
30
30
|
-s, --sites <number> Number of sites to create (default: 3)
|
|
31
|
+
--site-names <csv> Comma-separated site names (used in order when creating sites)
|
|
31
32
|
--site-id <id> Populate existing site ID with events (skips site creation)
|
|
32
33
|
-e, --events <number> Number of events per site (default: 100)
|
|
33
34
|
--days <number> Number of days back to generate events (default: 30)
|
|
@@ -37,6 +38,7 @@ Options:
|
|
|
37
38
|
|
|
38
39
|
Example:
|
|
39
40
|
bun run cli/seed-data.ts --team-id 1 --sites 2 --events 50 --seed-secret "$SEED_DATA_SECRET"
|
|
41
|
+
bun run cli/seed-data.ts --team-id 1 --sites 2 --site-names "Site1,Site2" --events 50 --seed-secret "$SEED_DATA_SECRET"
|
|
40
42
|
bun run cli/seed-data.ts --team-id 1 --site-id 3 --events 100 --seed-secret "$SEED_DATA_SECRET"
|
|
41
43
|
`);
|
|
42
44
|
process.exit(0);
|
|
@@ -91,11 +93,26 @@ const getSiteIdArg = () => {
|
|
|
91
93
|
}
|
|
92
94
|
};
|
|
93
95
|
|
|
96
|
+
const getSiteNamesArg = () => {
|
|
97
|
+
try {
|
|
98
|
+
const value = getArg("--site-names");
|
|
99
|
+
const parsed = value
|
|
100
|
+
.split(",")
|
|
101
|
+
.map((name) => name.trim())
|
|
102
|
+
.filter((name) => name.length > 0);
|
|
103
|
+
|
|
104
|
+
return parsed.length > 0 ? parsed : null;
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
94
110
|
const teamId = getTeamIdArg();
|
|
95
111
|
const numSites = getSitesArg();
|
|
96
112
|
const numEvents = getEventsArg();
|
|
97
113
|
const numDays = getDaysArg();
|
|
98
114
|
const siteId = getSiteIdArg();
|
|
115
|
+
const customSiteNames = getSiteNamesArg();
|
|
99
116
|
const devUrl = hasFlag("--dev-url") ? getArg("--dev-url") : "http://localhost:6123";
|
|
100
117
|
const seedSecret = hasFlag("--seed-secret") ? getArg("--seed-secret") : "";
|
|
101
118
|
|
|
@@ -156,6 +173,13 @@ const sampleReferrers = [
|
|
|
156
173
|
"https://medium.com",
|
|
157
174
|
"https://dev.to",
|
|
158
175
|
"https://hackernews.com",
|
|
176
|
+
"https://chatgpt.com",
|
|
177
|
+
"https://chat.openai.com",
|
|
178
|
+
"https://claude.ai",
|
|
179
|
+
"https://gemini.google.com",
|
|
180
|
+
"https://perplexity.ai",
|
|
181
|
+
"https://copilot.microsoft.com",
|
|
182
|
+
"https://poe.com",
|
|
159
183
|
"",
|
|
160
184
|
"direct",
|
|
161
185
|
];
|
|
@@ -180,15 +204,129 @@ const sampleDeviceTypes = ["desktop", "mobile", "tablet"];
|
|
|
180
204
|
|
|
181
205
|
const sampleGeo = [
|
|
182
206
|
{ country: "US", region: "California", city: "San Francisco" },
|
|
207
|
+
{ country: "US", region: "Virginia", city: "Ashburn" },
|
|
208
|
+
{ country: "US", region: "Oregon", city: "Portland" },
|
|
183
209
|
{ country: "US", region: "Texas", city: "Austin" },
|
|
210
|
+
{ country: "US", region: "Washington", city: "Seattle" },
|
|
184
211
|
{ country: "US", region: "New York", city: "New York" },
|
|
185
|
-
{ country: "
|
|
212
|
+
{ country: "US", region: "North Carolina", city: "Charlotte" },
|
|
213
|
+
{ country: "US", region: "Illinois", city: "Chicago" },
|
|
214
|
+
{ country: "US", region: "Florida", city: "Miami" },
|
|
215
|
+
{ country: "US", region: "Ohio", city: "Columbus" },
|
|
216
|
+
{ country: "US", region: "New Jersey", city: "Newark" },
|
|
217
|
+
{ country: "US", region: "Minnesota", city: "Minneapolis" },
|
|
218
|
+
{ country: "US", region: "Michigan", city: "Detroit" },
|
|
219
|
+
{ country: "US", region: "Maryland", city: "Baltimore" },
|
|
220
|
+
{ country: "US", region: "Iowa", city: "Des Moines" },
|
|
186
221
|
{ country: "CA", region: "Ontario", city: "Toronto" },
|
|
187
|
-
{ country: "
|
|
222
|
+
{ country: "CA", region: "Quebec", city: "Montreal" },
|
|
223
|
+
{ country: "CA", region: "British Columbia", city: "Vancouver" },
|
|
224
|
+
{ country: "GB", region: "England", city: "London" },
|
|
225
|
+
{ country: "DE", region: "Saxony", city: "Dresden" },
|
|
226
|
+
{ country: "DE", region: "Hesse", city: "Frankfurt" },
|
|
227
|
+
{ country: "DE", region: "Lower Saxony", city: "Hanover" },
|
|
228
|
+
{ country: "DE", region: "State of Berlin", city: "Berlin" },
|
|
188
229
|
{ country: "FR", region: "Ile-de-France", city: "Paris" },
|
|
189
|
-
{ country: "
|
|
230
|
+
{ country: "FR", region: "Brittany", city: "Rennes" },
|
|
231
|
+
{ country: "FR", region: "Grand Est", city: "Strasbourg" },
|
|
232
|
+
{ country: "NL", region: "North Holland", city: "Amsterdam" },
|
|
233
|
+
{ country: "NL", region: "Overijssel", city: "Zwolle" },
|
|
234
|
+
{ country: "BE", region: "Brussels Capital", city: "Brussels" },
|
|
235
|
+
{ country: "PL", region: "Mazovia", city: "Warsaw" },
|
|
236
|
+
{ country: "CH", region: "Ticino", city: "Lugano" },
|
|
237
|
+
{ country: "SE", region: "Stockholm", city: "Stockholm" },
|
|
238
|
+
{ country: "FI", region: "Uusimaa", city: "Helsinki" },
|
|
239
|
+
{ country: "DK", region: "Capital Region", city: "Copenhagen" },
|
|
240
|
+
{ country: "PT", region: "Lisbon", city: "Lisbon" },
|
|
241
|
+
{ country: "IT", region: "Lombardy", city: "Milan" },
|
|
242
|
+
{ country: "IT", region: "Sicily", city: "Palermo" },
|
|
243
|
+
{ country: "IE", region: "Leinster", city: "Dublin" },
|
|
244
|
+
{ country: "ES", region: "Madrid", city: "Madrid" },
|
|
245
|
+
{ country: "CN", region: "Beijing", city: "Beijing" },
|
|
246
|
+
{ country: "CN", region: "Shanghai", city: "Shanghai" },
|
|
247
|
+
{ country: "CN", region: "Hebei", city: "Shijiazhuang" },
|
|
248
|
+
{ country: "CN", region: "Fujian", city: "Fuzhou" },
|
|
249
|
+
{ country: "CN", region: "Tianjin", city: "Tianjin" },
|
|
250
|
+
{ country: "CN", region: "Xinjiang", city: "Urumqi" },
|
|
251
|
+
{ country: "IN", region: "Telangana", city: "Hyderabad" },
|
|
252
|
+
{ country: "IN", region: "Punjab", city: "Ludhiana" },
|
|
253
|
+
{ country: "IN", region: "Maharashtra", city: "Mumbai" },
|
|
254
|
+
{ country: "IN", region: "Tamil Nadu", city: "Chennai" },
|
|
255
|
+
{ country: "IN", region: "Madhya Pradesh", city: "Bhopal" },
|
|
256
|
+
{ country: "IN", region: "Uttarakhand", city: "Dehradun" },
|
|
257
|
+
{ country: "VN", region: "Vinh Long", city: "Vinh Long" },
|
|
258
|
+
{ country: "VN", region: "Thanh Hoa Province", city: "Thanh Hoa" },
|
|
259
|
+
{ country: "VN", region: "Ninh Binh", city: "Ninh Binh" },
|
|
260
|
+
{ country: "VN", region: "Quang Tri", city: "Dong Ha" },
|
|
261
|
+
{ country: "BD", region: "Chittagong", city: "Chittagong" },
|
|
262
|
+
{ country: "BD", region: "Sylhet Division", city: "Sylhet" },
|
|
263
|
+
{ country: "BD", region: "Rajshahi Division", city: "Rajshahi" },
|
|
264
|
+
{ country: "KR", region: "Seoul", city: "Seoul" },
|
|
265
|
+
{ country: "KR", region: "Gyeonggi-do", city: "Suwon" },
|
|
190
266
|
{ country: "JP", region: "Tokyo", city: "Tokyo" },
|
|
267
|
+
{ country: "TW", region: "Taiwan", city: "Taipei" },
|
|
268
|
+
{ country: "SG", region: "Singapore", city: "Singapore" },
|
|
269
|
+
{ country: "MY", region: "Selangor", city: "Shah Alam" },
|
|
270
|
+
{ country: "ID", region: "North Sumatra", city: "Medan" },
|
|
271
|
+
{ country: "PH", region: "Metro Manila", city: "Manila" },
|
|
272
|
+
{ country: "TH", region: "Bangkok", city: "Bangkok" },
|
|
273
|
+
{ country: "AU", region: "New South Wales", city: "Sydney" },
|
|
274
|
+
{ country: "AU", region: "Victoria", city: "Melbourne" },
|
|
275
|
+
{ country: "NZ", region: "Auckland", city: "Auckland" },
|
|
276
|
+
{ country: "AQ", region: "Ross Dependency", city: "McMurdo Station" },
|
|
191
277
|
{ country: "BR", region: "Sao Paulo", city: "Sao Paulo" },
|
|
278
|
+
{ country: "BR", region: "Rio de Janeiro", city: "Rio de Janeiro" },
|
|
279
|
+
{ country: "BR", region: "Rio Grande do Sul", city: "Porto Alegre" },
|
|
280
|
+
{ country: "BR", region: "Minas Gerais", city: "Belo Horizonte" },
|
|
281
|
+
{ country: "BR", region: "Goias", city: "Goiania" },
|
|
282
|
+
{ country: "BR", region: "Paraiba", city: "Joao Pessoa" },
|
|
283
|
+
{ country: "MX", region: "Nuevo Leon", city: "Monterrey" },
|
|
284
|
+
{ country: "MX", region: "Puebla", city: "Puebla" },
|
|
285
|
+
{ country: "MX", region: "Queretaro", city: "Queretaro" },
|
|
286
|
+
{ country: "MX", region: "Veracruz", city: "Veracruz" },
|
|
287
|
+
{ country: "MX", region: "Mexico", city: "Mexico City" },
|
|
288
|
+
{ country: "CO", region: "Bogota D.C.", city: "Bogota" },
|
|
289
|
+
{ country: "AR", region: "Buenos Aires F.D.", city: "Buenos Aires" },
|
|
290
|
+
{ country: "EC", region: "Pichincha", city: "Quito" },
|
|
291
|
+
{ country: "PA", region: "Panama", city: "Panama City" },
|
|
292
|
+
{ country: "JM", region: "Kingston", city: "Kingston" },
|
|
293
|
+
{ country: "TT", region: "Port of Spain", city: "Port of Spain" },
|
|
294
|
+
{ country: "DO", region: "Distrito Nacional", city: "Santo Domingo" },
|
|
295
|
+
{ country: "PR", region: "San Juan", city: "San Juan" },
|
|
296
|
+
{ country: "BS", region: "New Providence", city: "Nassau" },
|
|
297
|
+
{ country: "BB", region: "Saint Michael", city: "Bridgetown" },
|
|
298
|
+
{ country: "CU", region: "La Habana", city: "Havana" },
|
|
299
|
+
{ country: "VE", region: "Zulia", city: "Maracaibo" },
|
|
300
|
+
{ country: "ZA", region: "Western Cape", city: "Cape Town" },
|
|
301
|
+
{ country: "ZA", region: "Gauteng", city: "Johannesburg" },
|
|
302
|
+
{ country: "ZA", region: "KwaZulu-Natal", city: "Durban" },
|
|
303
|
+
{ country: "SA", region: "Riyadh Region", city: "Riyadh" },
|
|
304
|
+
{ country: "SA", region: "Mecca Region", city: "Jeddah" },
|
|
305
|
+
{ country: "IL", region: "Tel Aviv", city: "Tel Aviv" },
|
|
306
|
+
{ country: "JO", region: "Amman", city: "Amman" },
|
|
307
|
+
{ country: "TR", region: "Istanbul", city: "Istanbul" },
|
|
308
|
+
{ country: "TN", region: "Tunis Governorate", city: "Tunis" },
|
|
309
|
+
{ country: "DZ", region: "Oran", city: "Oran" },
|
|
310
|
+
{ country: "DZ", region: "M'Sila", city: "M'Sila" },
|
|
311
|
+
{ country: "IQ", region: "Nineveh", city: "Mosul" },
|
|
312
|
+
{ country: "LB", region: "Liban-Nord", city: "Tripoli" },
|
|
313
|
+
{ country: "KG", region: "Bishkek", city: "Bishkek" },
|
|
314
|
+
{ country: "UZ", region: "Tashkent", city: "Tashkent" },
|
|
315
|
+
{ country: "KE", region: "Meru County", city: "Meru" },
|
|
316
|
+
{ country: "KE", region: "Nairobi County", city: "Nairobi" },
|
|
317
|
+
{ country: "KE", region: "Mombasa County", city: "Mombasa" },
|
|
318
|
+
{ country: "NG", region: "Lagos", city: "Lagos" },
|
|
319
|
+
{ country: "NG", region: "Federal Capital Territory", city: "Abuja" },
|
|
320
|
+
{ country: "NG", region: "Rivers", city: "Port Harcourt" },
|
|
321
|
+
{ country: "GH", region: "Greater Accra", city: "Accra" },
|
|
322
|
+
{ country: "GH", region: "Ashanti", city: "Kumasi" },
|
|
323
|
+
{ country: "AE", region: "Dubai", city: "Dubai" },
|
|
324
|
+
{ country: "RO", region: "Bucharest", city: "Bucharest" },
|
|
325
|
+
{ country: "LT", region: "Vilnius", city: "Vilnius" },
|
|
326
|
+
{ country: "AZ", region: "Baku", city: "Baku" },
|
|
327
|
+
{ country: "AL", region: "Vlore County", city: "Vlore" },
|
|
328
|
+
{ country: "GT", region: "Guatemala", city: "Guatemala City" },
|
|
329
|
+
{ country: "EG", region: "Cairo", city: "Cairo" },
|
|
192
330
|
];
|
|
193
331
|
|
|
194
332
|
const sampleEvents = ["page_view", "form_fill", "phone_call"];
|
|
@@ -342,6 +480,9 @@ async function seedData() {
|
|
|
342
480
|
console.log(`📈 Events to generate: ${numEvents}`);
|
|
343
481
|
} else {
|
|
344
482
|
console.log(`🌐 Sites to create: ${numSites}`);
|
|
483
|
+
if (customSiteNames) {
|
|
484
|
+
console.log(`🏷️ Custom site names: ${customSiteNames.join(", ")}`);
|
|
485
|
+
}
|
|
345
486
|
console.log(`📈 Events per site: ${numEvents}`);
|
|
346
487
|
}
|
|
347
488
|
console.log(`📅 Days back: ${numDays}`);
|
|
@@ -367,7 +508,7 @@ async function seedData() {
|
|
|
367
508
|
} else {
|
|
368
509
|
// Create sample sites
|
|
369
510
|
for (let i = 0; i < numSites; i++) {
|
|
370
|
-
const name = sampleSiteNames[i % sampleSiteNames.length];
|
|
511
|
+
const name = customSiteNames?.[i] || sampleSiteNames[i % sampleSiteNames.length];
|
|
371
512
|
const domain = sampleDomains[i % sampleDomains.length];
|
|
372
513
|
|
|
373
514
|
console.log(`📡 Creating site: ${name}...`);
|
|
@@ -455,5 +596,20 @@ if (siteId && numSites !== 3) {
|
|
|
455
596
|
console.log("ℹ️ Note: --sites parameter is ignored when using --site-id");
|
|
456
597
|
}
|
|
457
598
|
|
|
599
|
+
if (siteId && customSiteNames) {
|
|
600
|
+
console.log("ℹ️ Note: --site-names parameter is ignored when using --site-id");
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (!siteId && customSiteNames && customSiteNames.length < numSites) {
|
|
604
|
+
console.error(
|
|
605
|
+
`❌ Error: --site-names provided ${customSiteNames.length} name(s), but --sites is ${numSites}. Provide at least ${numSites} names.`,
|
|
606
|
+
);
|
|
607
|
+
process.exit(1);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (!siteId && customSiteNames && customSiteNames.length > numSites) {
|
|
611
|
+
console.log(`ℹ️ Note: --site-names provided ${customSiteNames.length} names; only the first ${numSites} will be used.`);
|
|
612
|
+
}
|
|
613
|
+
|
|
458
614
|
// Run the seeding
|
|
459
615
|
seedData();
|
package/db/tranformReports.ts
CHANGED
|
@@ -1,18 +1,107 @@
|
|
|
1
|
-
|
|
1
|
+
export type Pagination = {
|
|
2
|
+
limit: number;
|
|
3
|
+
offset: number;
|
|
4
|
+
total: number;
|
|
5
|
+
hasMore: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type DashboardEventRow = {
|
|
9
|
+
page_url: string | null;
|
|
10
|
+
client_page_url: string | null;
|
|
11
|
+
referer: string | null;
|
|
12
|
+
event: string | null;
|
|
13
|
+
createdAt: Date | null;
|
|
14
|
+
operating_system: string | null;
|
|
15
|
+
browser: string | null;
|
|
16
|
+
country: string | null;
|
|
17
|
+
region: string | null;
|
|
18
|
+
rid: string | null;
|
|
19
|
+
city: string | null;
|
|
20
|
+
postal: string | null;
|
|
21
|
+
screen_width: number | null;
|
|
22
|
+
screen_height: number | null;
|
|
23
|
+
device_type: string | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type DasboardDataResult = DashboardEventRow[];
|
|
27
|
+
|
|
28
|
+
type NivoDatumValue = string | number;
|
|
29
|
+
|
|
30
|
+
type NivoAxisLegendPosition = "start" | "middle" | "end";
|
|
31
|
+
|
|
32
|
+
type NivoLegendAnchor =
|
|
33
|
+
| "top"
|
|
34
|
+
| "top-right"
|
|
35
|
+
| "right"
|
|
36
|
+
| "bottom-right"
|
|
37
|
+
| "bottom"
|
|
38
|
+
| "bottom-left"
|
|
39
|
+
| "left"
|
|
40
|
+
| "top-left"
|
|
41
|
+
| "center";
|
|
42
|
+
|
|
43
|
+
type NivoLegendDirection = "row" | "column";
|
|
44
|
+
|
|
45
|
+
type NivoLegendItemDirection =
|
|
46
|
+
| "left-to-right"
|
|
47
|
+
| "right-to-left"
|
|
48
|
+
| "top-to-bottom"
|
|
49
|
+
| "bottom-to-top";
|
|
50
|
+
|
|
51
|
+
type NivoLegendEffect = {
|
|
52
|
+
on: "hover";
|
|
53
|
+
style: {
|
|
54
|
+
itemBackground?: string;
|
|
55
|
+
itemOpacity?: number;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export interface NivoAxisConfig {
|
|
60
|
+
tickSize?: number;
|
|
61
|
+
tickPadding?: number;
|
|
62
|
+
tickRotation?: number;
|
|
63
|
+
truncateTickAt?: number;
|
|
64
|
+
legend?: string;
|
|
65
|
+
legendOffset?: number;
|
|
66
|
+
legendPosition?: NivoAxisLegendPosition;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type NivoLegendBase = {
|
|
70
|
+
anchor: NivoLegendAnchor;
|
|
71
|
+
direction: NivoLegendDirection;
|
|
72
|
+
justify: boolean;
|
|
73
|
+
translateX: number;
|
|
74
|
+
translateY: number;
|
|
75
|
+
itemWidth: number;
|
|
76
|
+
itemHeight: number;
|
|
77
|
+
itemTextColor?: string;
|
|
78
|
+
itemDirection: NivoLegendItemDirection;
|
|
79
|
+
itemsSpacing: number;
|
|
80
|
+
itemOpacity: number;
|
|
81
|
+
symbolSize: number;
|
|
82
|
+
symbolShape?: "circle" | "diamond" | "square" | "triangle";
|
|
83
|
+
effects?: NivoLegendEffect[];
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type NivoBarLegendConfig = NivoLegendBase & {
|
|
87
|
+
dataFrom: "keys";
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type NivoPieLegendConfig = NivoLegendBase;
|
|
2
91
|
|
|
3
92
|
export interface NivoBarChartData {
|
|
4
|
-
data: Record<string,
|
|
93
|
+
data: Array<Record<string, NivoDatumValue>>;
|
|
5
94
|
keys: string[];
|
|
6
95
|
indexBy: string;
|
|
7
|
-
axisBottom?:
|
|
8
|
-
axisLeft?:
|
|
9
|
-
legends?:
|
|
96
|
+
axisBottom?: NivoAxisConfig;
|
|
97
|
+
axisLeft?: NivoAxisConfig;
|
|
98
|
+
legends?: NivoBarLegendConfig[];
|
|
10
99
|
options: { chart: { type: "bar" } }; // Mandatory type
|
|
11
100
|
}
|
|
12
101
|
|
|
13
102
|
export interface NivoPieChartData {
|
|
14
103
|
data: { id: string | number; value: number }[];
|
|
15
|
-
legends?:
|
|
104
|
+
legends?: NivoPieLegendConfig[];
|
|
16
105
|
options: { chart: { type: "pie" } }; // Mandatory type
|
|
17
106
|
}
|
|
18
107
|
|
|
@@ -21,10 +110,10 @@ export interface NivoLineChartData {
|
|
|
21
110
|
id: string | number;
|
|
22
111
|
data: { x: string | number; y: string | number }[];
|
|
23
112
|
}[];
|
|
24
|
-
legends?:
|
|
113
|
+
legends?: NivoPieLegendConfig[]; // Optional
|
|
25
114
|
options: { chart: { type: "line" } }; // Mandatory type
|
|
26
|
-
axisBottom?:
|
|
27
|
-
axisLeft?:
|
|
115
|
+
axisBottom?: NivoAxisConfig; // Optional, can be customized per chart
|
|
116
|
+
axisLeft?: NivoAxisConfig; // Optional
|
|
28
117
|
// Add other line-specific Nivo props if they need to be dynamic from data source
|
|
29
118
|
}
|
|
30
119
|
|
package/index.d.ts
CHANGED
|
@@ -57,6 +57,19 @@ export type {
|
|
|
57
57
|
LytxAiModelPreset,
|
|
58
58
|
LytxAiModel,
|
|
59
59
|
} from "./src/config/createLytxAppConfig";
|
|
60
|
+
export type {
|
|
61
|
+
LytxAdditionalRoute,
|
|
62
|
+
LytxDocumentComponent,
|
|
63
|
+
LytxDashboardReportData,
|
|
64
|
+
LytxDashboardRouteUiOverrideArgs,
|
|
65
|
+
LytxEventsRouteUiOverrideArgs,
|
|
66
|
+
LytxExploreRouteUiOverrideArgs,
|
|
67
|
+
LytxRouteOverrideResult,
|
|
68
|
+
LytxRouteRequestInfo,
|
|
69
|
+
LytxRouteUiOverrides,
|
|
70
|
+
LytxRoutesConfig,
|
|
71
|
+
LytxToolbarSiteOption,
|
|
72
|
+
} from "./src/config/routeUiOverrides";
|
|
60
73
|
export function createLytxApp(
|
|
61
74
|
config?: CreateLytxAppConfig,
|
|
62
75
|
): ReturnType<typeof import("./src/worker").createLytxApp>;
|
package/index.ts
CHANGED
|
@@ -79,6 +79,19 @@ export type {
|
|
|
79
79
|
LytxAiModelPreset,
|
|
80
80
|
LytxAiModel,
|
|
81
81
|
} from "./src/config/createLytxAppConfig";
|
|
82
|
+
export type {
|
|
83
|
+
LytxAdditionalRoute,
|
|
84
|
+
LytxDocumentComponent,
|
|
85
|
+
LytxDashboardReportData,
|
|
86
|
+
LytxDashboardRouteUiOverrideArgs,
|
|
87
|
+
LytxEventsRouteUiOverrideArgs,
|
|
88
|
+
LytxExploreRouteUiOverrideArgs,
|
|
89
|
+
LytxRouteOverrideResult,
|
|
90
|
+
LytxRouteRequestInfo,
|
|
91
|
+
LytxRouteUiOverrides,
|
|
92
|
+
LytxRoutesConfig,
|
|
93
|
+
LytxToolbarSiteOption,
|
|
94
|
+
} from "./src/config/routeUiOverrides";
|
|
82
95
|
export { DEFAULT_LYTX_RESOURCE_NAMES, resolveLytxResourceNames } from "./src/config/resourceNames";
|
|
83
96
|
export type { LytxResourceNames, LytxResourceNamingOptions, LytxResourceStagePosition } from "./src/config/resourceNames";
|
|
84
97
|
|
package/lib/auth.ts
CHANGED
|
@@ -18,7 +18,7 @@ type SocialProviderToggles = {
|
|
|
18
18
|
github?: boolean;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
export const SIGNUP_MODES = ["open", "bootstrap_then_invite", "invite_only"] as const;
|
|
21
|
+
export const SIGNUP_MODES = ["open", "bootstrap_then_invite", "invite_only", "demo"] as const;
|
|
22
22
|
export type SignupMode = (typeof SIGNUP_MODES)[number];
|
|
23
23
|
|
|
24
24
|
export type SignupAccessState = {
|
|
@@ -122,7 +122,7 @@ export function getAuthProviderAvailability(): AuthProviderAvailability {
|
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
function getSignupMode(): SignupMode {
|
|
125
|
+
export function getSignupMode(): SignupMode {
|
|
126
126
|
return auth_runtime_config.signupMode ?? "bootstrap_then_invite";
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -173,7 +173,7 @@ async function hasPendingInviteForEmail(email: string): Promise<boolean> {
|
|
|
173
173
|
export async function getSignupAccessState(): Promise<SignupAccessState> {
|
|
174
174
|
const mode = getSignupMode();
|
|
175
175
|
|
|
176
|
-
if (mode === "open") {
|
|
176
|
+
if (mode === "open" || mode === "demo") {
|
|
177
177
|
return {
|
|
178
178
|
mode,
|
|
179
179
|
hasUsers: true,
|
|
@@ -207,7 +207,7 @@ export async function isPublicSignupOpen(): Promise<boolean> {
|
|
|
207
207
|
|
|
208
208
|
export async function canRegisterEmail(email: string): Promise<boolean> {
|
|
209
209
|
const signupMode = getSignupMode();
|
|
210
|
-
if (signupMode === "open") return true;
|
|
210
|
+
if (signupMode === "open" || signupMode === "demo") return true;
|
|
211
211
|
|
|
212
212
|
const invited = await hasPendingInviteForEmail(email);
|
|
213
213
|
if (invited) return true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lytx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -63,6 +63,12 @@
|
|
|
63
63
|
"import": "./src/config/resourceNames.ts",
|
|
64
64
|
"default": "./src/config/resourceNames.ts"
|
|
65
65
|
},
|
|
66
|
+
"./vite": {
|
|
67
|
+
"types": "./vite/index.ts",
|
|
68
|
+
"import": "./vite/index.ts",
|
|
69
|
+
"default": "./vite/index.ts"
|
|
70
|
+
},
|
|
71
|
+
"./styles.css": "./src/index.css",
|
|
66
72
|
"./package.json": "./package.json"
|
|
67
73
|
},
|
|
68
74
|
"bin": {
|
package/src/Document.tsx
CHANGED
|
@@ -79,7 +79,7 @@ export const Document: React.FC<{ children: React.ReactNode }> = ({
|
|
|
79
79
|
<link rel="modulepreload" href="/src/client.tsx" />
|
|
80
80
|
</head>
|
|
81
81
|
<body className="overflow-x-hidden">
|
|
82
|
-
|
|
82
|
+
{children}
|
|
83
83
|
<script>import("/src/client.tsx")</script>
|
|
84
84
|
</body>
|
|
85
85
|
</html>
|
|
@@ -39,7 +39,7 @@ export async function sessionMiddleware({ request, ctx }: RequestInfo<unknown, A
|
|
|
39
39
|
export function getSiteFromContext(ctx: AppContext, site_id: number) {
|
|
40
40
|
if (!ctx.session) return null;
|
|
41
41
|
if (!ctx.sites) return null;
|
|
42
|
-
return ctx.sites.find(
|
|
42
|
+
return ctx.sites.find((site) => site.site_id == site_id);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export const sessionName = 'lytx_session';
|