mirror of
https://github.com/AykutSarac/jsoncrack.com.git
synced 2025-01-27 15:22:56 +08:00
feat: overall improvements
# updated login flow # removed sign-up # use next-seo
This commit is contained in:
parent
a679149d03
commit
3582c1e063
@ -37,6 +37,7 @@
|
||||
"jxon": "2.0.0-beta.5",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"next": "14.2.3",
|
||||
"next-seo": "^6.5.0",
|
||||
"react": "^18.3.1",
|
||||
"react-compare-slider": "^3.1.0",
|
||||
"react-countup": "^6.5.3",
|
||||
|
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@ -80,6 +80,9 @@ importers:
|
||||
next:
|
||||
specifier: 14.2.3
|
||||
version: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next-seo:
|
||||
specifier: ^6.5.0
|
||||
version: 6.5.0(next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
@ -2004,6 +2007,13 @@ packages:
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
next-seo@6.5.0:
|
||||
resolution: {integrity: sha512-MfzUeWTN/x/rsKp/1n0213eojO97lIl0unxqbeCY+6pAucViHDA8GSLRRcXpgjsSmBxfCFdfpu7LXbt4ANQoNQ==}
|
||||
peerDependencies:
|
||||
next: ^8.1.1-canary.54 || >=9.0.0
|
||||
react: '>=16.0.0'
|
||||
react-dom: '>=16.0.0'
|
||||
|
||||
next@14.2.3:
|
||||
resolution: {integrity: sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
@ -4901,6 +4911,12 @@ snapshots:
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
next-seo@6.5.0(next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
next: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@next/env': 14.2.3
|
||||
|
@ -3,6 +3,6 @@ Allow: /
|
||||
|
||||
User-agent: *
|
||||
Disallow: /forgot-password
|
||||
Disallow: /sign-in
|
||||
Disallow: /sign-up
|
||||
Disallow: /widget
|
||||
Disallow: /widget
|
||||
|
||||
Sitemap: https://jsoncrack.com/sitemap.txt
|
||||
|
@ -1,5 +1,8 @@
|
||||
https://jsoncrack.com
|
||||
https://jsoncrack.com/pricing
|
||||
https://jsoncrack.com/sign-in
|
||||
https://jsoncrack.com/sign-up
|
||||
https://jsoncrack.com/oauth
|
||||
https://jsoncrack.com/forgot-password
|
||||
https://jsoncrack.com/editor
|
||||
https://jsoncrack.com/docs
|
||||
|
@ -1,21 +0,0 @@
|
||||
export function generateJsonld() {
|
||||
return {
|
||||
__html: `{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebApplication",
|
||||
"name": "JSON Crack",
|
||||
"applicationCategory": "DeveloperApplication",
|
||||
"screenshot": "https://jsoncrack.com/assets/preview/1.png",
|
||||
"description": "JSON Crack is a powerful JSON visualization tool that offers a wide range of features including formatting, validation, conversion, and more.",
|
||||
"url": "https://jsoncrack.com",
|
||||
"browserRequirements": "Requires JavaScript. Requires HTML5.",
|
||||
"operatingSystem": "All",
|
||||
"sameAs": [
|
||||
"https://www.x.com/jsoncrack",
|
||||
"https://www.linkedin.com/company/jsoncrack",
|
||||
"https://github.com/AykutSarac/jsoncrack.com"
|
||||
]
|
||||
}
|
||||
`,
|
||||
};
|
||||
}
|
@ -1,6 +1,3 @@
|
||||
export const metaDescription =
|
||||
"JSON Crack Editor is a tool for visualizing into graphs, analyzing, editing, formatting, querying, transforming and validating JSON, CSV, YAML, XML, and more.";
|
||||
|
||||
export const images = Object.freeze([
|
||||
{
|
||||
id: 1,
|
||||
|
29
src/constants/seo.ts
Normal file
29
src/constants/seo.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import type { NextSeoProps } from "next-seo";
|
||||
|
||||
export const SEO: NextSeoProps = {
|
||||
title: "JSON Crack | Transform your data into interactive graphs",
|
||||
description:
|
||||
"JSON Crack Editor is a tool for visualizing into graphs, analyzing, editing, formatting, querying, transforming and validating JSON, CSV, YAML, XML, and more.",
|
||||
themeColor: "#36393E",
|
||||
openGraph: {
|
||||
type: "website",
|
||||
images: [
|
||||
{
|
||||
url: "https://jsoncrack.com/assets/jsoncrack.png",
|
||||
width: 1200,
|
||||
height: 627,
|
||||
},
|
||||
],
|
||||
},
|
||||
additionalLinkTags: [
|
||||
{
|
||||
rel: "manifest",
|
||||
href: "/manifest.json",
|
||||
},
|
||||
{
|
||||
rel: "icon",
|
||||
href: "/favicon.ico",
|
||||
sizes: "48x48",
|
||||
},
|
||||
],
|
||||
};
|
129
src/containers/AuthLayout/index.tsx
Normal file
129
src/containers/AuthLayout/index.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
import React from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
Alert,
|
||||
Anchor,
|
||||
Center,
|
||||
Container,
|
||||
LoadingOverlay,
|
||||
MantineProvider,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import styled, { ThemeProvider } from "styled-components";
|
||||
import { FaInfoCircle } from "react-icons/fa";
|
||||
import { lightTheme } from "src/constants/theme";
|
||||
import { JSONCrackLogo } from "src/layout/JsonCrackLogo";
|
||||
|
||||
const Toaster = dynamic(() => import("react-hot-toast").then(c => c.Toaster));
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: 40px 40px;
|
||||
background-image: linear-gradient(to right, #f7f7f7 1px, transparent 1px),
|
||||
linear-gradient(to bottom, #f7f7f7 1px, transparent 1px);
|
||||
image-rendering: pixelated;
|
||||
-webkit-mask-image: linear-gradient(to bottom, transparent, 0%, white, 98%, transparent);
|
||||
mask-image: linear-gradient(to bottom, transparent, 0%, white, 98%, transparent);
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledPaper = styled.div`
|
||||
border-radius: 0px;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
|
||||
background: rgba(255, 255, 255, 0.09);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(5px);
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
`;
|
||||
|
||||
function Loading() {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleStart = (url: string) => url !== router.asPath && setLoading(true);
|
||||
const handleComplete = (url: string) => url === router.asPath && setLoading(false);
|
||||
|
||||
router.events.on("routeChangeStart", handleStart);
|
||||
router.events.on("routeChangeComplete", handleComplete);
|
||||
router.events.on("routeChangeError", handleComplete);
|
||||
|
||||
return () => {
|
||||
router.events.off("routeChangeStart", handleStart);
|
||||
router.events.off("routeChangeComplete", handleComplete);
|
||||
router.events.off("routeChangeError", handleComplete);
|
||||
};
|
||||
});
|
||||
|
||||
if (loading) return <LoadingOverlay visible />;
|
||||
return null;
|
||||
}
|
||||
|
||||
export const AuthLayout = ({ children }: React.PropsWithChildren) => {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<MantineProvider forceColorScheme="light">
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
containerStyle={{
|
||||
bottom: 34,
|
||||
right: 8,
|
||||
fontSize: 14,
|
||||
}}
|
||||
toastOptions={{
|
||||
style: {
|
||||
background: "#4D4D4D",
|
||||
color: "#B9BBBE",
|
||||
borderRadius: 4,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Container>
|
||||
<Center mb="xl">
|
||||
<JSONCrackLogo fontSize="1.5rem" />
|
||||
</Center>
|
||||
<Alert py="sm" mb="md" color="indigo" icon={<FaInfoCircle />}>
|
||||
Premium editor has been moved to{" "}
|
||||
<Anchor href="https://todiagram.com" inherit>
|
||||
todiagram.com
|
||||
</Anchor>
|
||||
.
|
||||
</Alert>
|
||||
<StyledPaper>
|
||||
{children}
|
||||
<Loading />
|
||||
</StyledPaper>
|
||||
<Text maw={250} ta="center" mx="auto" pos="relative" mt="md" fz="xs" c="gray.6">
|
||||
By continuing you are agreeing to our{" "}
|
||||
<Anchor fz="xs" component="a" href="/legal/terms" target="_blank">
|
||||
Terms of Service
|
||||
</Anchor>{" "}
|
||||
and{" "}
|
||||
<Anchor fz="xs" component="a" href="/legal/privacy" target="_blank">
|
||||
Privacy Policy
|
||||
</Anchor>
|
||||
</Text>
|
||||
</Container>
|
||||
</ThemeProvider>
|
||||
</MantineProvider>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import { Flex, Popover, Text } from "@mantine/core";
|
||||
import styled from "styled-components";
|
||||
import { AiOutlineLink, AiOutlineLock, AiOutlineUnlock } from "react-icons/ai";
|
||||
@ -106,15 +105,11 @@ export const BottomBar = () => {
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsPrivate(data?.private ?? true);
|
||||
if (data?.name) window.document.title = `${data.name} | JSON Crack`;
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<StyledBottomBar>
|
||||
{data?.name && (
|
||||
<Head>
|
||||
<title>{data.name} | JSON Crack</title>
|
||||
</Head>
|
||||
)}
|
||||
<StyledLeft>
|
||||
<StyledBottomBarItem onClick={toggleEditor}>
|
||||
<BiSolidDockLeft />
|
||||
|
@ -1,14 +1,10 @@
|
||||
import React from "react";
|
||||
import type { ModalProps } from "@mantine/core";
|
||||
import { Modal, Group, Button, Avatar, Text, Divider, Paper, Badge } from "@mantine/core";
|
||||
import { IoRocketSharp } from "react-icons/io5";
|
||||
import { gaEvent } from "src/lib/utils/gaEvent";
|
||||
import useModal from "src/store/useModal";
|
||||
import { Modal, Group, Button, Avatar, Text, Divider, Paper, Badge, Anchor } from "@mantine/core";
|
||||
import useUser from "src/store/useUser";
|
||||
|
||||
export const AccountModal = ({ opened, onClose }: ModalProps) => {
|
||||
const user = useUser(state => state.user);
|
||||
const setVisible = useModal(state => state.setVisible);
|
||||
const logout = useUser(state => state.logout);
|
||||
|
||||
const username =
|
||||
@ -47,20 +43,17 @@ export const AccountModal = ({ opened, onClose }: ModalProps) => {
|
||||
</div>
|
||||
</Group>
|
||||
</Paper>
|
||||
|
||||
<Divider py="xs" />
|
||||
<Text fz="xs" c="dimmed">
|
||||
If you're already a premium user, please login at{" "}
|
||||
<Anchor inherit href="https://todiagram.com" target="_blank">
|
||||
ToDiagram
|
||||
</Anchor>
|
||||
.
|
||||
</Text>
|
||||
<Divider my="xs" />
|
||||
<Group justify="right">
|
||||
<Button
|
||||
variant="default"
|
||||
leftSection={<IoRocketSharp />}
|
||||
onClick={() => {
|
||||
setVisible("upgrade")(true);
|
||||
gaEvent("Account Modal", "click upgrade premium");
|
||||
}}
|
||||
>
|
||||
Upgrade to Premium
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
onClick={() => {
|
||||
logout();
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import type { ModalProps } from "@mantine/core";
|
||||
import { Modal, Stack, Button } from "@mantine/core";
|
||||
|
||||
@ -8,8 +9,9 @@ export const LoginModal = ({ opened, onClose }: ModalProps) => {
|
||||
<Stack py="sm">
|
||||
<Button
|
||||
variant="default"
|
||||
component="a"
|
||||
href="https://app.jsoncrack.com/sign-in"
|
||||
component={Link}
|
||||
prefetch={false}
|
||||
href="/sign-in"
|
||||
rel="noreferrer"
|
||||
size="md"
|
||||
fullWidth
|
||||
|
@ -1,180 +1,46 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import type { ModalProps } from "@mantine/core";
|
||||
import {
|
||||
Text,
|
||||
Flex,
|
||||
Divider,
|
||||
List,
|
||||
Drawer,
|
||||
Stack,
|
||||
Radio,
|
||||
Badge,
|
||||
Button,
|
||||
Group,
|
||||
Anchor,
|
||||
} from "@mantine/core";
|
||||
import styled from "styled-components";
|
||||
import { Text, Divider, List, Button, Modal } from "@mantine/core";
|
||||
import { IoMdCheckmarkCircleOutline } from "react-icons/io";
|
||||
import { MdChevronRight, MdOutlineTimer } from "react-icons/md";
|
||||
import { gaEvent } from "src/lib/utils/gaEvent";
|
||||
import { PRICING, purchaseLinks } from "src/pages/pricing";
|
||||
import useUser from "src/store/useUser";
|
||||
|
||||
const StyledRadioCard = styled(Radio.Card)`
|
||||
border-width: 2px;
|
||||
border-color: #efefef;
|
||||
min-width: 450px;
|
||||
transition: 0.2s;
|
||||
|
||||
&[data-checked] {
|
||||
border-color: #2cb200;
|
||||
background: #f9fff7;
|
||||
}
|
||||
|
||||
&:hover:not([data-checked]) {
|
||||
border-color: #d5d5d5;
|
||||
background: #fafafa;
|
||||
}
|
||||
`;
|
||||
import { MdChevronRight } from "react-icons/md";
|
||||
|
||||
export const UpgradeModal = ({ opened, onClose }: ModalProps) => {
|
||||
const user = useUser(state => state.user);
|
||||
const [plan, setPlan] = React.useState("annual");
|
||||
|
||||
const handleUpgrade = () => {
|
||||
const link = new URL(purchaseLinks[plan]);
|
||||
|
||||
if (user?.email) {
|
||||
link.searchParams.append("checkout[email]", user.email);
|
||||
}
|
||||
|
||||
if (user?.user_metadata.display_name) {
|
||||
link.searchParams.append("checkout[name]", user.user_metadata.display_name);
|
||||
}
|
||||
|
||||
gaEvent("Premium Modal", "click select", plan);
|
||||
window.open(link.toString(), "_blank");
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
<Modal
|
||||
title={
|
||||
<Text c="bright" fz="h2" fw={600}>
|
||||
Upgrade
|
||||
</Text>
|
||||
}
|
||||
size="md"
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
zIndex={1001}
|
||||
position="bottom"
|
||||
radius="lg"
|
||||
styles={{
|
||||
body: { background: "#ffffff" },
|
||||
header: { background: "#ffffff" },
|
||||
content: { background: "#ffffff" },
|
||||
}}
|
||||
centered
|
||||
>
|
||||
<Flex mx="auto" w="fit-content" miw={600} gap="3vw" justify="space-between">
|
||||
<Stack>
|
||||
<Text c="black" fz="h2" fw={600}>
|
||||
Upgrade
|
||||
</Text>
|
||||
<Divider fz="md" labelPosition="left" label="Included features" color="gray.4" />
|
||||
<List
|
||||
spacing="xs"
|
||||
c="gray.7"
|
||||
icon={<IoMdCheckmarkCircleOutline size="24" color="#16a34a" />}
|
||||
>
|
||||
<List.Item>Larger data support up to 4 MB</List.Item>
|
||||
<List.Item>Edit data directly on visualizations</List.Item>
|
||||
<List.Item>Compare data differences on graphs</List.Item>
|
||||
<List.Item>AI-powered data filter</List.Item>
|
||||
<List.Item>Customizable graph colors</List.Item>
|
||||
<List.Item>Tabs for multiple documents</List.Item>
|
||||
<List.Item>
|
||||
<Anchor c="inherit" td="underline" href="/#pricing" target="_blank">
|
||||
..see all features
|
||||
</Anchor>
|
||||
</List.Item>
|
||||
</List>
|
||||
</Stack>
|
||||
<Radio.Group value={plan} onChange={setPlan}>
|
||||
<Stack>
|
||||
<StyledRadioCard value="monthly" radius="lg" px="xl" py="md">
|
||||
<Group align="center" justify="space-between">
|
||||
<Flex align="center" gap="xs">
|
||||
<Text fz="xl" c="gray.7" fw={600}>
|
||||
Monthly
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex fw={500} align="baseline" fz="sm" c="gray.5">
|
||||
<Text fw={600} fz="xl" c="gray.7" mr="2">
|
||||
${PRICING.MONTHLY}
|
||||
</Text>
|
||||
<Text inherit mr="2">
|
||||
/
|
||||
</Text>
|
||||
month
|
||||
</Flex>
|
||||
</Group>
|
||||
</StyledRadioCard>
|
||||
<StyledRadioCard value="annual" radius="lg" px="xl" py="md">
|
||||
<Group align="center" justify="space-between">
|
||||
<Flex align="center" gap="xs">
|
||||
<Text fz="xl" c="gray.7" fw={600}>
|
||||
Yearly
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex fw={500} align="baseline" fz="sm" c="gray.5">
|
||||
<Text fw={600} fz="xl" c="gray.7" mr="2">
|
||||
${PRICING.ANNUAL * 12}
|
||||
</Text>
|
||||
<Text inherit mr="2">
|
||||
/
|
||||
</Text>
|
||||
year
|
||||
</Flex>
|
||||
</Group>
|
||||
</StyledRadioCard>
|
||||
{PRICING.LTD && (
|
||||
<StyledRadioCard value="ltd" radius="lg" px="xl" py="md">
|
||||
<Group align="center" justify="space-between">
|
||||
<Flex align="center" gap="xs">
|
||||
<Text fz="xl" c="gray.7" fw={600}>
|
||||
Lifetime
|
||||
</Text>
|
||||
<Badge
|
||||
variant="light"
|
||||
size="md"
|
||||
radius="lg"
|
||||
color="#f00"
|
||||
leftSection={<MdOutlineTimer size="12" />}
|
||||
>
|
||||
Limited
|
||||
</Badge>
|
||||
</Flex>
|
||||
<Flex fw={500} align="baseline" fz="sm" c="gray.5">
|
||||
<Text fw={600} fz="xl" c="gray.7" mr="2">
|
||||
${PRICING.LTD}
|
||||
</Text>
|
||||
<Text inherit mr="2">
|
||||
/
|
||||
</Text>
|
||||
lifetime
|
||||
</Flex>
|
||||
</Group>
|
||||
</StyledRadioCard>
|
||||
)}
|
||||
</Stack>
|
||||
<Button
|
||||
color="green"
|
||||
fullWidth
|
||||
mt="xl"
|
||||
size="xl"
|
||||
radius="md"
|
||||
onClick={handleUpgrade}
|
||||
rightSection={<MdChevronRight size="24" />}
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
</Radio.Group>
|
||||
</Flex>
|
||||
</Drawer>
|
||||
<Divider mb="xs" fz="md" labelPosition="left" label="Included features" color="dimmed" />
|
||||
<List spacing="xs" c="gray" icon={<IoMdCheckmarkCircleOutline size="24" color="#16a34a" />}>
|
||||
<List.Item>Larger data support up to 4 MB</List.Item>
|
||||
<List.Item>Edit data directly on visualizations</List.Item>
|
||||
<List.Item>Compare data differences on graphs</List.Item>
|
||||
<List.Item>AI-powered data filter</List.Item>
|
||||
<List.Item>Customizable graph colors</List.Item>
|
||||
<List.Item>Tabs for multiple documents</List.Item>
|
||||
<List.Item>...and more</List.Item>
|
||||
</List>
|
||||
<Link href="https://todiagram.com/#preview" target="_blank" passHref>
|
||||
<Button
|
||||
color="green"
|
||||
fullWidth
|
||||
mt="md"
|
||||
size="lg"
|
||||
radius="md"
|
||||
rightSection={<MdChevronRight size="24" />}
|
||||
>
|
||||
See more
|
||||
</Button>
|
||||
</Link>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import { Menu, Avatar, Text } from "@mantine/core";
|
||||
import { VscSignIn, VscFeedback, VscSignOut } from "react-icons/vsc";
|
||||
import useModal from "src/store/useModal";
|
||||
@ -31,11 +32,11 @@ export const AccountMenu = () => {
|
||||
<Text size="xs">{username ?? "Account"}</Text>
|
||||
</Menu.Item>
|
||||
) : (
|
||||
<a href="https://app.jsoncrack.com/sign-in">
|
||||
<Link href="/sign-in" prefetch={false}>
|
||||
<Menu.Item leftSection={<VscSignIn />}>
|
||||
<Text size="xs">Sign in</Text>
|
||||
</Menu.Item>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
{user && (
|
||||
<>
|
||||
|
@ -91,10 +91,7 @@ export const OptionsMenu = () => {
|
||||
<Menu.Item
|
||||
closeMenuOnClick
|
||||
leftSection={<VscLock />}
|
||||
onClick={() => {
|
||||
setVisible("upgrade")(true);
|
||||
gaEvent("Options Menu", "toggle dark mode", darkmodeEnabled ? "on" : "off");
|
||||
}}
|
||||
onClick={() => setVisible("upgrade")(true)}
|
||||
>
|
||||
<Text size="xs">Customize Graph Colors</Text>
|
||||
</Menu.Item>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@mantine/core";
|
||||
import styled from "styled-components";
|
||||
import { JSONCrackLogo } from "./JsonCrackLogo";
|
||||
@ -95,8 +96,8 @@ export const Navbar = () => {
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="black"
|
||||
component="a"
|
||||
href="https://app.jsoncrack.com/sign-in"
|
||||
component={Link}
|
||||
href="/sign-in"
|
||||
visibleFrom="sm"
|
||||
size="md"
|
||||
radius="md"
|
||||
|
@ -1,9 +1,8 @@
|
||||
import React from "react";
|
||||
import { Button, Title } from "@mantine/core";
|
||||
import Link from "next/link";
|
||||
import { Button, Flex, Title, Image } from "@mantine/core";
|
||||
import styled from "styled-components";
|
||||
import { MdChevronRight } from "react-icons/md";
|
||||
import { JSONCrackLogo } from "src/layout/JsonCrackLogo";
|
||||
import useModal from "src/store/useModal";
|
||||
|
||||
const StyledNotSupported = styled.div`
|
||||
position: relative;
|
||||
@ -134,9 +133,9 @@ const StyledNotSupported = styled.div`
|
||||
`;
|
||||
|
||||
const StyledInfo = styled.p`
|
||||
width: 60%;
|
||||
max-width: 500px;
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
font-size: 26px;
|
||||
text-align: center;
|
||||
color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
|
||||
`;
|
||||
@ -155,30 +154,31 @@ const StyledContent = styled.div`
|
||||
`;
|
||||
|
||||
export const NotSupported = () => {
|
||||
const setVisible = useModal(state => state.setVisible);
|
||||
|
||||
return (
|
||||
<StyledNotSupported>
|
||||
<StyledContent>
|
||||
<Title mb="lg" style={{ pointerEvents: "none" }}>
|
||||
<JSONCrackLogo fontSize="4rem" style={{ color: "gray" }} hideLogo />
|
||||
</Title>
|
||||
<Flex align="center" justify="center" gap="16" mb="lg">
|
||||
<Image src="https://todiagram.com/logo.svg" alt="ToDiagram" w="48" h="48" />
|
||||
<Title fz="48" style={{ pointerEvents: "none", mixBlendMode: "difference" }}>
|
||||
ToDiagram
|
||||
</Title>
|
||||
</Flex>
|
||||
<StyledInfo>
|
||||
Upgrade to premium for larger data size support. The free version is incapable of handling
|
||||
data of this size!
|
||||
Use ToDiagram for larger data size, faster performance, and more features.
|
||||
</StyledInfo>
|
||||
|
||||
<Button
|
||||
mt="lg"
|
||||
size="lg"
|
||||
fw="bolder"
|
||||
color="green"
|
||||
radius="md"
|
||||
rightSection={<MdChevronRight size="24" />}
|
||||
onClick={() => setVisible("upgrade")(true)}
|
||||
>
|
||||
Upgrade Premium
|
||||
</Button>
|
||||
<Link href="https://todiagram.com" target="_blank" passHref>
|
||||
<Button
|
||||
mt="lg"
|
||||
size="lg"
|
||||
fw="bolder"
|
||||
color="#FE634E"
|
||||
autoContrast
|
||||
radius="md"
|
||||
rightSection={<MdChevronRight size="24" />}
|
||||
>
|
||||
Go to ToDiagram
|
||||
</Button>
|
||||
</Link>
|
||||
</StyledContent>
|
||||
|
||||
<div className="glowing">
|
||||
|
@ -1,16 +1,14 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { Button, Stack, Text, Title } from "@mantine/core";
|
||||
import { NextSeo } from "next-seo";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import Layout from "src/layout/Layout";
|
||||
|
||||
const NotFound = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<Head>
|
||||
<title>404 | JSON Crack</title>
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
</Head>
|
||||
<NextSeo {...SEO} title="404 | ToDiagram" noindex nofollow />
|
||||
<Stack mt={100} justify="center" align="center">
|
||||
<Title fz={150} style={{ fontFamily: "monospace" }}>
|
||||
404
|
||||
|
@ -1,14 +1,15 @@
|
||||
import React from "react";
|
||||
import type { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { createTheme, MantineProvider } from "@mantine/core";
|
||||
import "@mantine/core/styles.css";
|
||||
import "@mantine/code-highlight/styles.css";
|
||||
import { ThemeProvider } from "styled-components";
|
||||
import { NextSeo } from "next-seo";
|
||||
import ReactGA from "react-ga4";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import GlobalStyle from "src/constants/globalStyle";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import { lightTheme } from "src/constants/theme";
|
||||
import { supabase } from "src/lib/api/supabase";
|
||||
import useUser from "src/store/useUser";
|
||||
@ -81,9 +82,7 @@ function JsonCrack({ Component, pageProps }: AppProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>JSON Crack | Transform your data into interactive graphs</title>
|
||||
</Head>
|
||||
<NextSeo {...SEO} />
|
||||
<MantineProvider defaultColorScheme="light" theme={theme}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<Toaster
|
||||
|
@ -3,11 +3,6 @@ import Document, { Html, Head, Main, NextScript } from "next/document";
|
||||
import { ColorSchemeScript } from "@mantine/core";
|
||||
import { ServerStyleSheet } from "styled-components";
|
||||
|
||||
const metatags = Object.freeze({
|
||||
title: "JSON Crack | Transform your data into interactive graphs",
|
||||
image: "https://jsoncrack.com/assets/jsoncrack.png",
|
||||
});
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
|
||||
const sheet = new ServerStyleSheet();
|
||||
@ -39,19 +34,6 @@ class MyDocument extends Document {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<meta name="theme-color" content="#36393E" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
|
||||
<meta property="og:url" content="https://jsoncrack.com" key="ogurl" />
|
||||
<meta property="og:type" content="website" key="ogtype" />
|
||||
<meta property="og:title" content={metatags.title} key="ogtitle" />
|
||||
<meta property="og:image" content={metatags.image} key="ogimage" />
|
||||
<meta name="twitter:card" content="summary_large_image" key="twcard" />
|
||||
<meta property="twitter:domain" content="https://jsoncrack.com" key="twdomain" />
|
||||
<meta property="twitter:url" content="https://jsoncrack.com" key="twurl" />
|
||||
<meta name="twitter:title" content={metatags.title} key="twtitle" />
|
||||
<meta name="twitter:image" content={metatags.image} key="twimage" />
|
||||
<ColorSchemeScript />
|
||||
</Head>
|
||||
<body>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { Button, Stack, Text, Title } from "@mantine/core";
|
||||
import { NextSeo } from "next-seo";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import Layout from "src/layout/Layout";
|
||||
|
||||
const Custom500 = () => {
|
||||
@ -9,10 +10,7 @@ const Custom500 = () => {
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Head>
|
||||
<title>Unexpected Error Occured | JSON Crack</title>
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
</Head>
|
||||
<NextSeo {...SEO} title="Unexpected Error Occured | ToDiagram" />
|
||||
<Stack mt={100} justify="center" align="center">
|
||||
<Title fz={150} style={{ fontFamily: "monospace" }}>
|
||||
500
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import { Group, Paper, Stack, Text, Title } from "@mantine/core";
|
||||
import { CodeHighlight } from "@mantine/code-highlight";
|
||||
import styled from "styled-components";
|
||||
import { NextSeo } from "next-seo";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import Layout from "src/layout/Layout";
|
||||
|
||||
const StyledFrame = styled.iframe`
|
||||
@ -37,11 +38,12 @@ const StyledHighlight = styled.span<{ $link?: boolean; $alert?: boolean }>`
|
||||
const Docs = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<Head>
|
||||
<title>Embed - JSON Crack</title>
|
||||
<meta name="description" content="Integrate JSON Crack widgets into your website." />
|
||||
<link rel="canonical" href="https://jsoncrack.com/docs" />
|
||||
</Head>
|
||||
<NextSeo
|
||||
{...SEO}
|
||||
title="Documentation - JSON Crack"
|
||||
description="Integrate JSON Crack widgets into your website."
|
||||
canonical="https://jsoncrack.com/docs"
|
||||
/>
|
||||
<Stack mx="auto" maw="90%">
|
||||
<Group mb="lg" mt={40}>
|
||||
<Title order={1} c="dark">
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { useMantineColorScheme } from "@mantine/core";
|
||||
import "@mantine/dropzone/styles.css";
|
||||
import styled, { ThemeProvider } from "styled-components";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { metaDescription } from "src/constants/landing";
|
||||
import { NextSeo } from "next-seo";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import { darkTheme, lightTheme } from "src/constants/theme";
|
||||
import { Editor } from "src/containers/Editor";
|
||||
import { BottomBar } from "src/containers/Editor/BottomBar";
|
||||
@ -57,13 +57,12 @@ const EditorPage = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Editor | JSON Crack</title>
|
||||
<meta name="description" content={metaDescription} key="description" />
|
||||
<meta property="og:description" content={metaDescription} key="ogdescription" />
|
||||
<meta name="twitter:description" content={metaDescription} key="twdescription" />{" "}
|
||||
<link rel="canonical" href="https://jsoncrack.com/editor" />
|
||||
</Head>
|
||||
<NextSeo
|
||||
{...SEO}
|
||||
title="Editor | JSON Crack"
|
||||
description="JSON Crack Editor is a tool for visualizing into graphs, analyzing, editing, formatting, querying, transforming and validating JSON, CSV, YAML, XML, and more."
|
||||
canonical="https://jsoncrack.com/editor"
|
||||
/>
|
||||
<ThemeProvider theme={darkmodeEnabled ? darkTheme : lightTheme}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ExternalMode />
|
||||
|
@ -1,16 +1,31 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { Button, Group, Paper, Stack, TextInput, Text, Anchor, PasswordInput } from "@mantine/core";
|
||||
import { toast } from "react-hot-toast";
|
||||
import Layout from "src/layout/Layout";
|
||||
import {
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
TextInput,
|
||||
Text,
|
||||
Anchor,
|
||||
PasswordInput,
|
||||
Title,
|
||||
FocusTrap,
|
||||
Alert,
|
||||
} from "@mantine/core";
|
||||
import { NextSeo } from "next-seo";
|
||||
import { MdErrorOutline } from "react-icons/md";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import { AuthLayout } from "src/containers/AuthLayout";
|
||||
import { supabase } from "src/lib/api/supabase";
|
||||
|
||||
function ResetPassword() {
|
||||
const { push } = useRouter();
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [password, setPassword] = React.useState("");
|
||||
const [password2, setPassword2] = React.useState("");
|
||||
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
|
||||
const [success, setSuccess] = React.useState(false);
|
||||
|
||||
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
try {
|
||||
@ -22,21 +37,42 @@ function ResetPassword() {
|
||||
const { error } = await supabase.auth.updateUser({ password });
|
||||
if (error) throw new Error(error.message);
|
||||
|
||||
toast.success("Successfully updated password!");
|
||||
setTimeout(() => window.location.assign("/sign-in"), 2000);
|
||||
setSuccess(true);
|
||||
push("/sign-in");
|
||||
} catch (error) {
|
||||
if (error instanceof Error) toast.error(error.message);
|
||||
if (error instanceof Error) setErrorMessage(error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper mx="auto" mt={70} maw={400} p="lg" withBorder>
|
||||
<Text size="lg" w={500} mb="lg">
|
||||
Create New Password
|
||||
if (success) {
|
||||
return (
|
||||
<Text>
|
||||
Password updated successfully.{" "}
|
||||
<Anchor component={Link} href="/sign-in">
|
||||
Sign in
|
||||
</Anchor>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FocusTrap>
|
||||
{errorMessage && (
|
||||
<Alert
|
||||
onClose={() => setErrorMessage(null)}
|
||||
color="red"
|
||||
py="xs"
|
||||
mb="lg"
|
||||
icon={<MdErrorOutline color="red" />}
|
||||
withCloseButton
|
||||
>
|
||||
<Text fz="sm" c="red">
|
||||
{errorMessage}
|
||||
</Text>
|
||||
</Alert>
|
||||
)}
|
||||
<form onSubmit={onSubmit}>
|
||||
<Stack>
|
||||
<PasswordInput
|
||||
@ -48,13 +84,16 @@ function ResetPassword() {
|
||||
radius="sm"
|
||||
placeholder="∗∗∗∗∗∗∗∗∗∗∗"
|
||||
style={{ color: "black" }}
|
||||
autoComplete="new-password"
|
||||
data-autofocus
|
||||
/>
|
||||
<PasswordInput
|
||||
name="password"
|
||||
value={password2}
|
||||
onChange={e => setPassword2(e.target.value)}
|
||||
required
|
||||
label="Validate Password"
|
||||
label="Confirm Password"
|
||||
autoComplete="new-password"
|
||||
radius="sm"
|
||||
placeholder="∗∗∗∗∗∗∗∗∗∗∗"
|
||||
style={{ color: "black" }}
|
||||
@ -67,7 +106,15 @@ function ResetPassword() {
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
</Paper>
|
||||
<Stack mt="lg" align="center">
|
||||
<Anchor component={Link} prefetch={false} href="/sign-up" c="dark" fz="sm">
|
||||
Don't have an account?
|
||||
<Text component="span" fz="sm" c="blue" ml={4}>
|
||||
Sign up
|
||||
</Text>
|
||||
</Anchor>
|
||||
</Stack>
|
||||
</FocusTrap>
|
||||
);
|
||||
}
|
||||
|
||||
@ -76,6 +123,7 @@ const ForgotPassword = () => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [email, setEmail] = React.useState("");
|
||||
const [success, setSuccess] = React.useState(false);
|
||||
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
|
||||
const isPasswordReset = query?.type === "recovery" && !query?.error;
|
||||
|
||||
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
@ -83,12 +131,14 @@ const ForgotPassword = () => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
const { error } = await supabase.auth.resetPasswordForEmail(email);
|
||||
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||
redirectTo: `${window.location.origin}/forgot-password`,
|
||||
});
|
||||
if (error) throw new Error(error.message);
|
||||
|
||||
setSuccess(true);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) toast.error(error.message);
|
||||
if (error instanceof Error) setErrorMessage(error.message);
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@ -96,24 +146,45 @@ const ForgotPassword = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Head>
|
||||
<title>Reset Password - JSON Crack</title>
|
||||
<link rel="canonical" href="https://app.jsoncrack.com/forgot-password" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
</Head>
|
||||
<AuthLayout>
|
||||
<NextSeo
|
||||
{...SEO}
|
||||
title="Reset Password - JSON Crack"
|
||||
noindex
|
||||
nofollow
|
||||
canonical="https://jsoncrack.com/forgot-password"
|
||||
/>
|
||||
<Title c="dark.4" order={2} fz="xl" mb={25} fw={600}>
|
||||
{isPasswordReset ? "Create New Password" : "Forgot Password"}
|
||||
</Title>
|
||||
{isPasswordReset ? (
|
||||
<ResetPassword />
|
||||
) : (
|
||||
<Paper mx="auto" mt={100} maw={400} p="lg" withBorder>
|
||||
<Text size="lg" w={500} c="dark">
|
||||
Reset Password
|
||||
</Text>
|
||||
<Paper pt="lg">
|
||||
{success ? (
|
||||
<>
|
||||
{success ? (
|
||||
<>
|
||||
<Text>We've sent an email to you, please check your inbox.</Text>
|
||||
) : (
|
||||
<form onSubmit={onSubmit}>
|
||||
<Button component={Link} href="/sign-in" color="dark" radius="sm" mt="lg" fullWidth>
|
||||
Back to Login
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<form onSubmit={onSubmit}>
|
||||
{errorMessage && (
|
||||
<Alert
|
||||
onClose={() => setErrorMessage(null)}
|
||||
color="red"
|
||||
py="xs"
|
||||
mb="lg"
|
||||
icon={<MdErrorOutline color="red" />}
|
||||
withCloseButton
|
||||
>
|
||||
<Text fz="sm" c="red">
|
||||
{errorMessage}
|
||||
</Text>
|
||||
</Alert>
|
||||
)}
|
||||
<FocusTrap>
|
||||
<Stack>
|
||||
<TextInput
|
||||
name="email"
|
||||
@ -125,25 +196,29 @@ const ForgotPassword = () => {
|
||||
placeholder="hello@jsoncrack.com"
|
||||
radius="sm"
|
||||
style={{ color: "black" }}
|
||||
data-autofocus
|
||||
/>
|
||||
</Stack>
|
||||
</FocusTrap>
|
||||
|
||||
<Group justify="apart" mt="xl">
|
||||
<Button color="dark" type="submit" radius="sm" loading={loading} fullWidth>
|
||||
Reset Password
|
||||
</Button>
|
||||
</Group>
|
||||
<Stack mt="lg" align="center">
|
||||
<Anchor component={Link} href="/sign-in" c="dark" size="xs">
|
||||
Don't have an account? Sign Up
|
||||
</Anchor>
|
||||
</Stack>
|
||||
</form>
|
||||
)}
|
||||
</Paper>
|
||||
</Paper>
|
||||
<Group justify="apart" mt="md">
|
||||
<Button color="dark" type="submit" radius="sm" loading={loading} fullWidth>
|
||||
Reset Password
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
)}
|
||||
<Stack mt="lg" align="center">
|
||||
<Anchor component={Link} prefetch={false} href="/sign-up" c="dark" fz="sm">
|
||||
Don't have an account?
|
||||
<Text component="span" fz="sm" c="blue" ml={4}>
|
||||
Sign up
|
||||
</Text>
|
||||
</Anchor>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Layout>
|
||||
</AuthLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import { generateJsonld } from "src/constants/jsonld";
|
||||
import { metaDescription } from "src/constants/landing";
|
||||
import { NextSeo, SoftwareAppJsonLd } from "next-seo";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import { FAQ } from "src/containers/Landing/FAQ";
|
||||
import { Features } from "src/containers/Landing/Features";
|
||||
import { HeroPreview } from "src/containers/Landing/HeroPreview";
|
||||
@ -13,18 +12,16 @@ import Layout from "src/layout/Layout";
|
||||
export const HomePage = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<Head>
|
||||
<title>JSON Crack | Transform your data into interactive graphs</title>
|
||||
<meta name="description" content={metaDescription} key="description" />
|
||||
<meta property="og:description" content={metaDescription} key="ogdescription" />
|
||||
<meta name="twitter:description" content={metaDescription} key="twdescription" />
|
||||
<link rel="canonical" href="https://jsoncrack.com" />
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={generateJsonld()}
|
||||
key="product-jsonld"
|
||||
/>
|
||||
</Head>
|
||||
<NextSeo {...SEO} canonical="https://jsoncrack.com" />
|
||||
<SoftwareAppJsonLd
|
||||
name="JSON Crack"
|
||||
price="0"
|
||||
priceCurrency="USD"
|
||||
type="DeveloperApplication"
|
||||
operatingSystem="All"
|
||||
keywords="json, json editor, json viewer, json formatter, json beautifier, json validator, json minifier, json compressor, json decompressor, json parser, json converter, json to yaml, json to xml, json to csv, json to tsv, json to html, json to markdown, json to base64, json to url, json to query string, json to form data, json to javascript object, json to php array, json to python dictionary, json to ruby hash, json to java object, json to c# object, json to go object, json to rust object, json to swift object, json to kotlin object, json to typescript object, json to graphql, json to sql, json to mongodb, json to yaml, yaml to json, xml to json, csv to json, tsv to json, html to json, markdown to json, base64 to json, url to json, query string to json, form data to json, javascript object to json, php array to json, python dictionary to json, ruby hash to json, java object to json, c# object to json, go object to json, rust object to json, swift object to json, kotlin object to json, typescript object to json, graphql to json, sql to json, mongodb to json, yaml to json, json to yaml, xml to json, csv to json, tsv to json, html to json, markdown to json, base64 to json, url to json, query string to json, form data to json, javascript object to json, php array to json, python dictionary to json, ruby hash to json, java object to json, c# object to json, go object to json, rust object to json, swift object to json, kotlin object to json, typescript object to json, graphql to json, sql to json, mongodb to json"
|
||||
applicationCategory="DeveloperApplication"
|
||||
/>
|
||||
<HeroSection />
|
||||
<HeroPreview />
|
||||
<Features />
|
||||
|
@ -1,16 +1,19 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import { Box, Container, Paper, Stack, Text, Title } from "@mantine/core";
|
||||
import { NextSeo } from "next-seo";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import Layout from "src/layout/Layout";
|
||||
import privacy from "../../constants/privacy.json";
|
||||
|
||||
const Privacy = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<Head>
|
||||
<title>Privacy Policy - JSON Crack</title>
|
||||
<link rel="canonical" href="https://jsoncrack.com/legal/privacy" />
|
||||
</Head>
|
||||
<NextSeo
|
||||
{...SEO}
|
||||
title="Privacy Policy - JSON Crack"
|
||||
description="JSON Crack Privacy Policy"
|
||||
canonical="https://jsoncrack.com/legal/privacy"
|
||||
/>
|
||||
<Container my={50} size="md" pb="lg">
|
||||
<Paper bg="transparent">
|
||||
<Title ta="center" c="gray.8">
|
||||
|
@ -1,16 +1,19 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import { Box, Container, Paper, Stack, Text, Title } from "@mantine/core";
|
||||
import { NextSeo } from "next-seo";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import Layout from "src/layout/Layout";
|
||||
import terms from "../../constants/terms.json";
|
||||
|
||||
const Terms = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<Head>
|
||||
<title>Terms of Service - JSON Crack</title>
|
||||
<link rel="canonical" href="https://jsoncrack.com/legal/terms" />
|
||||
</Head>
|
||||
<NextSeo
|
||||
{...SEO}
|
||||
title="Terms of Service - JSON Crack"
|
||||
description="JSON Crack Terms of Service"
|
||||
canonical="https://jsoncrack.com/legal/terms"
|
||||
/>
|
||||
<Container my={50} size="md" pb="lg">
|
||||
<Paper bg="transparent">
|
||||
<Title ta="center" c="gray.8">
|
||||
|
25
src/pages/oauth.tsx
Normal file
25
src/pages/oauth.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { NextSeo } from "next-seo";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import { supabase } from "src/lib/api/supabase";
|
||||
|
||||
const Oauth = () => {
|
||||
const { push } = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
supabase.auth.getSession().then(({ data, error }) => {
|
||||
if (error) return console.error(error);
|
||||
if (!data.session) return;
|
||||
push("/editor");
|
||||
});
|
||||
}, [push]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextSeo {...SEO} canonical="https://jsoncrack.com/oauth" nofollow noindex />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Oauth;
|
@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import {
|
||||
Flex,
|
||||
Stack,
|
||||
@ -14,8 +13,10 @@ import {
|
||||
Box,
|
||||
} from "@mantine/core";
|
||||
import styled from "styled-components";
|
||||
import { NextSeo } from "next-seo";
|
||||
import { AiOutlineInfoCircle } from "react-icons/ai";
|
||||
import { FaArrowRightLong, FaCircleCheck } from "react-icons/fa6";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import Layout from "src/layout/Layout";
|
||||
import { gaEvent } from "src/lib/utils/gaEvent";
|
||||
|
||||
@ -278,10 +279,12 @@ export const PricingCards = () => {
|
||||
const Pricing = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Pricing - JSON Crack</title>
|
||||
<link rel="canonical" href="https://jsoncrack.com/pricing" />
|
||||
</Head>
|
||||
<NextSeo
|
||||
{...SEO}
|
||||
title="Pricing - JSON Crack"
|
||||
description="Upgrade to JSON Crack Premium for more features and better performance."
|
||||
canonical="https://jsoncrack.com/pricing"
|
||||
/>
|
||||
<Layout>
|
||||
<PricingCards />
|
||||
</Layout>
|
||||
|
@ -1,31 +1,34 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import React, { useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import type { PaperProps } from "@mantine/core";
|
||||
import {
|
||||
TextInput,
|
||||
PasswordInput,
|
||||
Paper,
|
||||
Button,
|
||||
Divider,
|
||||
Anchor,
|
||||
Stack,
|
||||
Center,
|
||||
Text,
|
||||
FocusTrap,
|
||||
Alert,
|
||||
Box,
|
||||
Flex,
|
||||
} from "@mantine/core";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { AiOutlineGithub, AiOutlineGoogle } from "react-icons/ai";
|
||||
import Layout from "src/layout/Layout";
|
||||
import { NextSeo } from "next-seo";
|
||||
import { AiOutlineGithub } from "react-icons/ai";
|
||||
import { FcGoogle } from "react-icons/fc";
|
||||
import { MdErrorOutline } from "react-icons/md";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import { AuthLayout } from "src/containers/AuthLayout";
|
||||
import { supabase } from "src/lib/api/supabase";
|
||||
import { isIframe } from "src/lib/utils/widget";
|
||||
import useUser from "src/store/useUser";
|
||||
|
||||
export function AuthenticationForm(props: PaperProps) {
|
||||
export function AuthenticationForm() {
|
||||
const { push } = useRouter();
|
||||
const setSession = useUser(state => state.setSession);
|
||||
const isAuthenticated = useUser(state => state.isAuthenticated);
|
||||
const [sessionLoading, setSessionLoading] = React.useState(false);
|
||||
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [userData, setUserData] = React.useState({
|
||||
name: "",
|
||||
email: "",
|
||||
@ -34,7 +37,7 @@ export function AuthenticationForm(props: PaperProps) {
|
||||
|
||||
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setSessionLoading(true);
|
||||
setLoading(true);
|
||||
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email: userData.email,
|
||||
@ -42,148 +45,153 @@ export function AuthenticationForm(props: PaperProps) {
|
||||
});
|
||||
|
||||
if (error) {
|
||||
setSessionLoading(false);
|
||||
return toast.error(error.message);
|
||||
setLoading(false);
|
||||
return setErrorMessage("Incorrect email or password.");
|
||||
}
|
||||
|
||||
await setSession(data.session);
|
||||
setSession(data.session);
|
||||
push("/editor");
|
||||
setSessionLoading(false);
|
||||
};
|
||||
|
||||
const handleLoginClick = async (provider: "github" | "google") => {
|
||||
setSessionLoading(true);
|
||||
await supabase.auth.signInWithOAuth({
|
||||
provider,
|
||||
options: { redirectTo: `${window.location.origin}/editor` },
|
||||
options: { redirectTo: `${window.location.origin}/oauth` },
|
||||
});
|
||||
setSessionLoading(false);
|
||||
};
|
||||
|
||||
if (isAuthenticated) {
|
||||
return (
|
||||
<Paper p="lg" maw={400} style={{ textAlign: "center" }}>
|
||||
<Box mt="lg">
|
||||
<Text fz="sm" c="dark">
|
||||
You are already signed in. Click the button below to go to the editor.
|
||||
</Text>
|
||||
<Link href="/editor">
|
||||
<Button mt="lg" color="dark" size="lg">
|
||||
GO TO EDITOR
|
||||
<Button mt="lg" color="dark" size="md" radius="md" fullWidth>
|
||||
Go to Editor
|
||||
</Button>
|
||||
</Link>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper {...props} style={{ textAlign: "left" }}>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
name="email"
|
||||
required
|
||||
label="Email"
|
||||
placeholder="hello@jsoncrack.com"
|
||||
value={userData.email}
|
||||
onChange={event => setUserData(d => ({ ...d, email: event.target.value }))}
|
||||
radius="sm"
|
||||
style={{ color: "black" }}
|
||||
/>
|
||||
<Box>
|
||||
{errorMessage && (
|
||||
<Alert
|
||||
onClose={() => setErrorMessage(null)}
|
||||
color="red"
|
||||
py="xs"
|
||||
mb="lg"
|
||||
icon={<MdErrorOutline color="red" />}
|
||||
withCloseButton
|
||||
>
|
||||
<Text fz="sm" c="red">
|
||||
{errorMessage}
|
||||
</Text>
|
||||
</Alert>
|
||||
)}
|
||||
<FocusTrap>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
label="Email"
|
||||
placeholder="hello@jsoncrack.com"
|
||||
value={userData.email}
|
||||
onChange={event => setUserData(d => ({ ...d, email: event.target.value }))}
|
||||
radius="sm"
|
||||
style={{ color: "black" }}
|
||||
withAsterisk={false}
|
||||
/>
|
||||
|
||||
<PasswordInput
|
||||
name="password"
|
||||
required
|
||||
label="Password"
|
||||
placeholder="∗∗∗∗∗∗∗∗∗∗∗"
|
||||
value={userData.password}
|
||||
onChange={event => setUserData(d => ({ ...d, password: event.target.value }))}
|
||||
radius="sm"
|
||||
style={{ color: "black" }}
|
||||
/>
|
||||
<PasswordInput
|
||||
name="password"
|
||||
required
|
||||
label="Password"
|
||||
placeholder="∗∗∗∗∗∗∗∗∗∗∗"
|
||||
value={userData.password}
|
||||
onChange={event => setUserData(d => ({ ...d, password: event.target.value }))}
|
||||
radius="sm"
|
||||
style={{ color: "black" }}
|
||||
withAsterisk={false}
|
||||
/>
|
||||
|
||||
<Button color="dark" type="submit" radius="sm" loading={sessionLoading}>
|
||||
Sign in
|
||||
</Button>
|
||||
|
||||
<Stack gap="sm" mx="auto" align="center">
|
||||
<Anchor component={Link} href="/forgot-password" c="dark" size="xs">
|
||||
Forgot your password?
|
||||
<Anchor
|
||||
component={Link}
|
||||
prefetch={false}
|
||||
href="/forgot-password"
|
||||
c="dimmed"
|
||||
size="xs"
|
||||
ta="right"
|
||||
mt="-sm"
|
||||
>
|
||||
Forgot password?
|
||||
</Anchor>
|
||||
<Button color="dark" type="submit" radius="sm" loading={loading}>
|
||||
Sign in
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</form>
|
||||
</form>
|
||||
</FocusTrap>
|
||||
|
||||
<Divider my="lg" />
|
||||
|
||||
<Stack mb="md" mt="md">
|
||||
<Flex mt="lg" gap="sm">
|
||||
<Button
|
||||
radius="sm"
|
||||
leftSection={<AiOutlineGoogle size="20" />}
|
||||
fullWidth
|
||||
leftSection={<FcGoogle size="20" />}
|
||||
onClick={() => handleLoginClick("google")}
|
||||
color="red"
|
||||
variant="outline"
|
||||
variant="default"
|
||||
disabled={loading}
|
||||
>
|
||||
Sign in with Google
|
||||
Google
|
||||
</Button>
|
||||
<Button
|
||||
radius="sm"
|
||||
leftSection={<AiOutlineGithub size="20" />}
|
||||
onClick={() => handleLoginClick("github")}
|
||||
color="dark"
|
||||
variant="outline"
|
||||
variant="default"
|
||||
fullWidth
|
||||
disabled={loading}
|
||||
>
|
||||
Sign in with GitHub
|
||||
GitHub
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const SignIn = () => {
|
||||
const { isReady, push, query } = useRouter();
|
||||
const hasSession = useUser(state => !!state.user);
|
||||
const setSession = useUser(state => state.setSession);
|
||||
const isPasswordReset = query?.type === "recovery" && !query?.error;
|
||||
const { push, query, pathname } = useRouter();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isIframe()) {
|
||||
push("/");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isReady) return;
|
||||
|
||||
if (query?.access_token && query?.refresh_token) {
|
||||
(async () => {
|
||||
const refresh_token = query.refresh_token as string;
|
||||
const access_token = query.access_token as string;
|
||||
const { data, error } = await supabase.auth.setSession({ refresh_token, access_token });
|
||||
|
||||
if (error) return toast.error(error.message);
|
||||
if (data.session) setSession(data.session);
|
||||
})();
|
||||
}
|
||||
|
||||
if (hasSession && !isPasswordReset) push("/editor");
|
||||
}, [isReady, hasSession, push, isPasswordReset, query, setSession]);
|
||||
|
||||
if (!isReady) return null;
|
||||
useEffect(() => {
|
||||
supabase.auth.getSession().then(({ data, error }) => {
|
||||
if (error) return console.error(error);
|
||||
if (!data.session) return;
|
||||
push("/editor");
|
||||
});
|
||||
}, [pathname, push, query.code]);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Head>
|
||||
<title>Sign In - JSON Crack</title>
|
||||
<link rel="canonical" href="https://app.jsoncrack.com/sign-in" />
|
||||
</Head>
|
||||
<Paper mt={100} mx="auto" maw={400} p="lg" withBorder>
|
||||
<AuthenticationForm />
|
||||
</Paper>
|
||||
<Center my="xl">
|
||||
<Anchor component={Link} href="/sign-up" c="gray.5" fw="bold">
|
||||
<AuthLayout>
|
||||
<NextSeo
|
||||
{...SEO}
|
||||
title="Sign In - JSON Crack"
|
||||
description="Sign in to your JSON Crack account to create and edit diagrams with ease."
|
||||
canonical="https://jsoncrack.com/sign-in"
|
||||
/>
|
||||
<AuthenticationForm />
|
||||
<Center mt={80}>
|
||||
<Anchor component={Link} prefetch={false} href="/sign-up" c="dark" fz="sm">
|
||||
Don't have an account?
|
||||
<Text component="span" fz="sm" c="blue" ml={4}>
|
||||
Sign up
|
||||
</Text>
|
||||
</Anchor>
|
||||
</Center>
|
||||
</Layout>
|
||||
</AuthLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,193 +1,53 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import React, { useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Anchor,
|
||||
Button,
|
||||
Center,
|
||||
Divider,
|
||||
Flex,
|
||||
Paper,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import toast from "react-hot-toast";
|
||||
import { AiOutlineGithub, AiOutlineGoogle } from "react-icons/ai";
|
||||
import Layout from "src/layout/Layout";
|
||||
import { useRouter } from "next/router";
|
||||
import { Anchor, Button, Center, Text } from "@mantine/core";
|
||||
import { NextSeo } from "next-seo";
|
||||
import { SEO } from "src/constants/seo";
|
||||
import { AuthLayout } from "src/containers/AuthLayout";
|
||||
import { supabase } from "src/lib/api/supabase";
|
||||
|
||||
const SignUp = () => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [done, setDone] = React.useState(false);
|
||||
const [userData, setUserData] = React.useState({
|
||||
display_name: "",
|
||||
email: "",
|
||||
password: "",
|
||||
});
|
||||
const { push } = useRouter();
|
||||
|
||||
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
supabase.auth
|
||||
.signUp({
|
||||
email: userData.email,
|
||||
password: userData.password,
|
||||
options: {
|
||||
data: { display_name: userData.display_name },
|
||||
},
|
||||
})
|
||||
.then(({ error }) => {
|
||||
if (error) return toast.error(error.message);
|
||||
toast.success("Please check your inbox to confirm mail address!", { duration: 7000 });
|
||||
setDone(true);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
const handleLoginClick = (provider: "github" | "google") => {
|
||||
supabase.auth.signInWithOAuth({
|
||||
provider,
|
||||
options: { redirectTo: `${window.location.origin}/editor` },
|
||||
useEffect(() => {
|
||||
supabase.auth.getSession().then(({ data }) => {
|
||||
if (data.session) push("/editor");
|
||||
});
|
||||
};
|
||||
}, [push]);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Head>
|
||||
<title>Sign Up - JSON Crack</title>
|
||||
<link rel="canonical" href="https://app.jsoncrack.com/sign-up" />
|
||||
</Head>
|
||||
{done ? (
|
||||
<Paper mx="auto" maw={400} mt={100} p="lg" withBorder>
|
||||
<Text mt="lg" style={{ textAlign: "center" }}>
|
||||
Registration successul!
|
||||
<br />
|
||||
Please check your inbox for email confirmation.
|
||||
<AuthLayout>
|
||||
<NextSeo
|
||||
{...SEO}
|
||||
title="Sign Up - JSON Crack"
|
||||
description="Create an account to start creating graphs and visualizing your data."
|
||||
canonical="https://jsoncrack.com/sign-up"
|
||||
/>
|
||||
<Text fw={500}>JSON Crack is no longer accepting new sign-ups.</Text>
|
||||
<Text fz="sm" mt="md" c="gray.6">
|
||||
For advanced features, please visit{" "}
|
||||
<Anchor td="underline" href="https://todiagram.com" inherit>
|
||||
ToDiagram
|
||||
</Anchor>{" "}
|
||||
or you can continue to use JSON Crack without an account.
|
||||
</Text>
|
||||
<Center my="md">
|
||||
<Link href="/editor" prefetch={false} passHref>
|
||||
<Button size="md" color="dark" radius="md">
|
||||
Go to editor
|
||||
</Button>
|
||||
</Link>
|
||||
</Center>
|
||||
<Center mt={50}>
|
||||
<Anchor component={Link} prefetch={false} href="/sign-in" c="dark" fz="sm">
|
||||
Already have an account?
|
||||
<Text component="span" fz="sm" c="blue" ml={4}>
|
||||
Sign in
|
||||
</Text>
|
||||
<Anchor component={Link} href="/sign-in">
|
||||
<Button color="dark" radius="sm" mt="lg" fullWidth>
|
||||
Back to login
|
||||
</Button>
|
||||
</Anchor>
|
||||
</Paper>
|
||||
) : (
|
||||
<>
|
||||
<Paper mx="auto" maw={400} mt={100} p="lg" withBorder>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
name="name"
|
||||
onChange={e => setUserData(d => ({ ...d, display_name: e.target.value }))}
|
||||
required
|
||||
label="Name"
|
||||
placeholder="John Doe"
|
||||
radius="sm"
|
||||
style={{ color: "black" }}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
name="email"
|
||||
onChange={e => setUserData(d => ({ ...d, email: e.target.value }))}
|
||||
type="email"
|
||||
required
|
||||
label="Email"
|
||||
placeholder="hello@jsoncrack.com"
|
||||
radius="sm"
|
||||
style={{ color: "black" }}
|
||||
/>
|
||||
|
||||
<PasswordInput
|
||||
name="password"
|
||||
onChange={e => setUserData(d => ({ ...d, password: e.target.value }))}
|
||||
min={6}
|
||||
required
|
||||
label="Password"
|
||||
placeholder="∗∗∗∗∗∗∗∗∗∗"
|
||||
radius="sm"
|
||||
style={{ color: "black" }}
|
||||
/>
|
||||
|
||||
<Button color="dark" type="submit" loading={loading}>
|
||||
Sign up for free
|
||||
</Button>
|
||||
|
||||
<Divider color="dimmed" label="OR CONTINUE WITH" labelPosition="center" />
|
||||
|
||||
<Flex gap="sm">
|
||||
<Button
|
||||
radius="sm"
|
||||
fullWidth
|
||||
leftSection={<AiOutlineGoogle size="20" />}
|
||||
onClick={() => handleLoginClick("google")}
|
||||
color="red"
|
||||
variant="outline"
|
||||
>
|
||||
Google
|
||||
</Button>
|
||||
<Button
|
||||
radius="sm"
|
||||
leftSection={<AiOutlineGithub size="20" />}
|
||||
onClick={() => handleLoginClick("github")}
|
||||
color="dark"
|
||||
variant="outline"
|
||||
fullWidth
|
||||
loading={loading}
|
||||
>
|
||||
GitHub
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<Divider mx={-20} />
|
||||
|
||||
<Text fz="xs" c="gray">
|
||||
By signing up, you agree to our{" "}
|
||||
<Anchor
|
||||
fz="xs"
|
||||
component={Link}
|
||||
prefetch={false}
|
||||
href="/legal/terms"
|
||||
c="gray"
|
||||
fw={500}
|
||||
>
|
||||
Terms of Service
|
||||
</Anchor>{" "}
|
||||
and{" "}
|
||||
<Anchor
|
||||
fz="xs"
|
||||
component={Link}
|
||||
prefetch={false}
|
||||
href="/legal/privacy"
|
||||
c="gray"
|
||||
fw={500}
|
||||
>
|
||||
Privacy Policy
|
||||
</Anchor>
|
||||
. Need help?{" "}
|
||||
<Anchor
|
||||
fz="xs"
|
||||
component={Link}
|
||||
href="mailto:contact@jsoncrack.com"
|
||||
c="gray"
|
||||
fw={500}
|
||||
>
|
||||
Get in touch.
|
||||
</Anchor>
|
||||
</Text>
|
||||
</Stack>
|
||||
</form>
|
||||
</Paper>
|
||||
|
||||
<Center my="xl">
|
||||
<Anchor component={Link} href="/sign-in" c="gray.5" fw="bold">
|
||||
Already have an account?
|
||||
</Anchor>
|
||||
</Center>
|
||||
</>
|
||||
)}
|
||||
</Layout>
|
||||
</Anchor>
|
||||
</Center>
|
||||
</AuthLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { useMantineColorScheme } from "@mantine/core";
|
||||
import { ThemeProvider } from "styled-components";
|
||||
import { NextSeo } from "next-seo";
|
||||
import toast from "react-hot-toast";
|
||||
import { darkTheme, lightTheme } from "src/constants/theme";
|
||||
import { Toolbar } from "src/containers/Toolbar";
|
||||
@ -68,9 +68,7 @@ const WidgetPage = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
</Head>
|
||||
<NextSeo noindex nofollow />
|
||||
<ThemeProvider theme={theme === "dark" ? darkTheme : lightTheme}>
|
||||
<Toolbar isWidget />
|
||||
<GraphView isWidget />
|
||||
|
Loading…
x
Reference in New Issue
Block a user