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
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { AuthContext } from "@/app/providers/AuthProvider";
|
|
3
|
+
|
|
4
|
+
const LAST_SITE_KEY = "lytx_last_site_id";
|
|
5
|
+
|
|
6
|
+
/** Save to localStorage for immediate access on next page load */
|
|
7
|
+
function saveLastSiteToStorage(site_id: number): void {
|
|
8
|
+
try {
|
|
9
|
+
localStorage.setItem(LAST_SITE_KEY, String(site_id));
|
|
10
|
+
} catch {
|
|
11
|
+
// localStorage might not be available
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Get last site from localStorage */
|
|
16
|
+
export function getLastSiteFromStorage(): number | null {
|
|
17
|
+
try {
|
|
18
|
+
const stored = localStorage.getItem(LAST_SITE_KEY);
|
|
19
|
+
return stored ? parseInt(stored, 10) : null;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Persist to database for cross-device sync */
|
|
26
|
+
async function updateLastSiteInDB(site_id: number): Promise<void> {
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch("/api/user/update-last-site", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "Content-Type": "application/json" },
|
|
31
|
+
body: JSON.stringify({ site_id }),
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
console.error("Failed to update last site:", await response.text());
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error("Error updating last site:", error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type SiteSelectorProps = {
|
|
42
|
+
callBack?: (opts: { name: string; id: number; tag_id: string }) => void;
|
|
43
|
+
initialSites?: Array<{ site_id: number; name: string; tag_id: string }>;
|
|
44
|
+
initialSiteId?: number | null;
|
|
45
|
+
wrapperClassName?: string;
|
|
46
|
+
selectClassName?: string;
|
|
47
|
+
};
|
|
48
|
+
export const SiteSelector: React.FC<SiteSelectorProps> = ({
|
|
49
|
+
initialSites = [],
|
|
50
|
+
initialSiteId = null,
|
|
51
|
+
wrapperClassName,
|
|
52
|
+
selectClassName,
|
|
53
|
+
}) => {
|
|
54
|
+
const {
|
|
55
|
+
data: session,
|
|
56
|
+
current_site,
|
|
57
|
+
setCurrentSite,
|
|
58
|
+
} = useContext(AuthContext);
|
|
59
|
+
|
|
60
|
+
const sites = session?.userSites && session.userSites.length > 0
|
|
61
|
+
? session.userSites.map((site) => ({
|
|
62
|
+
site_id: site.site_id,
|
|
63
|
+
name: site.name || `Site ${site.site_id}`,
|
|
64
|
+
tag_id: site.tag_id,
|
|
65
|
+
}))
|
|
66
|
+
: initialSites;
|
|
67
|
+
|
|
68
|
+
const selectedSiteName = current_site?.name
|
|
69
|
+
?? sites.find((site) => site.site_id === initialSiteId)?.name
|
|
70
|
+
?? sites[0]?.name
|
|
71
|
+
?? "";
|
|
72
|
+
|
|
73
|
+
const combinedWrapperClassName = ["relative inline-block", wrapperClassName]
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.join(" ");
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className={combinedWrapperClassName}>
|
|
79
|
+
<select
|
|
80
|
+
value={selectedSiteName}
|
|
81
|
+
onChange={(e) => {
|
|
82
|
+
const selectedSite = sites.find(
|
|
83
|
+
(site) => site.name === e.target.value,
|
|
84
|
+
);
|
|
85
|
+
if (selectedSite) {
|
|
86
|
+
setCurrentSite({
|
|
87
|
+
name: selectedSite.name,
|
|
88
|
+
id: selectedSite.site_id,
|
|
89
|
+
tag_id: selectedSite.tag_id,
|
|
90
|
+
});
|
|
91
|
+
// Save to localStorage for immediate access on page reload
|
|
92
|
+
saveLastSiteToStorage(selectedSite.site_id);
|
|
93
|
+
// Persist to database for cross-device sync (fire and forget)
|
|
94
|
+
updateLastSiteInDB(selectedSite.site_id);
|
|
95
|
+
}
|
|
96
|
+
}}
|
|
97
|
+
className={`appearance-none bg-[var(--theme-input-bg)] pl-4 pr-10 py-2 text-sm text-left text-[var(--theme-text-primary)] rounded-lg border border-[var(--theme-input-border)] focus:border-[var(--theme-border-primary)] focus:outline-none transition-colors ${selectClassName ?? ""}`}
|
|
98
|
+
>
|
|
99
|
+
{sites.length > 0
|
|
100
|
+
? sites.map((site) => (
|
|
101
|
+
<option key={site.site_id} value={site.name} className="text-left">
|
|
102
|
+
{site.name}
|
|
103
|
+
</option>
|
|
104
|
+
))
|
|
105
|
+
: ""}
|
|
106
|
+
</select>
|
|
107
|
+
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-[var(--theme-text-secondary)]">
|
|
108
|
+
<svg
|
|
109
|
+
aria-hidden="true"
|
|
110
|
+
className="h-4 w-4"
|
|
111
|
+
viewBox="0 0 20 20"
|
|
112
|
+
fill="none"
|
|
113
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
114
|
+
>
|
|
115
|
+
<path
|
|
116
|
+
d="M5 7.5L10 12.5L15 7.5"
|
|
117
|
+
stroke="currentColor"
|
|
118
|
+
strokeWidth="1.8"
|
|
119
|
+
strokeLinecap="round"
|
|
120
|
+
strokeLinejoin="round"
|
|
121
|
+
/>
|
|
122
|
+
</svg>
|
|
123
|
+
</span>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { LYTX_SCRIPT_PATH } from "@/app/constants";
|
|
2
|
+
|
|
3
|
+
export type SiteTagProps = {
|
|
4
|
+
tag_id: string;
|
|
5
|
+
domain: string;
|
|
6
|
+
lytx_domain?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const SiteTag: React.FC<SiteTagProps> = ({
|
|
10
|
+
tag_id,
|
|
11
|
+
domain,
|
|
12
|
+
lytx_domain,
|
|
13
|
+
}) => {
|
|
14
|
+
// Infer the Lytx domain from the current host if not provided
|
|
15
|
+
if (!lytx_domain) {
|
|
16
|
+
lytx_domain = typeof window !== "undefined"
|
|
17
|
+
? window.location.host
|
|
18
|
+
: "localhost:5173";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<code className="text-sm">
|
|
23
|
+
<span style={{ color: "#6c7086" }}><</span>
|
|
24
|
+
<span style={{ color: "#f38ba8", fontWeight: "600" }}>script</span>
|
|
25
|
+
<span style={{ color: "#6c7086" }}> </span>
|
|
26
|
+
<span style={{ color: "#a6e3a1" }}>defer</span>
|
|
27
|
+
<span style={{ color: "#6c7086" }}> </span>
|
|
28
|
+
<span style={{ color: "#a6e3a1" }}>data-domain</span>
|
|
29
|
+
<span style={{ color: "#6c7086" }}>=</span>
|
|
30
|
+
<span style={{ color: "#fab387" }}>"{domain}"</span>
|
|
31
|
+
<span style={{ color: "#6c7086" }}> </span>
|
|
32
|
+
<span style={{ color: "#a6e3a1" }}>src</span>
|
|
33
|
+
<span style={{ color: "#6c7086" }}>=</span>
|
|
34
|
+
<span style={{ color: "#fab387" }}>
|
|
35
|
+
"https://{lytx_domain}{LYTX_SCRIPT_PATH}?account={tag_id}"
|
|
36
|
+
</span>
|
|
37
|
+
<span style={{ color: "#6c7086" }}>></</span>
|
|
38
|
+
<span style={{ color: "#f38ba8", fontWeight: "600" }}>script</span>
|
|
39
|
+
<span style={{ color: "#6c7086" }}>></span>
|
|
40
|
+
</code>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Highlight, themes } from "prism-react-renderer";
|
|
5
|
+
import { Button } from "@/app/components/ui/Button";
|
|
6
|
+
import { LYTX_SCRIPT_PATH } from "@/app/constants";
|
|
7
|
+
|
|
8
|
+
type SiteTagInstallCardProps = {
|
|
9
|
+
site: {
|
|
10
|
+
site_id?: number;
|
|
11
|
+
name?: string | null;
|
|
12
|
+
domain?: string | null;
|
|
13
|
+
tag_id: string;
|
|
14
|
+
createdAt?: string | Date | null;
|
|
15
|
+
};
|
|
16
|
+
className?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function CodeBlock({
|
|
20
|
+
code,
|
|
21
|
+
language,
|
|
22
|
+
id,
|
|
23
|
+
}: {
|
|
24
|
+
code: string;
|
|
25
|
+
language: string;
|
|
26
|
+
id: string;
|
|
27
|
+
}) {
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
id={id}
|
|
31
|
+
className="bg-[var(--theme-input-bg)] border border-[var(--theme-input-border)] rounded-md overflow-hidden"
|
|
32
|
+
>
|
|
33
|
+
<Highlight theme={themes.vsDark} code={code} language={language}>
|
|
34
|
+
{({ className: highlightClassName, style, tokens, getLineProps, getTokenProps }) => (
|
|
35
|
+
<pre
|
|
36
|
+
className={`${highlightClassName} p-4 text-sm overflow-x-auto`}
|
|
37
|
+
style={style}
|
|
38
|
+
>
|
|
39
|
+
{tokens.map((line, i) => (
|
|
40
|
+
<div key={i} {...getLineProps({ line })}>
|
|
41
|
+
{line.map((token, key) => (
|
|
42
|
+
<span key={key} {...getTokenProps({ token })} />
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
))}
|
|
46
|
+
</pre>
|
|
47
|
+
)}
|
|
48
|
+
</Highlight>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function SiteTagInstallCard({ site, className }: SiteTagInstallCardProps) {
|
|
54
|
+
const [activeInstallTab, setActiveInstallTab] = useState("html");
|
|
55
|
+
const [lytxDomain, setLytxDomain] = useState("https://lytx.io");
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
setLytxDomain(window.location.origin);
|
|
59
|
+
}, []);
|
|
60
|
+
const containerClassName = className
|
|
61
|
+
? `border border-[var(--theme-border-primary)] rounded-lg bg-[var(--theme-bg-secondary)] p-4 sm:p-6 ${className}`
|
|
62
|
+
: "border border-[var(--theme-border-primary)] rounded-lg bg-[var(--theme-bg-secondary)] p-4 sm:p-6";
|
|
63
|
+
|
|
64
|
+
const getCodeForTab = (tab: string) => {
|
|
65
|
+
switch (tab) {
|
|
66
|
+
case "html":
|
|
67
|
+
return `<script defer \n src="${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}"\n></script>`;
|
|
68
|
+
case "rwsdk":
|
|
69
|
+
return `// In your Document.tsx\nexport const Document = ({ children }) => (\n <html>\n <head>{/* ... */}</head>\n <body>\n <div id="root">{children}</div>\n <script>import("/src/client.tsx")</script>\n <script\n defer\n src="${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}"\n ></script>\n </body>\n </html>\n);`;
|
|
70
|
+
case "sveltekit":
|
|
71
|
+
return `<!-- In your src/app.html -->\n<script\n defer\n src="${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}"\n></script>\n\n<!-- Or in your +layout.svelte -->\n<svelte:head>\n <script\n defer\n src="${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}"\n ></script>\n</svelte:head>`;
|
|
72
|
+
case "solid":
|
|
73
|
+
return `// In your root.tsx or index.html\nimport { Script } from "@solidjs/meta";\n\nexport default function Root() {\n return (\n <Html>\n <Head>\n <Script\n defer\n src="${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}"\n />\n </Head>\n <Body>\n {/* Your app content */}\n </Body>\n </Html>\n );\n}`;
|
|
74
|
+
case "nextjs":
|
|
75
|
+
return `// In your _document.tsx or layout.tsx (App Router)\nimport Script from 'next/script';\n\nexport default function RootLayout({ children }) {\n return (\n <html>\n <head>\n <Script\n defer\n src="${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}"\n strategy="beforeInteractive"\n />\n </head>\n <body>{children}</body>\n </html>\n );\n}`;
|
|
76
|
+
case "nuxt":
|
|
77
|
+
return `// In your nuxt.config.ts\nexport default defineNuxtConfig({\n app: {\n head: {\n script: [\n {\n src: '${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}',\n defer: true\n }\n ]\n }\n }\n});\n\n// Or use useHead() in a component/page\nuseHead({\n script: [\n {\n src: '${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}',\n defer: true\n }\n ]\n});`;
|
|
78
|
+
default:
|
|
79
|
+
return "";
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div className={containerClassName}>
|
|
85
|
+
<div className="mb-4 flex items-start justify-between">
|
|
86
|
+
<div className="flex-1">
|
|
87
|
+
<div className="mb-2 flex flex-wrap items-center gap-2 sm:gap-3">
|
|
88
|
+
<h3 className="text-lg font-medium text-[var(--theme-text-primary)]">
|
|
89
|
+
{site.name || "Selected Site"}
|
|
90
|
+
</h3>
|
|
91
|
+
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
|
|
92
|
+
active
|
|
93
|
+
</span>
|
|
94
|
+
</div>
|
|
95
|
+
<div className="text-sm text-[var(--theme-text-secondary)] space-y-1">
|
|
96
|
+
<p>
|
|
97
|
+
<span className="font-medium">Domain:</span>{" "}
|
|
98
|
+
{site.domain || "—"}
|
|
99
|
+
</p>
|
|
100
|
+
<p>
|
|
101
|
+
<span className="font-medium">Tag ID:</span>{" "}
|
|
102
|
+
<code className="break-all rounded bg-[var(--theme-input-bg)] px-2 py-1 text-xs">
|
|
103
|
+
{site.tag_id}
|
|
104
|
+
</code>
|
|
105
|
+
</p>
|
|
106
|
+
<p>
|
|
107
|
+
<span className="font-medium">Created:</span>{" "}
|
|
108
|
+
{site.createdAt ? new Date(site.createdAt).toLocaleDateString() : "N/A"}
|
|
109
|
+
</p>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div className="border-t border-[var(--theme-border-primary)] pt-4">
|
|
115
|
+
<div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
116
|
+
<label className="text-sm font-medium text-[var(--theme-text-primary)]">
|
|
117
|
+
Installation Instructions
|
|
118
|
+
</label>
|
|
119
|
+
<Button
|
|
120
|
+
variant="primary"
|
|
121
|
+
size="sm"
|
|
122
|
+
className="w-full sm:w-auto"
|
|
123
|
+
onClick={() => {
|
|
124
|
+
const code = getCodeForTab(activeInstallTab);
|
|
125
|
+
if (code) {
|
|
126
|
+
navigator.clipboard.writeText(code);
|
|
127
|
+
alert("Code copied to clipboard!");
|
|
128
|
+
}
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
Copy Code
|
|
132
|
+
</Button>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<div className="mb-4 overflow-x-auto rounded-lg bg-[var(--theme-bg-secondary)] p-1">
|
|
136
|
+
<div className="flex min-w-max space-x-1">
|
|
137
|
+
{[
|
|
138
|
+
{ id: "html", label: "HTML" },
|
|
139
|
+
{ id: "rwsdk", label: "RWSDK" },
|
|
140
|
+
{ id: "sveltekit", label: "SvelteKit" },
|
|
141
|
+
{ id: "solid", label: "Solid" },
|
|
142
|
+
{ id: "nextjs", label: "Next.js" },
|
|
143
|
+
{ id: "nuxt", label: "Nuxt" },
|
|
144
|
+
].map((tab) => (
|
|
145
|
+
<button
|
|
146
|
+
key={tab.id}
|
|
147
|
+
onClick={() => setActiveInstallTab(tab.id)}
|
|
148
|
+
className={`whitespace-nowrap rounded-md px-3 py-2 text-xs font-medium transition-colors ${activeInstallTab === tab.id
|
|
149
|
+
? "bg-[var(--theme-bg-primary)] text-[var(--theme-text-primary)] shadow-sm"
|
|
150
|
+
: "text-[var(--theme-text-secondary)] hover:text-[var(--theme-text-primary)]"
|
|
151
|
+
}`}
|
|
152
|
+
>
|
|
153
|
+
{tab.label}
|
|
154
|
+
</button>
|
|
155
|
+
))}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div className="space-y-4">
|
|
160
|
+
{activeInstallTab === "html" && (
|
|
161
|
+
<div>
|
|
162
|
+
<CodeBlock
|
|
163
|
+
id="install-content-html"
|
|
164
|
+
language="html"
|
|
165
|
+
code={`<script\n defer\n src="${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}"\n></script>`}
|
|
166
|
+
/>
|
|
167
|
+
<p className="text-xs text-[var(--theme-text-secondary)] mt-2">
|
|
168
|
+
Copy and paste this script into the <head> section of your HTML.
|
|
169
|
+
</p>
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
|
|
173
|
+
{activeInstallTab === "rwsdk" && (
|
|
174
|
+
<div>
|
|
175
|
+
<CodeBlock
|
|
176
|
+
id="install-content-rwsdk"
|
|
177
|
+
language="tsx"
|
|
178
|
+
code={`// In your Document.tsx\nexport const Document = ({ children }) => (\n <html>\n <head>{/* ... */}</head>\n <body>\n <div id="root">{children}</div>\n <script>import("/src/client.tsx")</script>\n <script\n defer\n src="${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}"\n ></script>\n </body>\n </html>\n);`}
|
|
179
|
+
/>
|
|
180
|
+
<p className="text-xs text-[var(--theme-text-secondary)] mt-2">
|
|
181
|
+
Add this to your Document.tsx or root layout component.
|
|
182
|
+
</p>
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
{activeInstallTab === "sveltekit" && (
|
|
187
|
+
<div>
|
|
188
|
+
<CodeBlock
|
|
189
|
+
id="install-content-sveltekit"
|
|
190
|
+
language="html"
|
|
191
|
+
code={`<!-- In your src/app.html -->\n<script\n defer\n src="${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}"\n></script>\n\n<!-- Or in your +layout.svelte -->\n<svelte:head>\n <script\n defer\n src="${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}"\n ></script>\n</svelte:head>`}
|
|
192
|
+
/>
|
|
193
|
+
<p className="text-xs text-[var(--theme-text-secondary)] mt-2">
|
|
194
|
+
Add the script to your SvelteKit app.html or layout.
|
|
195
|
+
</p>
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
|
|
199
|
+
{activeInstallTab === "solid" && (
|
|
200
|
+
<div>
|
|
201
|
+
<CodeBlock
|
|
202
|
+
id="install-content-solid"
|
|
203
|
+
language="tsx"
|
|
204
|
+
code={`// In your root.tsx or index.html\nimport { Script } from "@solidjs/meta";\n\nexport default function Root() {\n return (\n <Html>\n <Head>\n <Script\n defer\n src="${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}"\n />\n </Head>\n <Body>\n {/* Your app content */}\n </Body>\n </Html>\n );\n}`}
|
|
205
|
+
/>
|
|
206
|
+
<p className="text-xs text-[var(--theme-text-secondary)] mt-2">
|
|
207
|
+
Place the script tag in your Solid root layout.
|
|
208
|
+
</p>
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
|
|
212
|
+
{activeInstallTab === "nextjs" && (
|
|
213
|
+
<div>
|
|
214
|
+
<CodeBlock
|
|
215
|
+
id="install-content-nextjs"
|
|
216
|
+
language="tsx"
|
|
217
|
+
code={`// In your _document.tsx or layout.tsx (App Router)\nimport Script from 'next/script';\n\nexport default function RootLayout({ children }) {\n return (\n <html>\n <head>\n <Script\n defer\n src="${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}"\n strategy="beforeInteractive"\n />\n </head>\n <body>{children}</body>\n </html>\n );\n}`}
|
|
218
|
+
/>
|
|
219
|
+
<p className="text-xs text-[var(--theme-text-secondary)] mt-2">
|
|
220
|
+
Add the script to your Next.js document or root layout.
|
|
221
|
+
</p>
|
|
222
|
+
</div>
|
|
223
|
+
)}
|
|
224
|
+
|
|
225
|
+
{activeInstallTab === "nuxt" && (
|
|
226
|
+
<div>
|
|
227
|
+
<CodeBlock
|
|
228
|
+
id="install-content-nuxt"
|
|
229
|
+
language="ts"
|
|
230
|
+
code={`// In your nuxt.config.ts\nexport default defineNuxtConfig({\n app: {\n head: {\n script: [\n {\n src: '${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}',\n defer: true\n }\n ]\n }\n }\n});\n\n// Or use useHead() in a component/page\nuseHead({\n script: [\n {\n src: '${lytxDomain}${LYTX_SCRIPT_PATH}?account=${site.tag_id}',\n defer: true\n }\n ]\n});`}
|
|
231
|
+
/>
|
|
232
|
+
<p className="text-xs text-[var(--theme-text-secondary)] mt-2">
|
|
233
|
+
Configure the script in nuxt.config.ts or useHead.
|
|
234
|
+
</p>
|
|
235
|
+
</div>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
241
|
+
}
|