Skip to content

Commit

Permalink
Merge branch 'AykutSarac:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
louyongjiu authored Feb 21, 2024
2 parents 83b021b + 2902ce0 commit 7bc0d7c
Show file tree
Hide file tree
Showing 18 changed files with 76 additions and 43 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

<img width="100%" alt="booking-screen" src="./public/assets/jsoncrack-screenshot.jpeg">

# 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.

Expand Down
2 changes: 0 additions & 2 deletions src/components/SearchInput/index.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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={<AiOutlineSearch />}
Expand Down
6 changes: 3 additions & 3 deletions src/containers/Modals/DownloadModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -90,11 +90,11 @@ export const DownloadModal: React.FC<ModalProps> = ({ 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();
}
};
Expand All @@ -111,11 +111,11 @@ export const DownloadModal: React.FC<ModalProps> = ({ 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();
}
};
Expand Down
2 changes: 2 additions & 0 deletions src/containers/Modals/NodeModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -36,6 +37,7 @@ export const NodeModal: React.FC<ModalProps> = ({ opened, onClose }) => {
if (!isPremium) return;
editContents(path!, value, () => {
setEditMode(false);
gaEvent("input", "node contents update");
onModalClose();
});
};
Expand Down
3 changes: 3 additions & 0 deletions src/containers/Toolbar/FileMenu.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -17,6 +18,8 @@ export const FileMenu = () => {
a.href = window.URL.createObjectURL(file);
a.download = `jsoncrack.${getFormat()}`;
a.click();

gaEvent("download", "file download");
};

