From 7772a7733527f335e8d1bd85c647b83c3666bf6b Mon Sep 17 00:00:00 2001 From: AykutSarac Date: Wed, 14 Feb 2024 14:20:44 +0300 Subject: [PATCH 1/4] update headline --- README.md | 2 +- src/pages/_app.tsx | 2 +- src/pages/_document.tsx | 2 +- src/pages/index.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9031dab07da..a03d9f4f6fb 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ booking-screen -# Visualize Instantly Into Graphs +# More Than a JSON Editor JSON Crack is a free, open-source data visualization app capable of visualizing data formats such as JSON, YAML, XML, CSV and more, into interactive graphs. With its intuitive and user-friendly interface, JSON Crack makes it easy to explore, analyze, and understand even the most complex data structures. Whether you're a developer working on a large-scale project or a data enthusiast looking to uncover hidden insights, JSON Crack has the tools and features you need to unlock the full potential of your data. diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 66b1928016e..34751897a4d 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -57,7 +57,7 @@ function JsonCrack({ return ( <> - JSON Crack | Visualize Instantly Into Graphs + JSON Crack | More Than a JSON Editor diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index b130eaa8aed..cf007f19707 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -11,7 +11,7 @@ import { ServerStyleSheet } from "styled-components"; const metatags = Object.freeze({ description: "JSON Crack Editor is a tool for visualizing into graphs, analyzing, editing, formatting, querying, transforming and validating JSON, CSV, YAML, XML, and more.", - title: "JSON Crack - Visualize Data to Graphs", + title: "JSON Crack | More Than A JSON Editor", image: "https://jsoncrack.com/assets/jsoncrack.png", }); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 8bd30986a9d..0667f8c2f7a 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -147,7 +147,7 @@ export const HomePage = () => { return ( - JSON Crack | Visualize Instantly Into Graphs + JSON Crack | More Than a JSON Editor From 16a9d159e8d5ac1fb11f0d14f0bac8a83d422b7c Mon Sep 17 00:00:00 2001 From: AykutSarac Date: Sat, 17 Feb 2024 00:19:51 +0300 Subject: [PATCH 2/4] add pro links --- src/containers/Toolbar/index.tsx | 26 ++++++++++++++++++++++++-- src/pages/index.tsx | 13 +++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/containers/Toolbar/index.tsx b/src/containers/Toolbar/index.tsx index 267775a4980..191cb4118c7 100644 --- a/src/containers/Toolbar/index.tsx +++ b/src/containers/Toolbar/index.tsx @@ -1,8 +1,10 @@ import React from "react"; -import { Flex, Group, Select, Text } from "@mantine/core"; +import Link from "next/link"; +import { Badge, Flex, Group, Select, Text } from "@mantine/core"; import toast from "react-hot-toast"; import { AiOutlineFullscreen } from "react-icons/ai"; import { AiFillGift } from "react-icons/ai"; +import { BsBoxArrowUpLeft } from "react-icons/bs"; import { FiDownload } from "react-icons/fi"; import { SearchInput } from "src/components/SearchInput"; import { FileFormat } from "src/enums/file.enum"; @@ -73,13 +75,33 @@ export const Toolbar: React.FC<{ isWidget?: boolean }> = ({ isWidget = false }) {!premium && !isWidget && ( setVisible("premium")(true)}> - + Get Premium )} + {premium && !isWidget && ( + + + + + You're invited to try the new editor! + + New + + + + + )} + {!isWidget && ( <> diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 0667f8c2f7a..0127dca8e45 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -137,6 +137,19 @@ const HeroSection = () => ( > GO TO EDITOR + From ea3011b0dbfacae6fa423d3d13c020fcc8cbb139 Mon Sep 17 00:00:00 2001 From: AykutSarac Date: Sat, 17 Feb 2024 14:55:04 +0300 Subject: [PATCH 3/4] update events --- src/components/SearchInput/index.tsx | 2 -- src/containers/Modals/DownloadModal/index.tsx | 6 ++--- src/containers/Modals/NodeModal/index.tsx | 2 ++ src/containers/Toolbar/FileMenu.tsx | 3 +++ src/containers/Toolbar/ToolsMenu.tsx | 3 ++- src/containers/Toolbar/ViewMenu.tsx | 22 ++++--------------- src/containers/Toolbar/ZoomMenu.tsx | 3 ++- src/hooks/useFocusNode.ts | 3 +++ src/lib/utils/gaEvent.ts | 9 ++++++++ src/store/useFile.ts | 5 ++--- src/store/useModal.ts | 4 ++-- src/store/useUser.ts | 6 ++--- 12 files changed, 34 insertions(+), 34 deletions(-) create mode 100644 src/lib/utils/gaEvent.ts diff --git a/src/components/SearchInput/index.tsx b/src/components/SearchInput/index.tsx index 6b76c25e4ab..b41c0832542 100644 --- a/src/components/SearchInput/index.tsx +++ b/src/components/SearchInput/index.tsx @@ -1,7 +1,6 @@ import React from "react"; import { Flex, Text, TextInput } from "@mantine/core"; import { getHotkeyHandler } from "@mantine/hooks"; -import ReactGA from "react-ga4"; import { AiOutlineSearch } from "react-icons/ai"; import { useFocusNode } from "src/hooks/useFocusNode"; @@ -16,7 +15,6 @@ export const SearchInput: React.FC = () => { w={180} value={searchValue} onChange={e => setValue(e.currentTarget.value)} - onFocus={() => ReactGA.event({ action: "focus_node_search", category: "User" })} placeholder="Search Node" onKeyDown={getHotkeyHandler([["Enter", skip]])} leftSection={} diff --git a/src/containers/Modals/DownloadModal/index.tsx b/src/containers/Modals/DownloadModal/index.tsx index 571fdaa7535..3276e21a741 100644 --- a/src/containers/Modals/DownloadModal/index.tsx +++ b/src/containers/Modals/DownloadModal/index.tsx @@ -11,9 +11,9 @@ import { ColorInput, } from "@mantine/core"; import { toBlob, toJpeg, toPng, toSvg } from "html-to-image"; -import ReactGA from "react-ga4"; import toast from "react-hot-toast"; import { FiCopy, FiDownload } from "react-icons/fi"; +import { gaEvent } from "src/lib/utils/gaEvent"; enum Extensions { SVG = "svg", @@ -90,11 +90,11 @@ export const DownloadModal: React.FC = ({ opened, onClose }) => { ]); toast.success("Copied to clipboard"); + gaEvent("click", "clipboard image"); } catch (error) { toast.error("Failed to copy to clipboard"); } finally { toast.dismiss("toastClipboard"); - ReactGA.event({ action: "click_clipboard_image", category: "User" }); onClose(); } }; @@ -111,11 +111,11 @@ export const DownloadModal: React.FC = ({ opened, onClose }) => { }); downloadURI(dataURI, `${fileDetails.filename}.${extension}`); + gaEvent("download", "download graph image", extension); } catch (error) { toast.error("Failed to download image!"); } finally { toast.dismiss("toastDownload"); - ReactGA.event({ action: "click_download_image", category: "User" }); onClose(); } }; diff --git a/src/containers/Modals/NodeModal/index.tsx b/src/containers/Modals/NodeModal/index.tsx index 92e9878dde8..c1cba7679e4 100644 --- a/src/containers/Modals/NodeModal/index.tsx +++ b/src/containers/Modals/NodeModal/index.tsx @@ -3,6 +3,7 @@ import { Modal, Stack, Text, ScrollArea, ModalProps, Button } from "@mantine/cor import { CodeHighlight } from "@mantine/code-highlight"; import Editor from "@monaco-editor/react"; import { VscLock } from "react-icons/vsc"; +import { gaEvent } from "src/lib/utils/gaEvent"; import { isIframe } from "src/lib/utils/widget"; import useConfig from "src/store/useConfig"; import useFile from "src/store/useFile"; @@ -36,6 +37,7 @@ export const NodeModal: React.FC = ({ opened, onClose }) => { if (!isPremium) return; editContents(path!, value, () => { setEditMode(false); + gaEvent("input", "node contents update"); onModalClose(); }); }; diff --git a/src/containers/Toolbar/FileMenu.tsx b/src/containers/Toolbar/FileMenu.tsx index 550bfe6582f..73c3c0e6157 100644 --- a/src/containers/Toolbar/FileMenu.tsx +++ b/src/containers/Toolbar/FileMenu.tsx @@ -1,6 +1,7 @@ import React from "react"; import { Flex, Menu } from "@mantine/core"; import { CgChevronDown } from "react-icons/cg"; +import { gaEvent } from "src/lib/utils/gaEvent"; import useFile from "src/store/useFile"; import useModal from "src/store/useModal"; import * as Styles from "./styles"; @@ -17,6 +18,8 @@ export const FileMenu = () => { a.href = window.URL.createObjectURL(file); a.download = `jsoncrack.${getFormat()}`; a.click(); + + gaEvent("download", "file download"); }; return ( diff --git a/src/containers/Toolbar/ToolsMenu.tsx b/src/containers/Toolbar/ToolsMenu.tsx index 2e8cb784db8..2bc0e406917 100644 --- a/src/containers/Toolbar/ToolsMenu.tsx +++ b/src/containers/Toolbar/ToolsMenu.tsx @@ -3,6 +3,7 @@ import { Menu, Flex } from "@mantine/core"; import { CgChevronDown } from "react-icons/cg"; import { SiJsonwebtokens } from "react-icons/si"; import { VscSearchFuzzy, VscJson, VscGroupByRefType } from "react-icons/vsc"; +import { gaEvent } from "src/lib/utils/gaEvent"; import useModal from "src/store/useModal"; import * as Styles from "./styles"; @@ -12,7 +13,7 @@ export const ToolsMenu = () => { return ( - + gaEvent("click", "tools menu")}> Tools diff --git a/src/containers/Toolbar/ViewMenu.tsx b/src/containers/Toolbar/ViewMenu.tsx index 04667cc6220..366a207f318 100644 --- a/src/containers/Toolbar/ViewMenu.tsx +++ b/src/containers/Toolbar/ViewMenu.tsx @@ -1,12 +1,12 @@ import React from "react"; import { Menu, Flex, Text, SegmentedControl } from "@mantine/core"; import { useHotkeys } from "@mantine/hooks"; -import ReactGA from "react-ga4"; import toast from "react-hot-toast"; import { CgChevronDown } from "react-icons/cg"; import { VscExpandAll, VscCollapseAll, VscTarget } from "react-icons/vsc"; import { ViewMode } from "src/enums/viewMode.enum"; import useToggleHide from "src/hooks/useToggleHide"; +import { gaEvent } from "src/lib/utils/gaEvent"; import { getNextDirection } from "src/lib/utils/graph/getNextDirection"; import useConfig from "src/store/useConfig"; import useGraph from "src/store/useGraph"; @@ -65,7 +65,7 @@ export const ViewMenu = () => { return ( - + gaEvent("click", "view menu")}> View @@ -88,14 +88,7 @@ export const ViewMenu = () => { { - toggleDirection(); - ReactGA.event({ - action: "toggle_layout_direction", - category: "User", - label: "Tools", - }); - }} + onClick={toggleDirection} leftSection={} rightSection={ @@ -107,14 +100,7 @@ export const ViewMenu = () => { { - toggleExpandCollapseGraph(); - ReactGA.event({ - action: "toggle_collapse_nodes", - category: "User", - label: "Tools", - }); - }} + onClick={toggleExpandCollapseGraph} leftSection={graphCollapsed ? : } rightSection={ diff --git a/src/containers/Toolbar/ZoomMenu.tsx b/src/containers/Toolbar/ZoomMenu.tsx index dda6d6b6099..1f09c9fcdcd 100644 --- a/src/containers/Toolbar/ZoomMenu.tsx +++ b/src/containers/Toolbar/ZoomMenu.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Menu, Flex, Input, Text } from "@mantine/core"; import { getHotkeyHandler, useHotkeys } from "@mantine/hooks"; import { CgChevronDown } from "react-icons/cg"; +import { gaEvent } from "src/lib/utils/gaEvent"; import useGraph from "src/store/useGraph"; import * as Styles from "./styles"; @@ -26,7 +27,7 @@ export const ZoomMenu = () => { return ( - + gaEvent("click", "zoom menu")}> {Math.round(zoomFactor * 100)}% diff --git a/src/hooks/useFocusNode.ts b/src/hooks/useFocusNode.ts index 24b92e3eccb..98289dd700d 100644 --- a/src/hooks/useFocusNode.ts +++ b/src/hooks/useFocusNode.ts @@ -1,5 +1,6 @@ import React from "react"; import { useDebouncedValue } from "@mantine/hooks"; +import { gaEvent } from "src/lib/utils/gaEvent"; import { searchQuery, cleanupHighlight, highlightMatchedNodes } from "src/lib/utils/graph/search"; import useGraph from "src/store/useGraph"; @@ -37,6 +38,8 @@ export const useFocusNode = () => { setSelectedNode(0); setNodeCount(0); } + + gaEvent("input", "search node in graph"); }, [selectedNode, debouncedValue, value, viewPort]); return [value, setValue, skip, nodeCount, selectedNode] as const; diff --git a/src/lib/utils/gaEvent.ts b/src/lib/utils/gaEvent.ts new file mode 100644 index 00000000000..b9909f46243 --- /dev/null +++ b/src/lib/utils/gaEvent.ts @@ -0,0 +1,9 @@ +import ReactGA4 from "react-ga4"; + +export const gaEvent = (category: string, action: string, label?: string) => { + ReactGA4.event({ + category, + action, + label, + }); +}; diff --git a/src/store/useFile.ts b/src/store/useFile.ts index 93b37f67a01..602d422b589 100644 --- a/src/store/useFile.ts +++ b/src/store/useFile.ts @@ -1,11 +1,11 @@ import debounce from "lodash.debounce"; import _get from "lodash.get"; import _set from "lodash.set"; -import ReactGA from "react-ga4"; import { toast } from "react-hot-toast"; import { create } from "zustand"; import { defaultJson } from "src/constants/data"; import { FileFormat } from "src/enums/file.enum"; +import { gaEvent } from "src/lib/utils/gaEvent"; import { contentToJson, jsonToContent } from "src/lib/utils/json/jsonAdapter"; import { isIframe } from "src/lib/utils/widget"; import { documentSvc } from "src/services/document.service"; @@ -111,8 +111,7 @@ const useFile = create()((set, get) => ({ const jsonContent = await jsonToContent(JSON.stringify(contentJson, null, 2), format); get().setContents({ contents: jsonContent }); - - ReactGA.event({ action: "change_data_format", category: "User" }); + gaEvent("input", "file format change"); } catch (error) { get().clear(); console.warn("The content was unable to be converted, so it was cleared instead."); diff --git a/src/store/useModal.ts b/src/store/useModal.ts index 305ecd1adb3..ab4d2d65e11 100644 --- a/src/store/useModal.ts +++ b/src/store/useModal.ts @@ -1,6 +1,6 @@ -import ReactGA from "react-ga4"; import { create } from "zustand"; import { Modal } from "src/containers/Modals"; +import { gaEvent } from "src/lib/utils/gaEvent"; import useUser from "./useUser"; type ModalState = { @@ -43,7 +43,7 @@ const useModal = create()(set => ({ return set({ premium: true }); } - if (visible) ReactGA.event({ category: "Modal View", action: `modal_view_${modal}` }); + if (visible) gaEvent("modal", `open ${modal}`); set({ [modal]: visible }); }, })); diff --git a/src/store/useUser.ts b/src/store/useUser.ts index 4d1df30d974..86f49e6f873 100644 --- a/src/store/useUser.ts +++ b/src/store/useUser.ts @@ -1,8 +1,8 @@ import { Session, User } from "@supabase/supabase-js"; -import ReactGA from "react-ga4"; import toast from "react-hot-toast"; import { create } from "zustand"; import { supabase } from "src/lib/api/supabase"; +import { gaEvent } from "src/lib/utils/gaEvent"; interface UserActions { logout: () => void; @@ -38,12 +38,10 @@ const useUser = create()(set => ({ premiumCancelled: !!data.cancelled, orgAdmin: data.orgAdmin, }); - - ReactGA.set({ tier: data.premium ? "premium" : "free" }); } + gaEvent("engagement", "login"); set({ user: session.user, isAuthenticated: true }); - ReactGA.set({ userId: session.user.id }); }); }, logout: async () => { From 2902ce08c698fdc7433d18cc682a1a17fa1556ca Mon Sep 17 00:00:00 2001 From: AykutSarac Date: Sun, 18 Feb 2024 12:10:41 +0300 Subject: [PATCH 4/4] update cancel sub docs --- src/pages/legal/subscription-refund.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/legal/subscription-refund.tsx b/src/pages/legal/subscription-refund.tsx index 24802677bff..a5d097f0fe3 100644 --- a/src/pages/legal/subscription-refund.tsx +++ b/src/pages/legal/subscription-refund.tsx @@ -35,9 +35,7 @@ const SubscriptionRefund = () => { To cancel your subscription, follow these steps: Log in to your account. - - Click on your account name located at the bottom left of the editor. - + Click on the account icon at the top right of the editor. Select the "Unsubscribe" option. Please note that subscription cancellations made after the initial 3-day period are