mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-12-15 01:21:48 +03:00
feat(homepage): add analytics (#1474)
This commit is contained in:
parent
f91247c6c7
commit
31ecde773a
@ -1,64 +1,18 @@
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AiFillStar } from "react-icons/ai";
|
||||
import { LuChevronRight } from "react-icons/lu";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { PopoverMenuMobile } from "./components/PopoverMenuMobile";
|
||||
import { QuivrLogo } from "./components/QuivrLogo";
|
||||
import { NavbarItem } from "./types";
|
||||
import { useHomeHeader } from "./hooks/useHomeHeader";
|
||||
import { linkStyle } from "./styles";
|
||||
|
||||
type HomeNavProps = {
|
||||
color?: "white" | "black";
|
||||
};
|
||||
|
||||
export const HomeHeader = ({ color = "white" }: HomeNavProps): JSX.Element => {
|
||||
const { t } = useTranslation("home");
|
||||
const linkStyle = {
|
||||
white: "text-white hover:text-slate-200",
|
||||
black: "text-black",
|
||||
};
|
||||
|
||||
const navItems: NavbarItem[] = [
|
||||
{
|
||||
href: "https://theodo.co.uk",
|
||||
label: `${t("sponsored_by")} Theodo`,
|
||||
rightIcon: null,
|
||||
newTab: true,
|
||||
className: "underline",
|
||||
},
|
||||
{
|
||||
href: "https://github.com/StanGirard/quivr",
|
||||
label: t("star_us"),
|
||||
leftIcon: <AiFillStar size={16} className="hidden md:inline" />,
|
||||
rightIcon: null,
|
||||
},
|
||||
{ href: "/blog", label: t("blog"), rightIcon: null, newTab: true },
|
||||
{ href: "/signup", label: t("sign_up") },
|
||||
{ href: "/login", label: t("sign_in") },
|
||||
];
|
||||
|
||||
const navLinks = (device: "mobile" | "desktop") =>
|
||||
navItems.map(
|
||||
({ href, label, leftIcon, rightIcon, newTab = false, className }) => (
|
||||
<li key={label}>
|
||||
<Link
|
||||
href={href}
|
||||
{...(newTab && { target: "_blank", rel: "noopener noreferrer" })}
|
||||
className={cn(
|
||||
"flex justify-between items-center hover:text-primary p-2 gap-1",
|
||||
device === "desktop" ? linkStyle[color] : null,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{leftIcon}
|
||||
{label}
|
||||
{rightIcon !== null && (rightIcon ?? <LuChevronRight size={16} />)}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
);
|
||||
const { navLinks } = useHomeHeader({ color });
|
||||
|
||||
return (
|
||||
<header className="w-screen flex justify-between items-center p-5 min-w-max md:max-w-6xl m-auto">
|
||||
|
@ -0,0 +1,71 @@
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AiFillStar } from "react-icons/ai";
|
||||
import { LuChevronRight } from "react-icons/lu";
|
||||
|
||||
import { useHomepageTracking } from "@/app/(home)/hooks/useHomepageTracking";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { linkStyle } from "../styles";
|
||||
import { NavbarItem } from "../types";
|
||||
|
||||
type UseHomeHeaderProps = {
|
||||
color: "white" | "black";
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useHomeHeader = ({ color }: UseHomeHeaderProps) => {
|
||||
const { t } = useTranslation("home");
|
||||
const { onLinkClick } = useHomepageTracking();
|
||||
|
||||
const navItems: NavbarItem[] = [
|
||||
{
|
||||
href: "https://theodo.co.uk",
|
||||
label: `${t("sponsored_by")} Theodo`,
|
||||
rightIcon: null,
|
||||
newTab: true,
|
||||
className: "underline",
|
||||
},
|
||||
{ href: "/blog", label: t("blog"), rightIcon: null, newTab: true },
|
||||
{
|
||||
href: "https://github.com/StanGirard/quivr",
|
||||
label: t("star_us"),
|
||||
leftIcon: <AiFillStar size={16} className="hidden md:inline" />,
|
||||
rightIcon: null,
|
||||
},
|
||||
{ href: "/signup", label: t("sign_up") },
|
||||
{ href: "/login", label: t("sign_in") },
|
||||
];
|
||||
|
||||
const navLinks = (device: "mobile" | "desktop") =>
|
||||
navItems.map(
|
||||
({ href, label, leftIcon, rightIcon, newTab = false, className }) => (
|
||||
<li key={label}>
|
||||
<Link
|
||||
href={href}
|
||||
onClick={(event) => {
|
||||
onLinkClick({
|
||||
href,
|
||||
label,
|
||||
event,
|
||||
});
|
||||
}}
|
||||
{...(newTab && { target: "_blank", rel: "noopener noreferrer" })}
|
||||
className={cn(
|
||||
"flex justify-between items-center hover:text-primary p-2 gap-1",
|
||||
device === "desktop" ? linkStyle[color] : null,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{leftIcon}
|
||||
{label}
|
||||
{rightIcon !== null && (rightIcon ?? <LuChevronRight size={16} />)}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
navLinks,
|
||||
};
|
||||
};
|
4
frontend/app/(home)/components/HomeHeader/styles.ts
Normal file
4
frontend/app/(home)/components/HomeHeader/styles.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const linkStyle = {
|
||||
white: "text-white hover:text-slate-200",
|
||||
black: "text-black",
|
||||
};
|
@ -3,6 +3,7 @@ import Link from "next/link";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LuChevronRight } from "react-icons/lu";
|
||||
|
||||
import { useHomepageTracking } from "@/app/(home)/hooks/useHomepageTracking";
|
||||
import { DEMO_VIDEO_DATA_KEY } from "@/lib/api/cms/config";
|
||||
import { useCmsApi } from "@/lib/api/cms/useCmsApi";
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
@ -13,7 +14,7 @@ import { VideoPlayer } from "./components/VideoPlayer";
|
||||
export const DemoSection = (): JSX.Element => {
|
||||
const { t } = useTranslation("home", { keyPrefix: "demo" });
|
||||
const { getDemoVideoUrl } = useCmsApi();
|
||||
|
||||
const { onLinkClick } = useHomepageTracking();
|
||||
const { data: demoVideoUrl } = useQuery({
|
||||
queryKey: [DEMO_VIDEO_DATA_KEY],
|
||||
queryFn: getDemoVideoUrl,
|
||||
@ -29,7 +30,16 @@ export const DemoSection = (): JSX.Element => {
|
||||
<Spinner />
|
||||
)}
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Link
|
||||
href="/signup"
|
||||
onClick={(event) => {
|
||||
onLinkClick({
|
||||
href: "/signup",
|
||||
label: "SIGN_UP",
|
||||
event,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button className="mt-2 rounded-full">
|
||||
{t("start_now")} <LuChevronRight size={24} />
|
||||
</Button>
|
||||
|
@ -7,8 +7,11 @@ import { RiTwitterXLine } from "react-icons/ri";
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import { GITHUB_URL, LINKEDIN_URL, TWITTER_URL } from "@/lib/config/CONSTANTS";
|
||||
|
||||
import { useHomepageTracking } from "../../hooks/useHomepageTracking";
|
||||
|
||||
export const FooterSection = (): JSX.Element => {
|
||||
const { t } = useTranslation("home", { keyPrefix: "footer" });
|
||||
const { onLinkClick } = useHomepageTracking();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-10 text-white text-center text-lg">
|
||||
@ -17,13 +20,31 @@ export const FooterSection = (): JSX.Element => {
|
||||
{t("description_1")} <br /> {t("description_2")}{" "}
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-5 flex-wrap">
|
||||
<Link href="/signup">
|
||||
<Link
|
||||
href="/signup"
|
||||
onClick={(event) => {
|
||||
onLinkClick({
|
||||
href: "/signup",
|
||||
label: "SIGN_UP",
|
||||
event,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button className=" rounded-full">
|
||||
{t("start_using")}
|
||||
<LuChevronRight size={24} />
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/contact">
|
||||
<Link
|
||||
href="/contact"
|
||||
onClick={(event) => {
|
||||
onLinkClick({
|
||||
href: "/contact",
|
||||
label: "CONTACT",
|
||||
event,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button variant="tertiary">
|
||||
{t("contact_sales")} <LuChevronRight size={24} />
|
||||
</Button>
|
||||
|
@ -5,10 +5,13 @@ import { LuChevronRight } from "react-icons/lu";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
|
||||
import { useHomepageTracking } from "../../hooks/useHomepageTracking";
|
||||
|
||||
export const IntroSection = (): JSX.Element => {
|
||||
const { t } = useTranslation("home", { keyPrefix: "intro" });
|
||||
const laptopImage = "/Homepage/laptop-demo.png";
|
||||
const smartphoneImage = "/Homepage/smartphone-demo.png";
|
||||
const { onLinkClick } = useHomepageTracking();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -22,12 +25,30 @@ export const IntroSection = (): JSX.Element => {
|
||||
<p className="text-xl">{t("subtitle")}</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-start sm:flex-row sm:items-center gap-5">
|
||||
<Link href="/signup">
|
||||
<Link
|
||||
href="/signup"
|
||||
onClick={(event) =>
|
||||
onLinkClick({
|
||||
href: "/signup",
|
||||
label: "SIGN_UP",
|
||||
event,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Button className="text-white bg-black rounded-full">
|
||||
{t("try_demo")} <LuChevronRight size={24} />
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/contact">
|
||||
<Link
|
||||
href="/contact"
|
||||
onClick={(event) => {
|
||||
onLinkClick({
|
||||
href: "/contact",
|
||||
label: "CONTACT_SALES",
|
||||
event,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button variant="tertiary" className="font-semibold">
|
||||
{t("contact_sales")} <LuChevronRight size={24} />
|
||||
</Button>
|
||||
|
@ -14,10 +14,13 @@ import {
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import Spinner from "@/lib/components/ui/Spinner";
|
||||
|
||||
import { useHomepageTracking } from "../../hooks/useHomepageTracking";
|
||||
|
||||
export const SecuritySection = (): JSX.Element => {
|
||||
const { t } = useTranslation("home", {
|
||||
keyPrefix: "security",
|
||||
});
|
||||
const { onLinkClick } = useHomepageTracking();
|
||||
|
||||
const { getSecurityQuestions } = useCmsApi();
|
||||
|
||||
@ -53,7 +56,16 @@ export const SecuritySection = (): JSX.Element => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex md:justify-end w-full">
|
||||
<Link href="/signup">
|
||||
<Link
|
||||
href="/signup"
|
||||
onClick={(event) => {
|
||||
onLinkClick({
|
||||
href: "/signup",
|
||||
label: "SIGN_UP",
|
||||
event,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button className="rounded-full">
|
||||
{t("cta")}
|
||||
<LuChevronRight size={24} />
|
||||
|
@ -5,9 +5,11 @@ import { LuChevronRight } from "react-icons/lu";
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
|
||||
import { UseCasesListing } from "./components/UseCasesListing/UseCasesListing";
|
||||
import { useHomepageTracking } from "../../hooks/useHomepageTracking";
|
||||
|
||||
export const UseCases = (): JSX.Element => {
|
||||
const { t } = useTranslation("home");
|
||||
const { onLinkClick } = useHomepageTracking();
|
||||
|
||||
return (
|
||||
<div className="text-white w-full">
|
||||
@ -19,7 +21,16 @@ export const UseCases = (): JSX.Element => {
|
||||
</div>
|
||||
<UseCasesListing />
|
||||
<div className="mt-10 flex md:justify-center">
|
||||
<Link href="/signup">
|
||||
<Link
|
||||
href="/signup"
|
||||
onClick={(event) => {
|
||||
onLinkClick({
|
||||
href: "/signup",
|
||||
label: "SIGN_UP",
|
||||
event,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button className="bg-black rounded-full">
|
||||
{t("intro.try_demo")} <LuChevronRight size={24} />
|
||||
</Button>
|
||||
|
@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LuPanelLeft } from "react-icons/lu";
|
||||
|
||||
import { useHomepageTracking } from "@/app/(home)/hooks/useHomepageTracking";
|
||||
import { USE_CASES_DATA_KEY } from "@/lib/api/cms/config";
|
||||
import { useCmsApi } from "@/lib/api/cms/useCmsApi";
|
||||
import Spinner from "@/lib/components/ui/Spinner";
|
||||
@ -12,7 +13,7 @@ import { UseCaseComponent } from "./components/UseCaseComponent";
|
||||
|
||||
export const UseCasesListing = (): JSX.Element => {
|
||||
const { getUseCases } = useCmsApi();
|
||||
|
||||
const { onButtonClick } = useHomepageTracking();
|
||||
const { data: cases = [], isLoading } = useQuery({
|
||||
queryKey: [USE_CASES_DATA_KEY],
|
||||
queryFn: getUseCases,
|
||||
@ -37,13 +38,23 @@ export const UseCasesListing = (): JSX.Element => {
|
||||
);
|
||||
}
|
||||
|
||||
const handleUseCaseClick = (id: string) => {
|
||||
onButtonClick({
|
||||
label: `USE_CASES_${id}`,
|
||||
});
|
||||
if (!isMobile) {
|
||||
return;
|
||||
}
|
||||
setSelectedCaseId(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-6 md:gap-10 flex-column items-start ">
|
||||
<div className={"col-span-6 md:col-span-2 flex flex-col gap-3"}>
|
||||
{cases.map((c) => (
|
||||
<div
|
||||
key={c.id}
|
||||
onClick={() => !isMobile && setSelectedCaseId(c.id)}
|
||||
onClick={() => handleUseCaseClick(c.id)}
|
||||
className={cn(
|
||||
"p-6 rounded-lg cursor-pointer",
|
||||
selectedCaseId === c.id &&
|
||||
|
@ -27,7 +27,7 @@ export const UseCaseComponent = ({
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
<div className="flex mt-1 flex-col w-full shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-4 mt-10">
|
||||
<div className="flex flex-col w-full shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-4 mt-10">
|
||||
<div className="flex items-center">
|
||||
<PiPaperclipFill className="text-3xl" />
|
||||
<span className="text-[#BFBFBF]">@Einstein</span>
|
||||
|
33
frontend/app/(home)/hooks/useHomepageTracking.ts
Normal file
33
frontend/app/(home)/hooks/useHomepageTracking.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { MouseEvent } from "react";
|
||||
|
||||
import { useEventTracking } from "@/services/analytics/june/useEventTracking";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useHomepageTracking = () => {
|
||||
const { track } = useEventTracking();
|
||||
const router = useRouter();
|
||||
|
||||
const onLinkClick = ({
|
||||
href,
|
||||
label,
|
||||
event,
|
||||
}: {
|
||||
href: string;
|
||||
label: string;
|
||||
event: MouseEvent<HTMLAnchorElement>;
|
||||
}) => {
|
||||
event.preventDefault();
|
||||
void track(`HOMEPAGE-${label}`);
|
||||
router.push(href);
|
||||
};
|
||||
|
||||
const onButtonClick = ({ label }: { label: string }) => {
|
||||
void track(`HOMEPAGE-${label}`);
|
||||
};
|
||||
|
||||
return {
|
||||
onLinkClick,
|
||||
onButtonClick,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user