feat: add gtm

This commit is contained in:
Marc Seitz
2025-07-29 17:41:14 +02:00
parent 0d2d7ea9a6
commit 175891bfb6
8 changed files with 128 additions and 63 deletions

View File

@@ -1,6 +1,5 @@
"use client";
import { Metadata } from "next";
import Link from "next/link";
import { useParams } from "next/navigation";
@@ -145,9 +144,7 @@ export default function Login() {
signIn("google", {
...(next && next.length > 0 ? { callbackUrl: next } : {}),
}).then((res) => {
if (res?.status) {
setClickedMethod(undefined);
}
setClickedMethod(undefined);
});
}}
loading={clickedMethod === "google"}
@@ -169,9 +166,7 @@ export default function Login() {
signIn("linkedin", {
...(next && next.length > 0 ? { callbackUrl: next } : {}),
}).then((res) => {
if (res?.status) {
setClickedMethod(undefined);
}
setClickedMethod(undefined);
});
}}
loading={clickedMethod === "linkedin"}
@@ -192,6 +187,8 @@ export default function Login() {
setClickedMethod("passkey");
signInWithPasskey({
tenantId: process.env.NEXT_PUBLIC_HANKO_TENANT_ID as string,
}).then(() => {
setClickedMethod(undefined);
});
}}
variant="outline"

View File

@@ -1,5 +1,7 @@
import { Metadata } from "next";
import { GTMComponent } from "@/components/gtm-component";
import LoginClient from "./page-client";
const data = {
@@ -37,5 +39,10 @@ export const metadata: Metadata = {
};
export default function LoginPage() {
return <LoginClient />;
return (
<>
<GTMComponent />
<LoginClient />
</>
);
}

View File

@@ -0,0 +1,11 @@
import { GoogleTagManager } from "@next/third-parties/google";
const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID;
export function GTMComponent() {
if (!GTM_ID) {
return null;
}
return <GoogleTagManager gtmId={GTM_ID} />;
}

20
package-lock.json generated
View File

@@ -21,6 +21,7 @@
"@github/webauthn-json": "^2.1.1",
"@jitsu/js": "^1.10.3",
"@next-auth/prisma-adapter": "^1.0.7",
"@next/third-parties": "^15.4.4",
"@pdf-lib/fontkit": "^1.1.1",
"@prisma/client": "^6.5.0",
"@radix-ui/react-accordion": "^1.2.11",
@@ -2936,6 +2937,19 @@
"node": ">= 10"
}
},
"node_modules/@next/third-parties": {
"version": "15.4.4",
"resolved": "https://registry.npmjs.org/@next/third-parties/-/third-parties-15.4.4.tgz",
"integrity": "sha512-0GIEDSxfl9hTU4Ne1TVr/lZUS1wGhDPB4gt6TNvLBA3Fioum+qZRoPt3fiOW9hHQynyLsojglRDaB97T92h0Og==",
"license": "MIT",
"dependencies": {
"third-party-capital": "1.0.20"
},
"peerDependencies": {
"next": "^13.0.0 || ^14.0.0 || ^15.0.0",
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -16216,6 +16230,12 @@
"node": ">=0.8"
}
},
"node_modules/third-party-capital": {
"version": "1.0.20",
"resolved": "https://registry.npmjs.org/third-party-capital/-/third-party-capital-1.0.20.tgz",
"integrity": "sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA==",
"license": "ISC"
},
"node_modules/throttleit": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz",

View File

@@ -32,6 +32,7 @@
"@github/webauthn-json": "^2.1.1",
"@jitsu/js": "^1.10.3",
"@next-auth/prisma-adapter": "^1.0.7",
"@next/third-parties": "^15.4.4",
"@pdf-lib/fontkit": "^1.1.1",
"@prisma/client": "^6.5.0",
"@radix-ui/react-accordion": "^1.2.11",

View File

@@ -70,7 +70,7 @@ export const authOptions: NextAuthOptions = {
const mainDomainObj = new URL(mainDomainUrl);
urlObj.hostname = mainDomainObj.hostname;
urlObj.protocol = mainDomainObj.protocol;
urlObj.port = mainDomainObj.port || '';
urlObj.port = mainDomainObj.port || "";
finalUrl = urlObj.toString();
}

View File

@@ -3,15 +3,17 @@ import { useRouter } from "next/router";
import { useEffect } from "react";
import { useTeam } from "@/context/team-context";
import { sendGTMEvent } from "@next/third-parties/google";
import { toast } from "sonner";
import UpgradePlanContainer from "@/components/billing/upgrade-plan-container";
import AppLayout from "@/components/layouts/app";
import { SettingsHeader } from "@/components/settings/settings-header";
import { useAnalytics } from "@/lib/analytics";
import { usePlan } from "@/lib/swr/use-billing";
import UpgradePlanContainer from "@/components/billing/upgrade-plan-container";
import { GTMComponent } from "@/components/gtm-component";
import AppLayout from "@/components/layouts/app";
import { SettingsHeader } from "@/components/settings/settings-header";
export default function Billing() {
const router = useRouter();
const analytics = useAnalytics();
@@ -29,6 +31,8 @@ export default function Billing() {
teamId: teamId,
$set: { teamId: teamId, teamPlan: plan },
});
sendGTMEvent({ event: "upgraded" });
}
if (router.query.cancel) {
@@ -39,12 +43,15 @@ export default function Billing() {
}, [router.query]);
return (
<AppLayout>
<main className="relative mx-2 mb-10 mt-4 space-y-8 overflow-hidden px-1 sm:mx-3 md:mx-5 md:mt-5 lg:mx-7 lg:mt-8 xl:mx-10">
<SettingsHeader />
<>
<GTMComponent />
<AppLayout>
<main className="relative mx-2 mb-10 mt-4 space-y-8 overflow-hidden px-1 sm:mx-3 md:mx-5 md:mt-5 lg:mx-7 lg:mt-8 xl:mx-10">
<SettingsHeader />
<UpgradePlanContainer />
</main>
</AppLayout>
<UpgradePlanContainer />
</main>
</AppLayout>
</>
);
}

