Browse Source

refactor codebase

AykutSarac 2 years ago
parent
commit
6eda021cbd

BIN
public/assets/bunny.png


+ 1 - 2
src/components/ErrorContainer/index.tsx

@@ -12,14 +12,13 @@ const StyledErrorExpand = styled.div<{ error: boolean }>`
   display: flex;
   width: 100%;
   padding: 4px 16px;
-  height: 28px;
+  height: 36px;
   border-radius: 0;
   justify-content: space-between;
   align-items: center;
   color: ${({ theme, error }) => (error ? theme.TEXT_DANGER : theme.TEXT_POSITIVE)};
   pointer-events: ${({ error }) => !error && "none"};
   background: ${({ theme }) => theme.BACKGROUND_SECONDARY};
-  box-shadow: 0 1px 0px ${({ theme }) => theme.BACKGROUND_TERTIARY};
 `;
 
 const StyledTitle = styled.span`

+ 2 - 3
src/components/Graph/index.tsx

@@ -4,7 +4,6 @@ import { Canvas, Edge, EdgeProps, ElkRoot, NodeProps } from "reaflow";
 import { CustomNode } from "src/components/CustomNode";
 import useGraph from "src/store/useGraph";
 import useUser from "src/store/useUser";
-import { getNodePath } from "src/utils/getNodePath";
 import styled from "styled-components";
 import { Loading } from "../Loading";
 import { ErrorView } from "./ErrorView";
@@ -89,10 +88,10 @@ export const Graph = ({ isWidget = false, openNodeModal }: GraphProps) => {
   const handleNodeClick = React.useCallback(
     (_: React.MouseEvent<SVGElement>, data: NodeData) => {
       if (setSelectedNode)
-        setSelectedNode({ nodeData: data, path: getNodePath(nodes, edges, data.id) });
+        setSelectedNode({ nodeData: data });
       if (openNodeModal) openNodeModal();
     },
-    [edges, nodes, openNodeModal, setSelectedNode]
+    [openNodeModal, setSelectedNode]
   );
 
   const onInit = React.useCallback(

+ 6 - 8
src/components/Sidebar/index.tsx

@@ -26,6 +26,8 @@ const StyledSidebar = styled.div`
   background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
   padding: 4px;
   border-right: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
