feat: add ga events

This commit is contained in:
AykutSarac 2024-05-09 17:32:52 +03:00
parent e4d9fff148
commit 4a5dcfb704
No known key found for this signature in database
23 changed files with 191 additions and 62 deletions

View File

@ -15,6 +15,7 @@ import { BiSolidDockLeft } from "react-icons/bi";
import { MdOutlineCheckCircleOutline } from "react-icons/md"; import { MdOutlineCheckCircleOutline } from "react-icons/md";
import { TbTransform } from "react-icons/tb"; import { TbTransform } from "react-icons/tb";
import { VscError, VscFeedback, VscSourceControl, VscSync, VscSyncIgnored } from "react-icons/vsc"; import { VscError, VscFeedback, VscSourceControl, VscSync, VscSyncIgnored } from "react-icons/vsc";
import { gaEvent } from "src/lib/utils/gaEvent";
import { documentSvc } from "src/services/document.service"; import { documentSvc } from "src/services/document.service";
import useConfig from "src/store/useConfig"; import useConfig from "src/store/useConfig";
import useFile from "src/store/useFile"; import useFile from "src/store/useFile";
@ -106,7 +107,10 @@ export const BottomBar = () => {
const [isPrivate, setIsPrivate] = React.useState(false); const [isPrivate, setIsPrivate] = React.useState(false);
const [isUpdating, setIsUpdating] = React.useState(false); const [isUpdating, setIsUpdating] = React.useState(false);
const toggleEditor = () => toggleFullscreen(!fullscreen); const toggleEditor = () => {
toggleFullscreen(!fullscreen);
gaEvent("Bottom Bar", "toggle fullscreen");
};
React.useEffect(() => { React.useEffect(() => {
setIsPrivate(data?.private ?? true); setIsPrivate(data?.private ?? true);
@ -239,12 +243,22 @@ export const BottomBar = () => {
Share Share
</StyledBottomBarItem> </StyledBottomBarItem>
{liveTransformEnabled ? ( {liveTransformEnabled ? (
<StyledBottomBarItem onClick={() => toggleLiveTransform(false)}> <StyledBottomBarItem
onClick={() => {
toggleLiveTransform(false);
gaEvent("Bottom Bar", "toggle live transform", "manual");
}}
>
<VscSync /> <VscSync />
<Text fz="xs">Live Transform</Text> <Text fz="xs">Live Transform</Text>
</StyledBottomBarItem> </StyledBottomBarItem>
) : ( ) : (
<StyledBottomBarItem onClick={() => toggleLiveTransform(true)}> <StyledBottomBarItem
onClick={() => {
toggleLiveTransform(true);
gaEvent("Bottom Bar", "toggle live transform", "live");
}}
>
<VscSyncIgnored /> <VscSyncIgnored />
<Text fz="xs">Manual Transform</Text> <Text fz="xs">Manual Transform</Text>
</StyledBottomBarItem> </StyledBottomBarItem>

View File

@ -11,6 +11,7 @@ import {
Badge, Badge,
} from "@mantine/core"; } from "@mantine/core";
import { IoRocketSharp } from "react-icons/io5"; import { IoRocketSharp } from "react-icons/io5";
import { gaEvent } from "src/lib/utils/gaEvent";
import useModal from "src/store/useModal"; import useModal from "src/store/useModal";
import useUser from "src/store/useUser"; import useUser from "src/store/useUser";
@ -62,7 +63,10 @@ export const AccountModal: React.FC<ModalProps> = ({ opened, onClose }) => {
variant="gradient" variant="gradient"
gradient={{ from: "teal", to: "lime", deg: 105 }} gradient={{ from: "teal", to: "lime", deg: 105 }}
leftSection={<IoRocketSharp />} leftSection={<IoRocketSharp />}
onClick={() => setVisible("premium")(true)} onClick={() => {
setVisible("premium")(true);
gaEvent("Account Modal", "click upgrade premium");
}}
> >
Upgrade to Premium Upgrade to Premium
</Button> </Button>

View File

@ -30,6 +30,7 @@ import { FaTrash } from "react-icons/fa";
import { SlOptionsVertical } from "react-icons/sl"; import { SlOptionsVertical } from "react-icons/sl";
import { VscAdd } from "react-icons/vsc"; import { VscAdd } from "react-icons/vsc";
import { FileFormat } from "src/enums/file.enum"; import { FileFormat } from "src/enums/file.enum";
import { gaEvent } from "src/lib/utils/gaEvent";
import { documentSvc } from "src/services/document.service"; import { documentSvc } from "src/services/document.service";
import useFile, { File } from "src/store/useFile"; import useFile, { File } from "src/store/useFile";
@ -124,6 +125,7 @@ export const CloudModal: React.FC<ModalProps> = ({ opened, onClose }) => {
if (error) throw new Error(error.message); if (error) throw new Error(error.message);
if (data[0]) setFile(data[0]); if (data[0]) setFile(data[0]);
gaEvent("Cloud Modal", "open file");
} catch (error) { } catch (error) {
if (error instanceof Error) toast.error(error.message); if (error instanceof Error) toast.error(error.message);
} finally { } finally {
@ -134,14 +136,23 @@ export const CloudModal: React.FC<ModalProps> = ({ opened, onClose }) => {
); );
const onDeleteClick = React.useCallback( const onDeleteClick = React.useCallback(
(file: File) => { async (file: File) => {
toast try {
.promise(documentSvc.delete(file.id), { toast.loading("Deleting file...", { id: "delete-file" });
loading: "Deleting file...",
error: "An error occurred while deleting the file!", const { error } = await documentSvc.delete(file.id);
success: `Deleted ${file.name}!`, if (error) throw new Error(error.message);
})
.then(() => refetch()); await refetch();
toast.success(`Deleted ${file.name}!`, { id: "delete-file" });
gaEvent("Cloud Modal", "delete file");
} catch (error) {
if (error instanceof Error) {
toast.error(error.message, { id: "delete-file" });
}
} finally {
toast.dismiss("delete-file");
}
}, },
[refetch] [refetch]
); );

View File

@ -90,7 +90,7 @@ export const DownloadModal: React.FC<ModalProps> = ({ opened, onClose }) => {
]); ]);
toast.success("Copied to clipboard"); toast.success("Copied to clipboard");
gaEvent("click", "clipboard image"); gaEvent("Download Modal", "clipboard image");
} catch (error) { } catch (error) {
toast.error("Failed to copy to clipboard"); toast.error("Failed to copy to clipboard");
} finally { } finally {
@ -111,7 +111,7 @@ export const DownloadModal: React.FC<ModalProps> = ({ opened, onClose }) => {
}); });
downloadURI(dataURI, `${fileDetails.filename}.${extension}`); downloadURI(dataURI, `${fileDetails.filename}.${extension}`);
gaEvent("download", "download graph image", extension); gaEvent("Download Modal", "download image", extension);
} catch (error) { } catch (error) {
toast.error("Failed to download image!"); toast.error("Failed to download image!");
} finally { } finally {

View File

@ -14,6 +14,7 @@ import { Dropzone } from "@mantine/dropzone";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { AiOutlineUpload } from "react-icons/ai"; import { AiOutlineUpload } from "react-icons/ai";
import { FileFormat } from "src/enums/file.enum"; import { FileFormat } from "src/enums/file.enum";
import { gaEvent } from "src/lib/utils/gaEvent";
import useFile from "src/store/useFile"; import useFile from "src/store/useFile";
export const ImportModal: React.FC<ModalProps> = ({ opened, onClose }) => { export const ImportModal: React.FC<ModalProps> = ({ opened, onClose }) => {
@ -28,6 +29,8 @@ export const ImportModal: React.FC<ModalProps> = ({ opened, onClose }) => {
setFile(null); setFile(null);
toast.loading("Loading...", { id: "toastFetch" }); toast.loading("Loading...", { id: "toastFetch" });
gaEvent("Import Modal", "fetch url");
return fetch(url) return fetch(url)
.then(res => res.json()) .then(res => res.json())
.then(json => { .then(json => {
@ -47,6 +50,8 @@ export const ImportModal: React.FC<ModalProps> = ({ opened, onClose }) => {
setURL(""); setURL("");
onClose(); onClose();
}); });
gaEvent("Import Modal", "import file", format);
} }
}; };

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { Modal, Button, ModalProps, Textarea, Divider, Group } from "@mantine/core"; import { Modal, Button, ModalProps, Textarea, Divider, Group } from "@mantine/core";
import { decode } from "jsonwebtoken"; import { decode } from "jsonwebtoken";
import { gaEvent } from "src/lib/utils/gaEvent";
import useFile from "src/store/useFile"; import useFile from "src/store/useFile";
export const JWTModal: React.FC<ModalProps> = ({ opened, onClose }) => { export const JWTModal: React.FC<ModalProps> = ({ opened, onClose }) => {
@ -10,8 +11,9 @@ export const JWTModal: React.FC<ModalProps> = ({ opened, onClose }) => {
const resolve = () => { const resolve = () => {
if (!token) return; if (!token) return;
const json = decode(token); const json = decode(token);
setContents({ contents: JSON.stringify(json, null, 2) }); setContents({ contents: JSON.stringify(json, null, 2) });
gaEvent("JWT Modal", "resolve");
setToken(""); setToken("");
onClose(); onClose();
}; };

View File

@ -2,6 +2,7 @@ import React from "react";
import { Modal, Stack, Text, ScrollArea, ModalProps, Button } from "@mantine/core"; import { Modal, Stack, Text, ScrollArea, ModalProps, Button } from "@mantine/core";
import { CodeHighlight } from "@mantine/code-highlight"; import { CodeHighlight } from "@mantine/code-highlight";
import { VscLock } from "react-icons/vsc"; import { VscLock } from "react-icons/vsc";
import { gaEvent } from "src/lib/utils/gaEvent";
import useGraph from "src/store/useGraph"; import useGraph from "src/store/useGraph";
import useModal from "src/store/useModal"; import useModal from "src/store/useModal";
@ -32,7 +33,10 @@ export const NodeModal: React.FC<ModalProps> = ({ opened, onClose }) => {
</ScrollArea.Autosize> </ScrollArea.Autosize>
</Stack> </Stack>
<Button <Button
onClick={() => setVisible("premium")(true)} onClick={() => {
setVisible("premium")(true);
gaEvent("Node Modal", "edit");
}}
rightSection={<VscLock strokeWidth={0.5} />} rightSection={<VscLock strokeWidth={0.5} />}
> >
Edit Edit

View File

@ -13,6 +13,7 @@ import {
} from "@mantine/core"; } from "@mantine/core";
import { BsCheck } from "react-icons/bs"; import { BsCheck } from "react-icons/bs";
import { MdChevronRight } from "react-icons/md"; import { MdChevronRight } from "react-icons/md";
import { gaEvent } from "src/lib/utils/gaEvent";
export const PremiumModal: React.FC<ModalProps> = ({ opened, onClose }) => { export const PremiumModal: React.FC<ModalProps> = ({ opened, onClose }) => {
return ( return (
@ -42,6 +43,7 @@ export const PremiumModal: React.FC<ModalProps> = ({ opened, onClose }) => {
<Stack gap="xs"> <Stack gap="xs">
<Title order={3}>Premium</Title> <Title order={3}>Premium</Title>
<Button <Button
onClick={() => gaEvent("Premium Modal", "click upgrade premium")}
component={Link} component={Link}
prefetch={false} prefetch={false}
href="/pricing" href="/pricing"

View File

@ -3,6 +3,7 @@ import { Stack, Modal, Button, ModalProps, Text, Anchor, Group, Divider } from "
import Editor from "@monaco-editor/react"; import Editor from "@monaco-editor/react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { VscLinkExternal } from "react-icons/vsc"; import { VscLinkExternal } from "react-icons/vsc";
import { gaEvent } from "src/lib/utils/gaEvent";
import useConfig from "src/store/useConfig"; import useConfig from "src/store/useConfig";
import useFile from "src/store/useFile"; import useFile from "src/store/useFile";
@ -32,8 +33,9 @@ export const SchemaModal: React.FC<ModalProps> = ({ opened, onClose }) => {
const onApply = () => { const onApply = () => {
try { try {
const parsedSchema = JSON.parse(schema); const parsedSchema = JSON.parse(schema);
setJsonSchema(parsedSchema); setJsonSchema(parsedSchema);
gaEvent("Schema Modal", "apply");
toast.success("Applied schema!"); toast.success("Applied schema!");
onClose(); onClose();
} catch (error) { } catch (error) {

View File

@ -13,6 +13,7 @@ import {
} from "@mantine/core"; } from "@mantine/core";
import { FiExternalLink } from "react-icons/fi"; import { FiExternalLink } from "react-icons/fi";
import { MdCheck, MdCopyAll } from "react-icons/md"; import { MdCheck, MdCopyAll } from "react-icons/md";
import { gaEvent } from "src/lib/utils/gaEvent";
export const ShareModal: React.FC<ModalProps> = ({ opened, onClose }) => { export const ShareModal: React.FC<ModalProps> = ({ opened, onClose }) => {
const { query } = useRouter(); const { query } = useRouter();
@ -32,7 +33,13 @@ export const ShareModal: React.FC<ModalProps> = ({ opened, onClose }) => {
<CopyButton value={shareURL} timeout={2000}> <CopyButton value={shareURL} timeout={2000}>
{({ copied, copy }) => ( {({ copied, copy }) => (
<Tooltip label={copied ? "Copied" : "Copy"} withArrow position="right"> <Tooltip label={copied ? "Copied" : "Copy"} withArrow position="right">
<ActionIcon color={copied ? "teal" : "gray"} onClick={copy}> <ActionIcon
color={copied ? "teal" : "gray"}
onClick={() => {
copy();
gaEvent("Share Modal", "copy");
}}
>
{copied ? <MdCheck size="1rem" /> : <MdCopyAll size="1rem" />} {copied ? <MdCheck size="1rem" /> : <MdCopyAll size="1rem" />}
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { Stack, Modal, ModalProps, Select, ScrollArea } from "@mantine/core"; import { Stack, Modal, ModalProps, Select, ScrollArea } from "@mantine/core";
import { CodeHighlight } from "@mantine/code-highlight"; import { CodeHighlight } from "@mantine/code-highlight";
import { gaEvent } from "src/lib/utils/gaEvent";
import useJson from "src/store/useJson"; import useJson from "src/store/useJson";
enum Language { enum Language {
@ -93,7 +94,10 @@ export const TypeModal: React.FC<ModalProps> = ({ opened, onClose }) => {
<Select <Select
value={selectedType} value={selectedType}
data={typeOptions} data={typeOptions}
onChange={e => setSelectedType(e as Language)} onChange={e => {
setSelectedType(e as Language);
gaEvent("Type Modal", "generate", e as string);
}}
allowDeselect={false} allowDeselect={false}
/> />
<ScrollArea.Autosize mah={400} maw={700}> <ScrollArea.Autosize mah={400} maw={700}>

View File

@ -19,7 +19,7 @@ export const FileMenu = () => {
a.download = `jsoncrack.${getFormat()}`; a.download = `jsoncrack.${getFormat()}`;
a.click(); a.click();
gaEvent("download", "file download"); gaEvent("File Menu", "download", getFormat());
}; };
return ( return (

View File

@ -2,6 +2,7 @@ import React from "react";
import { Menu, Text, Flex } from "@mantine/core"; import { Menu, Text, Flex } from "@mantine/core";
import { BsCheck2 } from "react-icons/bs"; import { BsCheck2 } from "react-icons/bs";
import { MdSettings } from "react-icons/md"; import { MdSettings } from "react-icons/md";
import { gaEvent } from "src/lib/utils/gaEvent";
import useConfig from "src/store/useConfig"; import useConfig from "src/store/useConfig";
import * as Styles from "./styles"; import * as Styles from "./styles";
@ -32,37 +33,55 @@ export const OptionsMenu = () => {
<Menu.Dropdown> <Menu.Dropdown>
<Menu.Item <Menu.Item
leftSection={<BsCheck2 opacity={rulersEnabled ? 100 : 0} />} leftSection={<BsCheck2 opacity={rulersEnabled ? 100 : 0} />}
onClick={() => toggleRulers(!rulersEnabled)} onClick={() => {
toggleRulers(!rulersEnabled);
gaEvent("Options Menu", "toggle rulers", rulersEnabled ? "on" : "off");
}}
> >
<Text size="xs">Rulers</Text> <Text size="xs">Rulers</Text>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
leftSection={<BsCheck2 opacity={gesturesEnabled ? 100 : 0} />} leftSection={<BsCheck2 opacity={gesturesEnabled ? 100 : 0} />}
onClick={() => toggleGestures(!gesturesEnabled)} onClick={() => {
toggleGestures(!gesturesEnabled);
gaEvent("Options Menu", "toggle gestures", gesturesEnabled ? "on" : "off");
}}
> >
<Text size="xs">Trackpad Gestures</Text> <Text size="xs">Trackpad Gestures</Text>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
leftSection={<BsCheck2 opacity={childrenCountVisible ? 100 : 0} />} leftSection={<BsCheck2 opacity={childrenCountVisible ? 100 : 0} />}
onClick={() => toggleChildrenCount(!childrenCountVisible)} onClick={() => {
toggleChildrenCount(!childrenCountVisible);
gaEvent("Options Menu", "toggle children count", childrenCountVisible ? "on" : "off");
}}
> >
<Text size="xs">Item Count</Text> <Text size="xs">Item Count</Text>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
leftSection={<BsCheck2 opacity={imagePreviewEnabled ? 100 : 0} />} leftSection={<BsCheck2 opacity={imagePreviewEnabled ? 100 : 0} />}
onClick={() => toggleImagePreview(!imagePreviewEnabled)} onClick={() => {
toggleImagePreview(!imagePreviewEnabled);
gaEvent("Options Menu", "toggle image preview", imagePreviewEnabled ? "on" : "off");
}}
> >
<Text size="xs">Image Link Preview</Text> <Text size="xs">Image Link Preview</Text>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
leftSection={<BsCheck2 opacity={collapseButtonVisible ? 100 : 0} />} leftSection={<BsCheck2 opacity={collapseButtonVisible ? 100 : 0} />}
onClick={() => toggleCollapseButton(!collapseButtonVisible)} onClick={() => {
toggleCollapseButton(!collapseButtonVisible);
gaEvent("Options Menu", "toggle collapse button", collapseButtonVisible ? "on" : "off");
}}
> >
<Text size="xs">Show Expand/Collapse</Text> <Text size="xs">Show Expand/Collapse</Text>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
leftSection={<BsCheck2 opacity={darkmodeEnabled ? 100 : 0} />} leftSection={<BsCheck2 opacity={darkmodeEnabled ? 100 : 0} />}
onClick={() => toggleDarkMode(!darkmodeEnabled)} onClick={() => {
toggleDarkMode(!darkmodeEnabled);
gaEvent("Options Menu", "toggle dark mode", darkmodeEnabled ? "on" : "off");
}}
> >
<Text size="xs">Dark Mode</Text> <Text size="xs">Dark Mode</Text>
</Menu.Item> </Menu.Item>

View File

@ -14,7 +14,7 @@ export const ToolsMenu = () => {
return ( return (
<Menu shadow="md" withArrow> <Menu shadow="md" withArrow>
<Menu.Target> <Menu.Target>
<Styles.StyledToolElement onClick={() => gaEvent("click", "tools menu")}> <Styles.StyledToolElement onClick={() => gaEvent("Tools Menu", "toggle menu")}>
<Flex align="center" gap={3}> <Flex align="center" gap={3}>
Tools <CgChevronDown /> Tools <CgChevronDown />
</Flex> </Flex>
@ -25,27 +25,50 @@ export const ToolsMenu = () => {
fz={12} fz={12}
leftSection={<MdCompare />} leftSection={<MdCompare />}
rightSection={<VscLock />} rightSection={<VscLock />}
onClick={() => setVisible("premium")(true)} onClick={() => {
setVisible("premium")(true);
gaEvent("Tools Menu", "open", "Compare Data");
}}
> >
Compare Data Compare Data
</Menu.Item> </Menu.Item>
<Menu.Item fz={12} leftSection={<VscSearchFuzzy />} onClick={() => setVisible("jq")(true)}> <Menu.Item
fz={12}
leftSection={<VscSearchFuzzy />}
onClick={() => {
setVisible("jq")(true);
gaEvent("Tools Menu", "open", "JSON Query");
}}
>
JSON Query (jq) JSON Query (jq)
</Menu.Item> </Menu.Item>
<Menu.Item fz={12} leftSection={<VscJson />} onClick={() => setVisible("schema")(true)}> <Menu.Item
fz={12}
leftSection={<VscJson />}
onClick={() => {
setVisible("schema")(true);
gaEvent("Tools Menu", "open", "JSON Schema");
}}
>
JSON Schema JSON Schema
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
fz={12} fz={12}
leftSection={<SiJsonwebtokens />} leftSection={<SiJsonwebtokens />}
onClick={() => setVisible("jwt")(true)} onClick={() => {
setVisible("jwt")(true);
gaEvent("Tools Menu", "open", "Decode JWT");
}}
> >
Decode JWT Decode JWT
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
fz={12} fz={12}
leftSection={<VscGroupByRefType />} leftSection={<VscGroupByRefType />}
onClick={() => setVisible("type")(true)} onClick={() => {
setVisible("type")(true);
gaEvent("Tools Menu", "open", "Generate Type");
}}
> >
Generate Type Generate Type
</Menu.Item> </Menu.Item>

View File

@ -65,7 +65,7 @@ export const ViewMenu = () => {
return ( return (
<Menu shadow="md" closeOnItemClick={false} withArrow> <Menu shadow="md" closeOnItemClick={false} withArrow>
<Menu.Target> <Menu.Target>
<Styles.StyledToolElement onClick={() => gaEvent("click", "view menu")}> <Styles.StyledToolElement onClick={() => gaEvent("View Menu", "open menu")}>
<Flex align="center" gap={3}> <Flex align="center" gap={3}>
View <CgChevronDown /> View <CgChevronDown />
</Flex> </Flex>
@ -76,7 +76,10 @@ export const ViewMenu = () => {
miw={205} miw={205}
size="xs" size="xs"
value={viewMode} value={viewMode}
onChange={e => setViewMode(e as ViewMode)} onChange={e => {
setViewMode(e as ViewMode);
gaEvent("View Menu", "change view mode", e as string);
}}
data={[ data={[
{ value: ViewMode.Graph, label: "Graph" }, { value: ViewMode.Graph, label: "Graph" },
{ value: ViewMode.Tree, label: "Tree" }, { value: ViewMode.Tree, label: "Tree" },
@ -88,7 +91,10 @@ export const ViewMenu = () => {
<Menu.Item <Menu.Item
mt="xs" mt="xs"
fz={12} fz={12}
onClick={toggleDirection} onClick={() => {
toggleDirection();
gaEvent("View Menu", "rotate layout");
}}
leftSection={<Styles.StyledFlowIcon rotate={rotateLayout(direction || "RIGHT")} />} leftSection={<Styles.StyledFlowIcon rotate={rotateLayout(direction || "RIGHT")} />}
rightSection={ rightSection={
<Text ml="md" fz={10} c="dimmed"> <Text ml="md" fz={10} c="dimmed">
@ -100,7 +106,10 @@ export const ViewMenu = () => {
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
fz={12} fz={12}
onClick={toggleExpandCollapseGraph} onClick={() => {
toggleExpandCollapseGraph();
gaEvent("View Menu", "expand collapse graph");
}}
leftSection={graphCollapsed ? <VscExpandAll /> : <VscCollapseAll />} leftSection={graphCollapsed ? <VscExpandAll /> : <VscCollapseAll />}
rightSection={ rightSection={
<Text ml="md" fz={10} c="dimmed"> <Text ml="md" fz={10} c="dimmed">

View File

@ -27,7 +27,7 @@ export const ZoomMenu = () => {
return ( return (
<Menu shadow="md" trigger="click" closeOnItemClick={false} withArrow> <Menu shadow="md" trigger="click" closeOnItemClick={false} withArrow>
<Menu.Target> <Menu.Target>
<Styles.StyledToolElement onClick={() => gaEvent("click", "zoom menu")}> <Styles.StyledToolElement onClick={() => gaEvent("Zoom Menu", "open menu")}>
<Flex gap={4} align="center"> <Flex gap={4} align="center">
{Math.round(zoomFactor * 100)}% {Math.round(zoomFactor * 100)}%
<CgChevronDown /> <CgChevronDown />
@ -45,22 +45,56 @@ export const ZoomMenu = () => {
rightSection="%" rightSection="%"
/> />
</Menu.Item> </Menu.Item>
<Menu.Item rightSection="+" onClick={zoomIn}> <Menu.Item
rightSection="+"
onClick={() => {
zoomIn();
gaEvent("Zoom Menu", "zoom in");
}}
>
<Text size="xs">Zoom in</Text> <Text size="xs">Zoom in</Text>
</Menu.Item> </Menu.Item>
<Menu.Item rightSection="-" onClick={zoomOut}> <Menu.Item
rightSection="-"
onClick={() => {
zoomOut();
gaEvent("Zoom Menu", "zoom out");
}}
>
<Text size="xs">Zoom out</Text> <Text size="xs">Zoom out</Text>
</Menu.Item> </Menu.Item>
<Menu.Item rightSection="⇧ 1" onClick={centerView}> <Menu.Item
rightSection="⇧ 1"
onClick={() => {
centerView();
gaEvent("Zoom Menu", "center view");
}}
>
<Text size="xs">Zoom to fit</Text> <Text size="xs">Zoom to fit</Text>
</Menu.Item> </Menu.Item>
<Menu.Item onClick={() => setZoomFactor(50 / 100)}> <Menu.Item
onClick={() => {
setZoomFactor(50 / 100);
gaEvent("Zoom Menu", "zoom to 50%");
}}
>
<Text size="xs">Zoom to %50</Text> <Text size="xs">Zoom to %50</Text>
</Menu.Item> </Menu.Item>
<Menu.Item rightSection="⇧ 0" onClick={() => setZoomFactor(100 / 100)}> <Menu.Item
rightSection="⇧ 0"
onClick={() => {
setZoomFactor(100 / 100);
gaEvent("Zoom Menu", "zoom to 100%");
}}
>
<Text size="xs">Zoom to %100</Text> <Text size="xs">Zoom to %100</Text>
</Menu.Item> </Menu.Item>
<Menu.Item onClick={() => setZoomFactor(200 / 100)}> <Menu.Item
onClick={() => {
setZoomFactor(200 / 100);
gaEvent("Zoom Menu", "zoom to 200%");
}}
>
<Text size="xs">Zoom to %200</Text> <Text size="xs">Zoom to %200</Text>
</Menu.Item> </Menu.Item>
</Menu.Dropdown> </Menu.Dropdown>

View File

@ -8,6 +8,7 @@ import { FiDownload } from "react-icons/fi";
import { SearchInput } from "src/components/SearchInput"; import { SearchInput } from "src/components/SearchInput";
import { FileFormat } from "src/enums/file.enum"; import { FileFormat } from "src/enums/file.enum";
import { JSONCrackLogo } from "src/layout/JsonCrackLogo"; import { JSONCrackLogo } from "src/layout/JsonCrackLogo";
import { gaEvent } from "src/lib/utils/gaEvent";
import useFile from "src/store/useFile"; import useFile from "src/store/useFile";
import useModal from "src/store/useModal"; import useModal from "src/store/useModal";
import { AccountMenu } from "./AccountMenu"; import { AccountMenu } from "./AccountMenu";
@ -80,6 +81,7 @@ export const Toolbar: React.FC<{ isWidget?: boolean }> = ({ isWidget = false })
onClick={() => { onClick={() => {
setSeenPremium(true); setSeenPremium(true);
setVisible("premium")(true); setVisible("premium")(true);
gaEvent("Toolbar", "click upgrade premium");
}} }}
> >
<Indicator <Indicator

View File

@ -4,6 +4,7 @@ import { Button, Title } from "@mantine/core";
import styled from "styled-components"; import styled from "styled-components";
import { MdChevronRight } from "react-icons/md"; import { MdChevronRight } from "react-icons/md";
import { JSONCrackLogo } from "src/layout/JsonCrackLogo"; import { JSONCrackLogo } from "src/layout/JsonCrackLogo";
import { gaEvent } from "src/lib/utils/gaEvent";
const StyledPremiumView = styled.div` const StyledPremiumView = styled.div`
position: relative; position: relative;
@ -166,6 +167,7 @@ export const PremiumView = () => (
</StyledInfo> </StyledInfo>
<Button <Button
onClick={() => gaEvent("Premium View", "click upgrade premium")}
component={Link} component={Link}
prefetch={false} prefetch={false}
href="/pricing" href="/pricing"

View File

@ -39,7 +39,7 @@ export const useFocusNode = () => {
setNodeCount(0); setNodeCount(0);
} }
gaEvent("input", "search node in graph"); gaEvent("Graph", "search");
}, [selectedNode, debouncedValue, value, viewPort]); }, [selectedNode, debouncedValue, value, viewPort]);
return [value, setValue, skip, nodeCount, selectedNode] as const; return [value, setValue, skip, nodeCount, selectedNode] as const;

View File

@ -15,6 +15,7 @@ import styled from "styled-components";
import { AiOutlineInfoCircle } from "react-icons/ai"; import { AiOutlineInfoCircle } from "react-icons/ai";
import { VscArrowRight } from "react-icons/vsc"; import { VscArrowRight } from "react-icons/vsc";
import Layout from "src/layout/Layout"; import Layout from "src/layout/Layout";
import { gaEvent } from "src/lib/utils/gaEvent";
import useUser from "src/store/useUser"; import useUser from "src/store/useUser";
const purchaseLinks = { const purchaseLinks = {
@ -161,6 +162,7 @@ export const PricingCards = () => {
</Flex> </Flex>
<Button <Button
component="a" component="a"
onClick={() => gaEvent("Pricing", "click upgrade premium")}
href={paymentURL(isMonthly ? purchaseLinks.monthly : purchaseLinks.annual)} href={paymentURL(isMonthly ? purchaseLinks.monthly : purchaseLinks.annual)}
target="_blank" target="_blank"
size="lg" size="lg"

View File

@ -45,6 +45,6 @@ export const documentSvc = {
return await supabase.from("document").update(data).eq("id", id).select("private"); return await supabase.from("document").update(data).eq("id", id).select("private");
}, },
delete: async (id: string) => { delete: async (id: string) => {
await supabase.from("document").delete().eq("id", id); return await supabase.from("document").delete().eq("id", id);
}, },
}; };

View File

@ -3,7 +3,6 @@ import { toast } from "react-hot-toast";
import { create } from "zustand"; import { create } from "zustand";
import { defaultJson } from "src/constants/data"; import { defaultJson } from "src/constants/data";
import { FileFormat } from "src/enums/file.enum"; import { FileFormat } from "src/enums/file.enum";
import { gaEvent } from "src/lib/utils/gaEvent";
import { contentToJson, jsonToContent } from "src/lib/utils/json/jsonAdapter"; import { contentToJson, jsonToContent } from "src/lib/utils/json/jsonAdapter";
import { isIframe } from "src/lib/utils/widget"; import { isIframe } from "src/lib/utils/widget";
import { documentSvc } from "src/services/document.service"; import { documentSvc } from "src/services/document.service";
@ -69,19 +68,6 @@ const debouncedUpdateJson = debounce((value: unknown) => {
useJson.getState().setJson(JSON.stringify(value, null, 2)); useJson.getState().setJson(JSON.stringify(value, null, 2));
}, 800); }, 800);
const filterArrayAndObjectFields = (obj: object) => {
const result = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if (Array.isArray(obj[key]) || typeof obj[key] === "object") {
result[key] = obj[key];
}
}
}
return result;
};
const useFile = create<FileStates & JsonActions>()((set, get) => ({ const useFile = create<FileStates & JsonActions>()((set, get) => ({
...initialStates, ...initialStates,
clear: () => { clear: () => {
@ -105,7 +91,6 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
const jsonContent = await jsonToContent(JSON.stringify(contentJson, null, 2), format); const jsonContent = await jsonToContent(JSON.stringify(contentJson, null, 2), format);
get().setContents({ contents: jsonContent }); get().setContents({ contents: jsonContent });
gaEvent("input", "file format change");
} catch (error) { } catch (error) {
get().clear(); get().clear();
console.warn("The content was unable to be converted, so it was cleared instead."); console.warn("The content was unable to be converted, so it was cleared instead.");

View File

@ -1,6 +1,5 @@
import { create } from "zustand"; import { create } from "zustand";
import { Modal } from "src/containers/Modals"; import { Modal } from "src/containers/Modals";
import { gaEvent } from "src/lib/utils/gaEvent";
import useUser from "./useUser"; import useUser from "./useUser";
type ModalState = { type ModalState = {
@ -40,7 +39,6 @@ const useModal = create<ModalState & ModalActions>()(set => ({
return set({ login: true }); return set({ login: true });
} }
if (visible) gaEvent("modal", `open ${modal}`);
set({ [modal]: visible }); set({ [modal]: visible });
}, },
})); }));