View File

@@ -3,11 +3,15 @@ import { useRouter } from "next/router";
import { useEffect } from "react";
import { useState } from "react";
import { sendGTMEvent } from "@next/third-parties/google";
import { ArrowLeft as ArrowLeftIcon } from "lucide-react";
import { AnimatePresence } from "motion/react";
import { useSession } from "next-auth/react";
import { CustomUser } from "@/lib/types";
import { cn } from "@/lib/utils";
import { GTMComponent } from "@/components/gtm-component";
import { Button } from "@/components/ui/button";
import Dataroom from "@/components/welcome/dataroom";
import DataroomTrial from "@/components/welcome/dataroom-trial";
@@ -21,6 +25,7 @@ import Upload from "@/components/welcome/upload";
export default function Welcome() {
const router = useRouter();
const [showSkipButtons, setShowSkipButtons] = useState(false);
const { data: session } = useSession();
useEffect(() => {
const timer = setTimeout(() => {
@@ -30,6 +35,19 @@ export default function Welcome() {
return () => clearTimeout(timer);
}, []);
// Track signup for new users when welcome page loads
useEffect(() => {
const user = session?.user as CustomUser;
if (user?.createdAt) {
// Check if user was created within the last 30 seconds (indicating new signup)
const isNewUser = new Date(user.createdAt).getTime() > Date.now() - 30000;
if (isNewUser) {
sendGTMEvent({ event: "signup" });
}
}
}, [session]);
const isDataroomUpload = router.query.type === "dataroom-upload";
const skipButtonText = isDataroomUpload
@@ -41,50 +59,54 @@ export default function Welcome() {
: "/documents";
return (
<div className="mx-auto flex h-screen max-w-3xl flex-col items-center justify-center overflow-x-hidden">
<AnimatePresence mode="wait">
{router.query.type ? (
<>
<button
className="group absolute left-2 top-10 z-40 rounded-full p-2 transition-all hover:bg-gray-400 sm:left-10"
onClick={() => router.back()}
>
<ArrowLeftIcon className="h-8 w-8 text-gray-500 group-hover:text-gray-800 group-active:scale-90" />
</button>
<>
<GTMComponent />
<div className="mx-auto flex h-screen max-w-3xl flex-col items-center justify-center overflow-x-hidden">
<AnimatePresence mode="wait">
{router.query.type ? (
<>
<button
className="group absolute left-2 top-10 z-40 rounded-full p-2 transition-all hover:bg-gray-400 sm:left-10"
onClick={() => router.back()}
>
<ArrowLeftIcon className="h-8 w-8 text-gray-500 group-hover:text-gray-800 group-active:scale-90" />
</button>
<Button
variant={"link"}
onClick={() => router.push(skipButtonPath)}
className={cn(
"absolute right-2 top-10 z-40 p-2 text-muted-foreground sm:right-10",
showSkipButtons ? "block" : "hidden",
)}
>
{skipButtonText}
</Button>
</>
) : (
<Intro key="intro" />
)}
{router.query.type === "next" && <Next key="next" />}
{router.query.type === "select" && <Select key="select" />}
{router.query.type === "pitchdeck" && <Upload key="pitchdeck" />}
{router.query.type === "document" && <Upload key="document" />}
{router.query.type === "sales-document" && (
<Upload key="sales-document" />
)}
{router.query.type === "notion" && <NotionForm key="notion" />}
{router.query.type === "dataroom" && <Dataroom key="dataroom" />}
{router.query.type === "dataroom-trial" && (
<DataroomTrial key="dataroom-trial" />
)}
{router.query.type === "dataroom-upload" && router.query.dataroomId && (
<DataroomUpload
key="dataroom-upload"
dataroomId={router.query.dataroomId as string}
/>
)}
</AnimatePresence>
</div>
<Button
variant={"link"}
onClick={() => router.push(skipButtonPath)}
className={cn(
"absolute right-2 top-10 z-40 p-2 text-muted-foreground sm:right-10",
showSkipButtons ? "block" : "hidden",
)}
>
{skipButtonText}
</Button>
</>
) : (
<Intro key="intro" />
)}
{router.query.type === "next" && <Next key="next" />}
{router.query.type === "select" && <Select key="select" />}
{router.query.type === "pitchdeck" && <Upload key="pitchdeck" />}
{router.query.type === "document" && <Upload key="document" />}
{router.query.type === "sales-document" && (
<Upload key="sales-document" />
)}
{router.query.type === "notion" && <NotionForm key="notion" />}
{router.query.type === "dataroom" && <Dataroom key="dataroom" />}
{router.query.type === "dataroom-trial" && (
<DataroomTrial key="dataroom-trial" />
)}
{router.query.type === "dataroom-upload" &&
router.query.dataroomId && (
<DataroomUpload
key="dataroom-upload"
dataroomId={router.query.dataroomId as string}
/>
)}
</AnimatePresence>
</div>
</>
);
}