return (
Expand Down
3 changes: 2 additions & 1 deletion src/containers/Toolbar/ToolsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -12,7 +13,7 @@ export const ToolsMenu = () => {
return (
<Menu shadow="md" withArrow>
<Menu.Target>
<Styles.StyledToolElement>
<Styles.StyledToolElement onClick={() => gaEvent("click", "tools menu")}>
<Flex align="center" gap={3}>
Tools <CgChevronDown />
</Flex>
Expand Down
22 changes: 4 additions & 18 deletions src/containers/Toolbar/ViewMenu.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -65,7 +65,7 @@ export const ViewMenu = () => {
return (
<Menu shadow="md" closeOnItemClick={false} withArrow>
<Menu.Target>
<Styles.StyledToolElement>
<Styles.StyledToolElement onClick={() => gaEvent("click", "view menu")}>
<Flex align="center" gap={3}>
View <CgChevronDown />
</Flex>
Expand All @@ -88,14 +88,7 @@ export const ViewMenu = () => {
<Menu.Item
mt="xs"
fz={12}
onClick={() => {
toggleDirection();
ReactGA.event({
action: "toggle_layout_direction",
category: "User",
label: "Tools",
});
}}
onClick={toggleDirection}
leftSection={<Styles.StyledFlowIcon rotate={rotateLayout(direction || "RIGHT")} />}
rightSection={
<Text ml="md" fz={10} c="dimmed">
Expand All @@ -107,14 +100,7 @@ export const ViewMenu = () => {
</Menu.Item>
<Menu.Item
fz={12}
onClick={() => {
toggleExpandCollapseGraph();
ReactGA.event({
action: "toggle_collapse_nodes",
category: "User",
label: "Tools",
});
}}
onClick={toggleExpandCollapseGraph}
leftSection={graphCollapsed ? <VscExpandAll /> : <VscCollapseAll />}
rightSection={
<Text ml="md" fz={10} c="dimmed">
Expand Down
3 changes: 2 additions & 1 deletion src/containers/Toolbar/ZoomMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -26,7 +27,7 @@ export const ZoomMenu = () => {
return (
<Menu shadow="md" trigger="click" closeOnItemClick={false} withArrow>
<Menu.Target>
<Styles.StyledToolElement>
<Styles.StyledToolElement onClick={() => gaEvent("click", "zoom menu")}>
<Flex gap={4} align="center">
{Math.round(zoomFactor * 100)}%
<CgChevronDown />
Expand Down
26 changes: 24 additions & 2 deletions src/containers/Toolbar/index.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -73,13 +75,33 @@ export const Toolbar: React.FC<{ isWidget?: boolean }> = ({ isWidget = false })
<Group gap="xs" justify="right" w="100%" style={{ flexWrap: "nowrap" }}>
{!premium && !isWidget && (
<Styles.StyledToolElement onClick={() => setVisible("premium")(true)}>
<Text display="flex" c="teal" fz="xs" fw="bold" style={{ textAlign: "center", gap: 4 }}>
<Text display="flex" c="teal" fz="xs" fw={600} style={{ textAlign: "center", gap: 4 }}>
<AiFillGift size="18" />
Get Premium
</Text>
</Styles.StyledToolElement>
)}

{premium && !isWidget && (
<Link href="https://pro.jsoncrack.com" target="_blank" passHref>
<Styles.StyledToolElement>
<Text
display="flex"
c="teal"
fz="xs"
fw={600}
style={{ textAlign: "center", gap: 8, alignItems: "center" }}
>
<BsBoxArrowUpLeft />
You&apos;re invited to try the new editor!
<Badge size="xs" variant="light" color="teal">
New
</Badge>
</Text>
</Styles.StyledToolElement>
</Link>
)}

<SearchInput />
{!isWidget && (
<>
Expand Down
3 changes: 3 additions & 0 deletions src/hooks/useFocusNode.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions src/lib/utils/gaEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import ReactGA4 from "react-ga4";

export const gaEvent = (category: string, action: string, label?: string) => {
ReactGA4.event({
category,
action,
label,
});
};
2 changes: 1 addition & 1 deletion src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function JsonCrack({
return (
<>
<Head>
<title>JSON Crack | Visualize Instantly Into Graphs</title>
<title>JSON Crack | More Than a JSON Editor</title>
</Head>
<SessionContextProvider supabaseClient={supabase}>
<MantineProvider theme={mantineTheme}>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
});

Expand Down
15 changes: 14 additions & 1 deletion src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,19 @@ const HeroSection = () => (
>
GO TO EDITOR
</Button>
<Button
href="https://pro.jsoncrack.com"
target="_blank"
variant="outline"
color="yellow"
component={Link}
fw="bold"
size="xl"
visibleFrom="md"
prefetch={false}
>
GET PRO+
</Button>
</Group>
</Stack>
</StyledHeroSectionBody>
Expand All @@ -147,7 +160,7 @@ export const HomePage = () => {
return (
<Layout>
<Head>
<title>JSON Crack | Visualize Instantly Into Graphs</title>
<title>JSON Crack | More Than a JSON Editor</title>
</Head>
<HeroSection />
</Layout>
Expand Down
4 changes: 1 addition & 3 deletions src/pages/legal/subscription-refund.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ const SubscriptionRefund = () => {
To cancel your subscription, follow these steps:
<List type="ordered" my="lg">
<List.Item>Log in to your account.</List.Item>
<List.Item>
Click on your account name located at the bottom left of the editor.
</List.Item>
<List.Item>Click on the account icon at the top right of the editor.</List.Item>
<List.Item>Select the &quot;Unsubscribe&quot; option.</List.Item>
</List>
Please note that subscription cancellations made after the initial 3-day period are
Expand Down
5 changes: 2 additions & 3 deletions src/store/useFile.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -111,8 +111,7 @@ const useFile = create<FileStates & JsonActions>()((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.");
Expand Down
4 changes: 2 additions & 2 deletions src/store/useModal.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -43,7 +43,7 @@ const useModal = create<ModalState & ModalActions>()(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 });
},
}));
Expand Down
6 changes: 2 additions & 4 deletions src/store/useUser.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -38,12 +38,10 @@ const useUser = create<UserStates & UserActions>()(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 () => {
Expand Down

0 comments on commit 7bc0d7c

Please sign in to comment.