feat: improve general & pricing ui

This commit is contained in:
AykutSarac 2024-01-03 17:46:36 +03:00
parent 7c84ca9fa3
commit 2a5e57fd39
No known key found for this signature in database
12 changed files with 243 additions and 230 deletions

View File

@ -26,7 +26,7 @@
</p>
<p align="center">
<img src="./public/assets/jsoncrack-screenshot.webp" alt="Product Preview" />
<img src="./public/assets/jsoncrack-screenshot.jpeg" alt="Product Preview" />
</p>
# JSON Crack (jsoncrack.com)

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

View File

@ -1,15 +1,5 @@
import useUser from "src/store/useUser";
export const baseURL = process.env.NEXT_PUBLIC_BASE_URL as string;
export const paymentURL = () => {
const email = useUser.getState().user?.email;
let url = process.env.NEXT_PUBLIC_PAYMENT_URL as string;
if (email) url += `?checkout[email]=${email}`;
return url;
};
// Example taken from https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json
const sampleJson = Object.freeze({
squadName: "Super hero squad",

View File

@ -89,7 +89,7 @@ export const Toolbar: React.FC<{ isWidget?: boolean }> = ({ isWidget = false })
</Group>
)}
<Group gap="xs" justify="right" w="100%" style={{ flexWrap: "nowrap" }}>
{!premium && (
{!premium && !isWidget && (
<Styles.StyledToolElement onClick={() => setVisible("premium")(true)}>
<Text display="flex" c="teal" fz="xs" fw="bold" style={{ textAlign: "center", gap: 4 }}>
<MdWorkspacePremium size="18" />

View File

@ -28,6 +28,7 @@ export const StyledToolElement = styled.button<{ $hide?: boolean }>`
color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
padding: 6px;
border-radius: 3px;
white-space: nowrap;
&:hover {
background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0);

View File

@ -2,7 +2,6 @@ import React from "react";
import { Button, List, ThemeIcon, Title } from "@mantine/core";
import styled from "styled-components";
import { BsCheck } from "react-icons/bs";
import { paymentURL } from "src/constants/data";
import { JSONCrackLogo } from "src/layout/JsonCrackLogo";
const StyledPremiumView = styled.div`
@ -187,7 +186,7 @@ export const PremiumView = () => (
fw="bolder"
variant="gradient"
gradient={{ from: "blue", to: "teal" }}
href={paymentURL()}
href="/pricing"
target="_blank"
>
UPGRADE TO PREMIUM

View File

@ -2,6 +2,7 @@ import React from "react";
import { Anchor, Button, Group, Modal, Text } from "@mantine/core";
import styled from "styled-components";
import { VscCode } from "react-icons/vsc";
import { VscArrowRight } from "react-icons/vsc";
const StyledAlert = styled.div`
position: fixed;
@ -65,11 +66,15 @@ const ExternalMode = () => {
</Button>
<Modal title="External Host of JSON Crack" opened={isOpen} onClose={closeModal} centered>
<Group>
<StyledTitle>Hi! Did you like the editor?</StyledTitle>
<StyledTitle>Dear valued user,</StyledTitle>
<Text>
You are currently using the external release of the{" "}
<Anchor href="https://jsoncrack.com/pricing">JSON Crack</Anchor>. Please consider
supporting by buying premium
We would like to inform you that you are presently utilizing the external release of the{" "}
<Anchor href="https://jsoncrack.com">JSON Crack</Anchor>. Your continued support is
crucial in sustaining and improving our services.
<br />
<br />
We kindly encourage you to consider upgrading to the premium version, which not only
enhances your experience but also contributes to the ongoing development of JSON Crack.
</Text>
</Group>
<Group pt="lg" justify="right">
@ -78,10 +83,11 @@ const ExternalMode = () => {
component="a"
href="https://jsoncrack.com/pricing"
target="_blank"
variant="light"
variant="outline"
color="red"
rightSection={<VscArrowRight />}
>
JSON Crack
</Button>
</Group>
</Modal>

View File

@ -11,6 +11,7 @@ const StyledTitle = styled.div<{ fontSize: string }>`
white-space: nowrap;
z-index: 10;
color: ${({ theme }) => theme.INTERACTIVE_HOVER};
vertical-align: middle;
`;
interface LogoProps extends React.ComponentPropsWithoutRef<"a"> {

View File

@ -9,15 +9,25 @@ const StyledLayoutWrapper = styled.div`
`;
const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [rendered, setRendered] = React.useState(false);
React.useEffect(() => {
setRendered(true);
}, []);
if (!rendered) return null;
return (
<MantineProvider forceColorScheme="light">
<ThemeProvider theme={lightTheme}>
<StyledLayoutWrapper>
<Navbar />
{children}
</StyledLayoutWrapper>
</ThemeProvider>
</MantineProvider>
<React.Suspense>
<MantineProvider forceColorScheme="light">
<ThemeProvider theme={lightTheme}>
<StyledLayoutWrapper>
<Navbar />
{children}
</StyledLayoutWrapper>
</ThemeProvider>
</MantineProvider>
</React.Suspense>
);
};

View File

@ -1,12 +1,17 @@
import React from "react";
import Link from "next/link";
import { Button, Menu } from "@mantine/core";
import { Alert, Button, Menu } from "@mantine/core";
import styled from "styled-components";
import { BiChevronDown } from "react-icons/bi";
import useUser from "src/store/useUser";
import { JSONCrackLogo } from "../JsonCrackLogo";
const StyledNavbarWrapper = styled.div``;
const StyledNavbarWrapper = styled.div`
position: sticky;
top: 0;
left: 0;
z-index: 3;
`;
const StyledNavbar = styled.div`
display: flex;
@ -16,7 +21,7 @@ const StyledNavbar = styled.div`
height: 56px;
margin: 0 auto;
border-bottom: 1px solid gray;
background: rgba(255, 255, 255, 0.75);
background: #ffffff;
padding: 8px 16px;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.35),
@ -37,8 +42,6 @@ const StyledNavbar = styled.div`
const Left = styled.div``;
const Middle = styled.div``;
const Right = styled.div`
display: flex;
gap: 16px;
@ -52,17 +55,22 @@ export const Navbar = () => {
<StyledNavbar>
<Left>
<JSONCrackLogo />
</Left>
<Middle className="hide-mobile">
<Button component={Link} href="/pricing" variant="subtle" color="dark" radius="md">
<Button
ml="lg"
component={Link}
href="/pricing"
variant="subtle"
color="black"
radius="md"
>
Pricing
</Button>
<Button
component={Link}
component="a"
href="https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode"
prefetch={false}
target="_blank"
variant="subtle"
color="dark"
color="black"
radius="md"
>
VS Code
@ -72,7 +80,7 @@ export const Navbar = () => {
href="/docs"
prefetch={false}
variant="subtle"
color="dark"
color="black"
radius="md"
>
Docs
@ -81,7 +89,7 @@ export const Navbar = () => {
<Menu.Target>
<Button
variant="subtle"
color="dark"
color="black"
radius="md"
rightSection={<BiChevronDown size="18" />}
>
@ -108,7 +116,7 @@ export const Navbar = () => {
<Menu.Target>
<Button
variant="subtle"
color="dark"
color="black"
radius="md"
rightSection={<BiChevronDown size="18" />}
>
@ -130,25 +138,29 @@ export const Navbar = () => {
</Menu.Item>
</Menu.Dropdown>
</Menu>
</Middle>
</Left>
<Right>
{!isAuthenticated && (
<Button
component={Link}
href="/sign-in"
prefetch={false}
variant="outline"
color="grape.9"
color="dark"
className="hide-mobile"
>
Login
</Button>
)}
<Button color="grape.9" component={Link} href="/editor" prefetch={false}>
<Button color="dark" component={Link} href="/editor" prefetch={false}>
Editor
</Button>
</Right>
</StyledNavbar>
<Link href="/pricing">
<Alert color="red" variant="filled" radius={0} fw="bold">
Unlock premium features now with 30% discount on the Premium plan!
</Alert>
</Link>
</StyledNavbarWrapper>
);
};

View File

@ -12,60 +12,11 @@ const StyledHeroSection = styled.div`
position: relative;
background-size: 100% 100%;
margin-bottom: -48px;
background-position:
0px 0px,
0px 0px,
0px 0px,
0px 0px,
0px 0px;
background-image: radial-gradient(49% 81% at 45% 47%, #26001fff 1%, #60006a00 100%),
radial-gradient(113% 91% at 17% -2%, #0f000cff 1%, #ff000000 99%),
radial-gradient(142% 91% at 83% 7%, #0f000cff 1%, #ff000000 99%),
radial-gradient(142% 91% at -6% 74%, #0f000cff 1%, #ff000000 99%),
radial-gradient(142% 91% at 111% 84%, #0f000cff 0%, #5b004eff 99%);
overflow: hidden;
@keyframes shine {
0% {
background-position:
0 0,
3px 60px,
130px 270px,
70px 100px;
}
100% {
background-position:
1000px 1000px,
1000px 1000px,
1000px 1000px,
1000px 1000px;
}
}
&::before {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
content: "";
background-color: transparent;
background-image: radial-gradient(white, rgba(255, 255, 255, 0.2) 2px, transparent 2px),
radial-gradient(white, rgba(255, 255, 255, 0.15) 1px, transparent 1px),
radial-gradient(white, rgba(255, 255, 255, 0.1) 2px, transparent 2px),
radial-gradient(rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.1) 2px, transparent 1px);
background-size:
800px 800px,
400px 400px,
300px 300px,
200px 200px;
background-position:
0 0,
3px 60px,
130px 270px,
70px 100px;
animation: shine 100s linear infinite alternate; /* Use alternate to make it smoother */
}
background-image: radial-gradient(49% 81% at 45% 47%, #21415f 1%, #60006a00 100%),
radial-gradient(113% 91% at 17% -2%, #1b1b1b 1%, #ff000000 99%),
radial-gradient(142% 91% at 83% 7%, #1b1b1b 1%, #ff000000 99%),
radial-gradient(142% 91% at -6% 74%, #1b1b1b 1%, #ff000000 99%),
radial-gradient(142% 91% at 111% 84%, #1b1b1b 0%, #5b004eff 99%);
@media only screen and (max-width: 1240px) {
flex-direction: column;
@ -81,7 +32,7 @@ const StyledHeroSectionBody = styled.div`
overflow: hidden;
backdrop-filter: blur(1px);
-webkit-backdrop-filter: blur(1px);
height: calc(100vh - 56px);
height: calc(100vh - 111px);
text-align: center;
@media only screen and (max-width: 1200px) {
@ -122,7 +73,7 @@ const StyledHeroText = styled.p`
`;
const StyledHeroTitle = styled.h1`
color: #d0c9c9;
color: #dacfcf;
font-size: 40px;
font-weight: 800;
text-align: center;
@ -157,21 +108,20 @@ const HeroSection = () => (
</StyledHeroText>
<Group gap="xl">
<Button
color="orange"
variant="light"
color="gray.2"
c="indigo"
component={Link}
href="/editor"
fw="bold"
rightSection={<FaChevronRight />}
size="xl"
style={{ border: "2px solid orange" }}
visibleFrom="md"
>
GO TO EDITOR
</Button>
<Button
color="orange"
variant="light"
color="gray.2"
c="indigo"
component={Link}
href="/editor"
fw="bold"

View File

@ -1,31 +1,73 @@
import React from "react";
import Head from "next/head";
import { Flex, Stack, Button, List, ThemeIcon, Divider, Text, Paper, Badge } from "@mantine/core";
import {
Flex,
Stack,
Button,
List,
ThemeIcon,
Text,
Paper,
Badge,
SegmentedControl,
Center,
} from "@mantine/core";
import { AiOutlineInfoCircle } from "react-icons/ai";
import { BsCheck, BsX } from "react-icons/bs";
import { paymentURL } from "src/constants/data";
import { FaBolt } from "react-icons/fa6";
import { VscArrowRight } from "react-icons/vsc";
import Layout from "src/layout/Layout";
import useUser from "src/store/useUser";
const purchaseLinks = {
monthly:
"https://herowand.lemonsqueezy.com/checkout/buy/ce30521f-c7cc-44f3-9435-995d3260ba22?media=0&enabled=67805%2C82417",
annual:
"https://herowand.lemonsqueezy.com/checkout/buy/577928ea-fb09-4076-9307-3e5931b35ad0?media=0&enabled=67805%2C82417",
};
const Pricing = () => {
const email = useUser(state => state.user?.email);
const [isMonthly, setIsMonthly] = React.useState(true);
const paymentURL = (url: string) => {
if (email) url += `?checkout[email]=${email}`;
return url;
};
return (
<Layout>
<Head>
<title>Pricing - JSON Crack</title>
</Head>
<Flex gap="lg" wrap="wrap" justify="center" my={60} w="fit-content" p="lg" mx="auto">
<Paper p="xl" radius="lg" withBorder w={325}>
<Center my="lg">
<SegmentedControl
onChange={v => setIsMonthly(v === "Monthly")}
size="lg"
data={["Monthly", "Yearly"]}
w={200}
radius="xl"
/>
</Center>
<Flex gap="lg" wrap="wrap" justify="center" w="fit-content" p="lg" mx="auto">
<Paper bg="white" p="xl" radius="lg" withBorder w={325}>
<Flex justify="space-between">
<Stack gap="0">
<Text fz="xl" fw="bold" c="dark">
<Text fz="xl" fw="bold" c="black">
Free
</Text>
<Text fz="xs" fw="bold" c="gray.6">
Free - forever.
<Text fz={32} fw="bold" c="black">
$0
</Text>
<Text fz="xs" c="gray.6">
billed {isMonthly ? "monthly" : "annually"}
</Text>
</Stack>
</Flex>
<Divider my="sm" />
<Flex direction="column" justify="space-between" h={250}>
<Button my="md" size="md" radius="md" color="black" variant="outline" fullWidth>
Free
</Button>
<Flex direction="column" justify="space-between" h={200}>
<List
spacing="xs"
size="sm"
@ -38,20 +80,26 @@ const Pricing = () => {
}
>
<List.Item>
<Text c="dark" fz="sm">
Maximum capability
</Text>
</List.Item>
<List.Item>
<Text c="dark" fz="sm">
<Text c="black" fz="sm">
Save & share up to 15 files
</Text>
</List.Item>
<List.Item>
<Text c="dark" fz="sm">
<Text c="black" fz="sm">
Visualize all data formats
</Text>
</List.Item>
<List.Item
icon={
<ThemeIcon color="gray.5" size={20} radius="xl">
<BsX size="1rem" />
</ThemeIcon>
}
>
<Text c="gray.6" fz="sm">
Maximum Capability
</Text>
</List.Item>
<List.Item
icon={
<ThemeIcon color="gray.5" size={20} radius="xl">
@ -75,108 +123,45 @@ const Pricing = () => {
</Text>
</List.Item>
</List>
<Button size="md" radius="md" color="orange" variant="outline">
Current
</Button>
</Flex>
</Paper>
<Paper p="xl" radius="lg" bg="#301e55" withBorder w={325}>
<Paper p="xl" radius="lg" bg="white" withBorder w={325}>
<Flex justify="space-between">
<Stack gap="0">
<Text c="white" fz="xl" fw="bold">
Premium
<Text fz="xl" fw="bold" c="black">
Premium{" "}
<Badge color="red" ml="sm" leftSection={<FaBolt />}>
Most Popular
</Badge>
</Text>
<Badge size="xs" mt="auto" color="violet" opacity={0.4}>
Annual Save 20%
</Badge>
</Stack>
<Paper py={5} px="sm" bg="#442f71">
<Stack gap="0" align="center" justify="center">
<Text fz="lg" c="white" fw="bolder">
$7
<Flex gap="xs" align="center">
<Text fz={32} fw="bold" c="black">
${isMonthly ? "5" : "60"}
</Text>
<Text fz="xs" c="gray.4" fw="bold">
Per month
<Text fz="md" fw="bold" c="dimmed" style={{ textDecoration: "line-through" }}>
${isMonthly ? "7" : "84"}
</Text>
</Stack>
</Paper>
</Flex>
<Divider my="sm" />
<Flex direction="column" justify="space-between" h={250}>
<List
spacing="xs"
size="sm"
mt="lg"
center
icon={
<ThemeIcon color="white" size={20} radius="xl">
<BsCheck size="1rem" color="black" />
</ThemeIcon>
}
>
<List.Item>
<Text c="white" fz="sm">
Maximum capability
</Text>
</List.Item>
<List.Item>
<Text c="white" fz="sm">
Save & share up to 200 files
</Text>
</List.Item>
<List.Item>
<Text c="white" fz="sm">
Visualize all data formats
</Text>
</List.Item>
<List.Item>
<Text c="white" fz="sm">
JSON Schema
</Text>
</List.Item>
<List.Item>
<Text c="white" fz="sm">
Edit data through graph
</Text>
</List.Item>
</List>
<Button
component="a"
href={paymentURL()}
target="_blank"
size="md"
radius="md"
color="orange"
>
Upgrade
</Button>
</Flex>
</Paper>
<Paper p="xl" radius="lg" withBorder w={325}>
<Flex justify="space-between">
<Stack gap="0">
<Text fz="xl" fw="bold" c="dark">
Enterprise
</Text>
<Text fz="xs" fw="bold" c="gray.6">
For Teams & Organizations
</Flex>
<Text fz="xs" c="gray.6">
billed {isMonthly ? "monthly" : "annually"}
</Text>
</Stack>
<Paper py={5} px="sm" bg="gray.0" radius="xs">
<Stack gap="0" align="center" justify="center">
<Text fz="lg" fw="bolder" c="dark">
$120
</Text>
<Text fz="xs" c="gray.6" fw="bold">
Per month
</Text>
</Stack>
</Paper>
</Flex>
<Divider my="sm" />
<Flex direction="column" justify="space-between" h={250}>
<Button
component="a"
href={paymentURL(isMonthly ? purchaseLinks.monthly : purchaseLinks.annual)}
target="_blank"
size="md"
radius="md"
color="dark"
fullWidth
my="md"
rightSection={<VscArrowRight />}
>
Upgrade
</Button>
<Flex direction="column" justify="space-between" h={200}>
<List
spacing="xs"
size="sm"
@ -187,44 +172,103 @@ const Pricing = () => {
<BsCheck size="1rem" />
</ThemeIcon>
}
c="dark"
>
<List.Item>
<Text c="black" fz="sm">
Maximum capability
</Text>
</List.Item>
<List.Item>
<Text c="black" fz="sm">
Save & share up to 200 files
</Text>
</List.Item>
<List.Item>
<Text c="black" fz="sm">
Visualize all data formats
</Text>
</List.Item>
<List.Item>
<Text c="black" fz="sm">
JSON Schema
</Text>
</List.Item>
<List.Item>
<Text c="black" fz="sm">
Edit data through graph
</Text>
</List.Item>
</List>
</Flex>
</Paper>
<Paper bg="white" p="xl" radius="lg" withBorder w={325}>
<Flex justify="space-between">
<Stack gap="0">
<Text fz="xl" fw="bold" c="black">
Enterprise
</Text>
<Text fz={32} fw="bold" c="black">
Custom
</Text>
<Text fz="xs" c="gray.6">
billed {isMonthly ? "monthly" : "annually"}
</Text>
</Stack>
</Flex>
<Button
component="a"
href="mailto:contact@jsoncrack.com?subject=Enterprise%20Plan%20Inquiry&body=Please%20replace%20this%20text%20with%20your%20inquiry%20content."
target="_blank"
size="md"
radius="md"
variant="outline"
color="dark"
fullWidth
my="md"
rightSection={<VscArrowRight />}
>
Contact Us
</Button>
<Flex direction="column" justify="space-between" h={200}>
<List
spacing="xs"
size="sm"
mt="lg"
center
icon={
<ThemeIcon color="dark.6" size={20} radius="xl">
<BsCheck size="1rem" />
</ThemeIcon>
}
c="black"
>
<List.Item>Everything from previous plans</List.Item>
<List.Item
icon={
<ThemeIcon color="orange" size={20} radius="xl">
<ThemeIcon color="dark.6" size={20} radius="xl">
<BsCheck size="1rem" />
</ThemeIcon>
}
c="dark"
c="black"
>
Unlimited premium accounts for work email
</List.Item>
<List.Item
icon={
<ThemeIcon color="orange" size={20} radius="xl">
<ThemeIcon color="dark.6" size={20} radius="xl">
<BsCheck size="1rem" />
</ThemeIcon>
}
c="dark"
c="black"
>
Shared cloud in app
</List.Item>
</List>
<Button
component="a"
href={paymentURL()}
target="_blank"
size="md"
radius="md"
color="orange"
>
Upgrade
</Button>
</Flex>
</Paper>
</Flex>
<Text size="sm" c="dimmed" style={{ textAlign: "center" }}>
<Text pt="sm" size="sm" c="dimmed" style={{ textAlign: "center" }}>
<AiOutlineInfoCircle style={{ marginRight: "4px" }} />
Payment email must be matching with the account registered to the JSON Crack.
</Text>