+  width: 50px;
+  text-align: center;
 
   @media only screen and (max-width: 768px) {
     flex-direction: row;
@@ -34,18 +36,14 @@ const StyledSidebar = styled.div`
 `;
 
 const StyledElement = styled.button`
-  position: relative;
-  display: flex;
-  justify-content: center;
-  text-align: center;
+  height: 45px;
   font-size: 24px;
   font-weight: 600;
-  width: fit-content;
-  color: ${({ theme }) => theme.SIDEBAR_ICONS};
   cursor: pointer;
+  color: ${({ theme }) => theme.SIDEBAR_ICONS};
+  width: 100%;
 
   svg {
-    padding: 12px 8px;
     vertical-align: middle;
   }
 
@@ -114,8 +112,8 @@ const StyledBottomWrapper = styled.nav`
 
 const StyledLogo = styled.a`
   color: ${({ theme }) => theme.FULL_WHITE};
-  padding: 8px 4px;
   border-bottom: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
+  width: 100%;
 
   @media only screen and (max-width: 768px) {
     border-bottom: 0;

+ 3 - 11
src/constants/globalStyle.ts

@@ -10,15 +10,10 @@ const monaSans = localFont({
 
 const GlobalStyle = createGlobalStyle`
   html, body {
-    margin: 0;
-    padding: 0;
-    box-sizing: border-box;
-    color: ${({ theme }) => theme.FULL_WHITE};
-    font-family: var(--mona-sans);
+    color: ${({ theme }) => theme.FULL_WHITE} !important;
     font-weight: 400;
     font-size: 16px;
-    height: 100%;
-    background-color: #000000;
+    background-color: #000000 !important;
     background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 800 800'%3E%3Cg fill-opacity='0.3'%3E%3Ccircle fill='%23000000' cx='400' cy='400' r='600'/%3E%3Ccircle fill='%23110718' cx='400' cy='400' r='500'/%3E%3Ccircle fill='%23220e30' cx='400' cy='400' r='400'/%3E%3Ccircle fill='%23331447' cx='400' cy='400' r='300'/%3E%3Ccircle fill='%23441b5f' cx='400' cy='400' r='200'/%3E%3Ccircle fill='%23552277' cx='400' cy='400' r='100'/%3E%3C/g%3E%3C/svg%3E");
     background-attachment: fixed;
     background-size: cover;
@@ -46,12 +41,9 @@ const GlobalStyle = createGlobalStyle`
     vertical-align: text-top;
   }
 
-  
   a {
-    text-decoration: none;
     color: unset;
-    padding: 0;
-    margin: 0;
+    text-decoration: none;
   }
 
   button {

+ 1 - 1
src/containers/Editor/LiveEditor/Tools.tsx

@@ -13,7 +13,7 @@ export const StyledTools = styled.div`
   align-items: center;
   gap: 4px;
   flex-direction: row-reverse;
-  height: 28px;
+  height: 36px;
   padding: 4px 16px;
   background: ${({ theme }) => theme.BACKGROUND_PRIMARY};
   color: ${({ theme }) => theme.SILVER};

+ 6 - 1
src/containers/Home/styles.tsx

@@ -126,6 +126,8 @@ export const StyledTitle = styled.h1`
   font-weight: 900;
   margin: 0;
   font-size: min(6vw, 86px);
+  color: white;
+  font-family: var(--mona-sans);
 
   @media only screen and (max-width: 768px) {
     font-size: 2.5rem;
@@ -329,6 +331,7 @@ export const StyledTabsWrapper = styled.div`
   gap: 10px;
   padding: 8px 8px;
   padding-bottom: 0;
+  height: 34px;
 
   pre {
     border-top: 2px solid ${({ theme }) => theme.PRIMARY};
@@ -341,13 +344,15 @@ export const StyledTab = styled.button<{ active?: boolean }>`
   border: 2px solid ${({ theme, active }) => (active ? theme.PRIMARY : "transparent")};
   border-bottom: 0;
   margin-bottom: -2px;
-  padding: 8px 16px;
+  padding: 4px 8px;
   min-width: 80px;
   max-width: 150px;
   color: ${({ theme, active }) => (active ? theme.INTERACTIVE_ACTIVE : theme.INTERACTIVE_NORMAL)};
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;
+  font-size: 12px;
+  font-weight: 600;
 
   &:hover {
     color: ${({ theme, active }) => !active && theme.INTERACTIVE_HOVER};

+ 4 - 0
src/containers/ModalController/index.tsx

@@ -9,6 +9,7 @@ import { SettingsModal } from "src/containers/Modals/SettingsModal";
 import { ShareModal } from "src/containers/Modals/ShareModal";
 import useModal from "src/store/useModal";
 import { shallow } from "zustand/shallow";
+import { PremiumModal } from "../Modals/PremiumModal";
 
 export const ModalController = () => {
   const setVisible = useModal(state => state.setVisible);
@@ -22,6 +23,7 @@ export const ModalController = () => {
     accountModal,
     loginModal,
     shareModal,
+    premiumModal,
   ] = useModal(
     state => [
       state.import,
@@ -32,6 +34,7 @@ export const ModalController = () => {
       state.account,
       state.login,
       state.share,
+      state.premium,
     ],
     shallow
   );
@@ -44,6 +47,7 @@ export const ModalController = () => {
       <SettingsModal opened={settingsModal} onClose={() => setVisible("settings")(false)} />
       <CloudModal opened={cloudModal} onClose={() => setVisible("cloud")(false)} />
       <AccountModal opened={accountModal} onClose={() => setVisible("account")(false)} />
+      <PremiumModal opened={premiumModal} onClose={() => setVisible("premium")(false)} />
       <LoginModal opened={loginModal} onClose={() => setVisible("login")(false)} />
       <ShareModal opened={shareModal} onClose={() => setVisible("share")(false)} />
     </>

+ 9 - 5
src/containers/Modals/AccountModal/index.tsx

@@ -47,10 +47,6 @@ export const AccountModal: React.FC<ModalProps> = ({ opened, onClose }) => {
   const isPremium = useUser(state => state.isPremium());
   const logout = useUser(state => state.logout);
 
-  const onImgFail = (e: React.SyntheticEvent<HTMLImageElement>) => {
-    e.currentTarget.setAttribute("src", `https://ui-avatars.com/api/?name=${user?.name}`);
-  };
-
   return (
     <Modal title="Account" opened={opened} onClose={onClose} centered>
       <StyledTitle>Hello, {user?.name}!</StyledTitle>
@@ -68,7 +64,15 @@ export const AccountModal: React.FC<ModalProps> = ({ opened, onClose }) => {
           <Grid.Col span={6}>
             <StyledContainer>
               ACCOUNT STATUS
-              <div>{isPremium ? <Badge>Premium</Badge> : <Badge color="gray">Free</Badge>}</div>
+              <div>
+                {isPremium ? (
+                  <Badge>Premium</Badge>
+                ) : (
+                  <Badge variant="outline" color="gray">
+                    Free
+                  </Badge>
+                )}
+              </div>
             </StyledContainer>
           </Grid.Col>
           <Grid.Col span={6}>

+ 35 - 0
src/containers/Modals/PremiumModal/index.tsx

@@ -0,0 +1,35 @@
+import React from "react";
+import Link from "next/link";
+import { Modal, Group, Button, Divider, ModalProps, Title, Image } from "@mantine/core";
+import { IoRocketSharp } from "react-icons/io5";
+
+export const PremiumModal: React.FC<ModalProps> = ({ opened, onClose }) => {
+  return (
+    <Modal title="JSON Crack Premium" opened={opened} onClose={onClose} centered>
+      <Group py="sm">
+        <Title
+          variant="gradient"
+          order={3}
+          gradient={{ from: "yellow", to: "hotpink" }}
+          strikethrough
+          align="center"
+        >
+          Enhance your experience, unlock full benefits of JSON Crack!
+        </Title>
+        <Image mx="auto" src="assets/bunny.png" width={150} alt="bunny" />
+      </Group>
+      <Divider py="xs" />
+      <Group position="center">
+        <Link href="/pricing" target="_blank" rel="noreferrer">
+          <Button
+            variant="gradient"
+            gradient={{ from: "yellow", to: "hotpink" }}
+            leftIcon={<IoRocketSharp />}
+          >
+            UPGRADE TO PREMIUM!
+          </Button>
+        </Link>
+      </Group>
+    </Modal>
+  );
+};

+ 50 - 32
src/pages/_app.tsx

@@ -1,5 +1,6 @@
 import React from "react";
 import type { AppProps } from "next/app";
+import localFont from "next/font/local";
 import { MantineProvider } from "@mantine/core";
 import { init } from "@sentry/nextjs";
 import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@@ -9,7 +10,7 @@ import GlobalStyle from "src/constants/globalStyle";
 import { darkTheme, lightTheme } from "src/constants/theme";
 import { ModalController } from "src/containers/ModalController";
 import useStored from "src/store/useStored";
-import { ThemeProvider, useTheme } from "styled-components";
+import { ThemeProvider } from "styled-components";
 
 if (process.env.NODE_ENV !== "development") {
   init({
@@ -28,8 +29,14 @@ const queryClient = new QueryClient({
   },
 });
 
+const monaSans = localFont({
+  src: "../pages/Mona-Sans.woff2",
+  variable: "--mona-sans",
+  display: "swap",
+  fallback: ["Arial, Helvetica, sans-serif", "Tahoma, Verdana, sans-serif"],
+});
+
 function JsonCrack({ Component, pageProps }: AppProps) {
-  const theme = useTheme();
   const [isReady, setReady] = React.useState(false);
   const lightmode = useStored(state => state.lightmode);
 
@@ -41,35 +48,46 @@ function JsonCrack({ Component, pageProps }: AppProps) {
     return (
       <QueryClientProvider client={queryClient}>
         <GoogleAnalytics />
-        <MantineProvider
-          theme={{
-            colorScheme: lightmode ? "light" : "dark",
-            components: {
-              Divider: {
-                styles: () => ({
-                  root: {
-                    borderTopColor: "#4D4D4D !important",
-                  },
-                }),
-              },
-              Modal: {
-                styles: theme => ({
-                  title: {
-                    fontWeight: 700,
-                  },
-                  header: {
-                    backgroundColor: theme.colorScheme === "dark" ? "#36393E" : "#FFFFFF",
-                  },
-                  body: {
-                    backgroundColor: theme.colorScheme === "dark" ? "#36393E" : "#FFFFFF",
-                  },
-                }),
+        <ThemeProvider theme={lightmode ? lightTheme : darkTheme}>
+          <GlobalStyle />
+          <MantineProvider
+            withGlobalStyles
+            withNormalizeCSS
+            withCSSVariables
+            theme={{
+              colorScheme: lightmode ? "light" : "dark",
+              fontFamily: monaSans.style.fontFamily,
+              components: {
+                Divider: {
+                  styles: () => ({
+                    root: {
+                      borderTopColor: "#4D4D4D !important",
+                    },
+                  }),
+                },
+                Modal: {
+                  styles: theme => ({
+                    title: {
+                      fontWeight: 700,
+                    },
+                    header: {
+                      backgroundColor: theme.colorScheme === "dark" ? "#36393E" : "#FFFFFF",
+                    },
+                    body: {
+                      backgroundColor: theme.colorScheme === "dark" ? "#36393E" : "#FFFFFF",
+                    },
+                  }),
+                },
+                Button: {
+                  styles: theme => ({
+                    inner: {
+                      fontWeight: 700,
+                    },
+                  }),
+                },
               },
-            },
-          }}
-        >
-          <ThemeProvider theme={lightmode ? lightTheme : darkTheme}>
-            <GlobalStyle />
+            }}
+          >
             <Component {...pageProps} />
             <ModalController />
             <Toaster
@@ -86,8 +104,8 @@ function JsonCrack({ Component, pageProps }: AppProps) {
                 },
               }}
             />
-          </ThemeProvider>
-        </MantineProvider>
+          </MantineProvider>
+        </ThemeProvider>
       </QueryClientProvider>
     );
 }

+ 0 - 3
src/pages/editor.tsx

@@ -9,8 +9,6 @@ import useJson from "src/store/useJson";
 import useUser from "src/store/useUser";
 import styled from "styled-components";
 
-// import { AdTest } from "src/components/AdTest";
-
 export const StyledPageWrapper = styled.div`
   display: flex;
   flex-direction: row;
@@ -60,7 +58,6 @@ const EditorPage: React.FC = () => {
         </StyledEditorWrapper>
       </StyledPageWrapper>
       <BottomBar />
-      {/* <AdTest /> */}
     </StyledEditorWrapper>
   );
 };

+ 2 - 2
src/store/useGraph.tsx

@@ -38,12 +38,12 @@ interface GraphActions {
   zoomIn: () => void;
   zoomOut: () => void;
   centerView: () => void;
-  setSelectedNode: ({ nodeData, path }: { nodeData: NodeData; path: string }) => void;
+  setSelectedNode: ({ nodeData}: { nodeData: NodeData }) => void;
 }
 
 const useGraph = create<Graph & GraphActions>((set, get) => ({
   ...initialStates,
-  setSelectedNode: ({ nodeData, path }) => set({ selectedNode: nodeData, path }),
+  setSelectedNode: ({ nodeData}) => set({ selectedNode: nodeData}),
   setGraph: (data, options) => {
     const { nodes, edges } = parser(data ?? useJson.getState().json);
 

+ 11 - 0
src/store/useJson.tsx

@@ -5,11 +5,14 @@ import { defaultJson } from "src/constants/data";
 import { saveJson as saveJsonDB } from "src/services/db/json";
 import useGraph from "src/store/useGraph";
 import { Json } from "src/typings/altogic";
+import { fixJSON } from "src/utils/repairJson";
 import { create } from "zustand";
+import useUser from "./useUser";
 
 interface JsonActions {
   setJson: (json: string) => void;
   getJson: () => string;
+  repairJSON: (jsonstring: string) => void;
   getHasChanges: () => boolean;
   fetchJson: (jsonId: string | string[] | undefined) => void;
   setError: (hasError: boolean) => void;
@@ -39,6 +42,14 @@ const useJson = create<JsonStates & JsonActions>()((set, get) => ({
   ...initialStates,
   getJson: () => get().json,
   getHasChanges: () => get().hasChanges,
+  repairJSON: jsonstring => {
+    useUser.getState().validatePremium(() => {
+      const parsed = fixJSON(jsonstring);
+      console.log(parsed);
+
+      get().setJson(parsed);
+    });
+  },
   fetchJson: async jsonId => {
     const isURL = new RegExp(
       /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/

+ 1 - 0
src/store/useModal.tsx

@@ -16,6 +16,7 @@ const initialStates = {
   settings: false,
   share: false,
   login: false,
+  premium: false,
 };
 
 type ModalType = keyof typeof initialStates;

+ 28 - 0
src/store/useUser.tsx

@@ -11,6 +11,7 @@ interface UserActions {
   checkSession: () => void;
   isPremium: () => boolean;
   tokenAuth: () => Promise<void>;
+  validatePremium: (cb: () => void) => void;
 }
 
 const initialStates = {
@@ -50,10 +51,29 @@ const useUser = create<UserStates & UserActions>()((set, get) => ({
     }
   },
   checkSession: async () => {
+    if (process.env.NODE_ENV === "development") {
+      return set({
+        user: {
+          _id: "0",
+          provider: "altogic",
+          providerUserId: "1",
+          email: "[email protected]",
+          name: "JSON Crack",
+          profilePicture: "",
+          signUpAt: "2022-12-04T11:07:32.000Z",
+          lastLoginAt: "2023-03-12T14:17:03.146Z",
+          type: 1,
+          updatedAt: "2022-12-30T10:56:29.772Z",
+        },
+        isAuthenticated: true,
+      });
+    }
+
     const currentSession = altogic.auth.getSession();
 
     if (currentSession) {
       const dbUser = await altogic.auth.getUserFromDB();
+      console.log(dbUser);
 
       altogic.auth.setSession(currentSession);
       set({ user: dbUser.user as any, isAuthenticated: true });
@@ -66,6 +86,14 @@ const useUser = create<UserStates & UserActions>()((set, get) => ({
       }
     }
   },
+  validatePremium: callback => {
+    if (get().isAuthenticated) {
+      if (!get().isPremium()) return useModal.getState().setVisible("premium")(true);
+      return callback();
+    } else {
+      return useModal.getState().setVisible("account")(true);
+    }
+  },
 }));
 
 export default useUser;

+ 3 - 2
src/typings/altogic.ts

@@ -5,8 +5,9 @@ export interface User {
   email: string;
   name: string;
   profilePicture: string;
-  signUpAt: Date;
-  lastLoginAt: Date;
+  signUpAt: string;
+  lastLoginAt: string;
+  updatedAt: string;
   type: 0 | 1;
 }
 

+ 73 - 0
src/utils/repairJson.ts

@@ -0,0 +1,73 @@
+import { toast } from "react-hot-toast";
+
+/**
+ * The ISC License
+ * Copyright (c) 2023 by Aykut Saraç
+ * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+export function fixJSON(jsonString: string) {
+  try {
+
+    // Remove comments
+    jsonString = jsonString.replace(/(\/\/[^\n]*|\/\*[\s\S]*?\*\/)/g, "");
+
+    // Remove JSONP notation
+    jsonString = jsonString.replace(/^\s*callback\((.*)\)\s*$/, "$1");
+
+    // Remove escape characters from escaped strings
+    jsonString = jsonString.replace(/\\"/g, '"');
+
+    // Remove MongoDB data types
+    jsonString = jsonString.replace(/(ISODate|NumberLong)\s*\(\s*("[^"]+"|\d+)\s*\)/g, "$2");
+
+    // Replace Python constants with JSON equivalents
+    jsonString = jsonString.replace(/(?<=^|\s)(None|True|False)(?=$|\s|,|\]|\})/g, match =>
+      match.toLowerCase() === "none" ? "null" : match.toLowerCase()
+    );
+
+    // Concatenate strings
+    jsonString = jsonString.replace(
+      /"([^"\\]*(?:\\.[^"\\]*)*)"(\s*\+\s*"([^"\\]*(?:\\.[^"\\]*)*)")+("([^"\\]*(?:\\.[^"\\]*)*)"|$)/g,
+      (match, p1, _, p2, p3, p4) => `"${p1}${p2}${p3}${p4}"`
+    );
+
+    // Add missing quotes around keys, replace single quotes with double quotes, replace special quote characters and white space characters
+    jsonString = jsonString.replace(/(['"])?([a-zA-Z0-9_]+)(['"])?:/g, '"$2": ');
+    jsonString = jsonString.replace(/'/g, '"');
+    jsonString = jsonString.replace(/[“”]/g, '"');
+    jsonString = jsonString.replace(/\s/g, " ");
+
+    // Add missing commas, strip trailing commas, and add double-quoted property names
+    jsonString = jsonString.replace(/([,{\[}\]])\s*(['"])?([a-zA-Z0-9_]+)(['"])?\s*:/g, '$1"$3": ');
+    jsonString = jsonString.replace(/,(\s*[}\]])/g, "$1");
+
+    // Add missing closing brackets
+    const brackets = [];
+    jsonString.split("").forEach(char => {
+      if (char === "{" || char === "[") {
+        brackets.push(char as never);
+      } else if (char === "}" || char === "]") {
+        if (
+          brackets.length > 0 &&
+          ((char === "}" && brackets[brackets.length - 1] === "{") ||
+            (char === "]" && brackets[brackets.length - 1] === "["))
+        ) {
+          brackets.pop();
+        } else {
+          jsonString = jsonString.slice(0, -1);
+        }
+      }
+    });
+    while (brackets.length > 0) {
+      const lastBracket = brackets.pop();
+      jsonString += lastBracket === "{" ? "}" : "]";
+    }
+
+    // Parse and return the fixed JSON
+    return JSON.stringify(JSON.parse(jsonString), null, 2);
+  } catch (error) {
+    toast.error("Unable to fix this JSON!");
+    return jsonString;
+  }
+}