mirror of
https://github.com/AykutSarac/jsoncrack.com.git
synced 2025-01-12 19:02:53 +08:00
feat: setup supabase
This commit is contained in:
parent
23b3eefad2
commit
ef82c68eeb
@ -11,11 +11,6 @@
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", { "avoidEscape": true }],
|
||||
"semi": ["error", "always"],
|
||||
"padding-line-between-statements": [
|
||||
"error",
|
||||
{ "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" },
|
||||
{ "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"] }
|
||||
],
|
||||
"space-before-function-paren": [
|
||||
"error",
|
||||
{
|
||||
|
@ -22,6 +22,9 @@
|
||||
"@mantine/prism": "^6.0.17",
|
||||
"@monaco-editor/react": "^4.5.1",
|
||||
"@sentry/nextjs": "^7.60.0",
|
||||
"@supabase/auth-helpers-nextjs": "^0.7.3",
|
||||
"@supabase/auth-helpers-react": "^0.4.1",
|
||||
"@supabase/supabase-js": "^2.31.0",
|
||||
"@tanstack/react-query": "^4.32.0",
|
||||
"allotment": "^1.19.2",
|
||||
"altogic": "^2.3.9",
|
||||
|
@ -30,6 +30,7 @@ const StyledBottomBar = styled.div`
|
||||
max-height: 27px;
|
||||
height: 27px;
|
||||
padding: 0 6px;
|
||||
z-index: 35;
|
||||
|
||||
@media screen and (max-width: 320px) {
|
||||
display: none;
|
||||
@ -73,7 +74,7 @@ const StyledBottomBarItem = styled.button<{ $error?: boolean }>`
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.4;
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
`;
|
||||
@ -116,19 +117,20 @@ export const BottomBar = () => {
|
||||
try {
|
||||
setIsUpdating(true);
|
||||
toast.loading("Saving document...", { id: "fileSave" });
|
||||
const res = await saveToCloud(query?.json ?? null, getContents(), getFormat());
|
||||
|
||||
if (res.errors && res.errors.items.length > 0) throw res.errors;
|
||||
if (res.data._id) replace({ query: { json: res.data._id } });
|
||||
const { data, error } = await saveToCloud({
|
||||
id: query?.json,
|
||||
contents: getContents(),
|
||||
format: getFormat(),
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
if (data[0].id) replace({ query: { json: data[0].id } });
|
||||
|
||||
toast.success("Document saved to cloud", { id: "fileSave" });
|
||||
setHasChanges(false);
|
||||
} catch (error: any) {
|
||||
if (error?.items?.length > 0) {
|
||||
return toast.error(error.items[0].message, { id: "fileSave", duration: 5000 });
|
||||
}
|
||||
|
||||
toast.error("Failed to save document!", { id: "fileSave" });
|
||||
toast.error(error.message, { id: "fileSave" });
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
@ -155,14 +157,18 @@ export const BottomBar = () => {
|
||||
if (!query.json) return handleSaveJson();
|
||||
setIsUpdating(true);
|
||||
|
||||
const res = await updateJson(query.json as string, { private: !isPrivate });
|
||||
const { data: updatedJsonData, error } = await updateJson(query.json as string, {
|
||||
private: !isPrivate,
|
||||
});
|
||||
|
||||
if (!res.errors?.items.length) {
|
||||
setIsPrivate(res.data.private);
|
||||
if (error) return toast.error(error.message);
|
||||
|
||||
if (updatedJsonData[0]) {
|
||||
setIsPrivate(updatedJsonData[0].private);
|
||||
toast.success(`Document set to ${isPrivate ? "public" : "private"}.`);
|
||||
} else throw res.errors;
|
||||
} else throw error;
|
||||
} catch (error) {
|
||||
toast.error("An error occurred while updating document!");
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
@ -178,7 +184,7 @@ export const BottomBar = () => {
|
||||
<StyledLeft>
|
||||
<StyledBottomBarItem onClick={handleLoginClick}>
|
||||
<VscAccount />
|
||||
{user ? user.name : "Login"}
|
||||
{user?.user_metadata.name ?? "Login"}
|
||||
{premium && (
|
||||
<Badge size="sm" color="orange" radius="sm" fw="bold">
|
||||
PREMIUM
|
||||
@ -211,24 +217,25 @@ export const BottomBar = () => {
|
||||
</Flex>
|
||||
)}
|
||||
</StyledBottomBarItem>
|
||||
<StyledBottomBarItem onClick={handleSaveJson} disabled={isUpdating}>
|
||||
{hasChanges ? <AiOutlineCloudUpload /> : <AiOutlineCloudSync />}
|
||||
{hasChanges ? (query?.json ? "Unsaved Changes" : "Create Document") : "Saved"}
|
||||
</StyledBottomBarItem>
|
||||
{data && (
|
||||
<>
|
||||
{typeof data.private !== "undefined" && (
|
||||
<StyledBottomBarItem onClick={setPrivate} disabled={isUpdating}>
|
||||
{isPrivate ? <AiOutlineLock /> : <AiOutlineUnlock />}
|
||||
{isPrivate ? "Private" : "Public"}
|
||||
</StyledBottomBarItem>
|
||||
)}
|
||||
<StyledBottomBarItem onClick={() => setVisible("share")(true)} disabled={isPrivate}>
|
||||
<AiOutlineLink />
|
||||
Share
|
||||
</StyledBottomBarItem>
|
||||
</>
|
||||
{(data?.owner_id === user?.id || (!data && user)) && (
|
||||
<StyledBottomBarItem onClick={handleSaveJson} disabled={isUpdating}>
|
||||
{hasChanges ? <AiOutlineCloudUpload /> : <AiOutlineCloudSync />}
|
||||
{hasChanges ? (query?.json ? "Unsaved Changes" : "Create Document") : "Saved"}
|
||||
</StyledBottomBarItem>
|
||||
)}
|
||||
{data?.owner_id === user?.id && (
|
||||
<StyledBottomBarItem onClick={setPrivate} disabled={isUpdating}>
|
||||
{isPrivate ? <AiOutlineLock /> : <AiOutlineUnlock />}
|
||||
{isPrivate ? "Private" : "Public"}
|
||||
</StyledBottomBarItem>
|
||||
)}
|
||||
<StyledBottomBarItem
|
||||
onClick={() => setVisible("share")(true)}
|
||||
disabled={isPrivate || !data}
|
||||
>
|
||||
<AiOutlineLink />
|
||||
Share
|
||||
</StyledBottomBarItem>
|
||||
{liveTransform ? (
|
||||
<StyledBottomBarItem onClick={() => toggleLiveTransform(false)}>
|
||||
<VscSync />
|
||||
|
@ -1,6 +1,8 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { Modal, Group, Button, Badge, Avatar, Grid, Divider, ModalProps } from "@mantine/core";
|
||||
import { useUser as useSupaUser } from "@supabase/auth-helpers-react";
|
||||
import dayjs from "dayjs";
|
||||
import { IoRocketSharp } from "react-icons/io5";
|
||||
import useModal from "src/store/useModal";
|
||||
import useUser from "src/store/useUser";
|
||||
@ -43,7 +45,7 @@ const StyledContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const AccountModal: React.FC<ModalProps> = ({ opened, onClose }) => {
|
||||
const user = useUser(state => state.user);
|
||||
const user = useSupaUser();
|
||||
const isPremium = useUser(state => state.premium);
|
||||
const premiumCancelled = useUser(state => state.premiumCancelled);
|
||||
const setVisible = useModal(state => state.setVisible);
|
||||
@ -51,16 +53,21 @@ export const AccountModal: React.FC<ModalProps> = ({ opened, onClose }) => {
|
||||
|
||||
return (
|
||||
<Modal title="Account" opened={opened} onClose={onClose} centered>
|
||||
<StyledTitle>Hello, {user?.name}!</StyledTitle>
|
||||
<StyledTitle>Hello, {user?.user_metadata.name}!</StyledTitle>
|
||||
<Group py="sm">
|
||||
<Grid gutter="xs">
|
||||
<Grid.Col span={2}>
|
||||
<Avatar size="lg" radius="lg" src={user?.profilePicture} alt={user?.name} />
|
||||
<Avatar
|
||||
size="lg"
|
||||
radius="lg"
|
||||
src={user?.user_metadata.avatar_url}
|
||||
alt={user?.user_metadata.name}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4}>
|
||||
<StyledContainer>
|
||||
USERNAME
|
||||
<div>{user?.name}</div>
|
||||
<div>{user?.user_metadata.name}</div>
|
||||
</StyledContainer>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
@ -82,15 +89,17 @@ export const AccountModal: React.FC<ModalProps> = ({ opened, onClose }) => {
|
||||
<Grid.Col span={6}>
|
||||
<StyledContainer>
|
||||
EMAIL
|
||||
<div>{user?.email}</div>
|
||||
</StyledContainer>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4}>
|
||||
<StyledContainer>
|
||||
REGISTRATION
|
||||
<div>{user?.signUpAt && new Date(user.signUpAt).toDateString()}</div>
|
||||
<div>{user?.user_metadata.email}</div>
|
||||
</StyledContainer>
|
||||
</Grid.Col>
|
||||
{user?.created_at && (
|
||||
<Grid.Col span={4}>
|
||||
<StyledContainer>
|
||||
REGISTRATION
|
||||
<div>{dayjs(user.created_at).format("DD MMMM YYYY")}</div>
|
||||
</StyledContainer>
|
||||
</Grid.Col>
|
||||
)}
|
||||
</Grid>
|
||||
</Group>
|
||||
<Divider py="xs" />
|
||||
|
@ -1,252 +1,242 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
Modal,
|
||||
Group,
|
||||
Button,
|
||||
Text,
|
||||
Stack,
|
||||
Loader,
|
||||
Center,
|
||||
Divider,
|
||||
ScrollArea,
|
||||
ModalProps,
|
||||
Table,
|
||||
ActionIcon,
|
||||
Badge,
|
||||
Paper,
|
||||
Flex,
|
||||
DefaultMantineColor,
|
||||
Input,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
RingProgress,
|
||||
UnstyledButton,
|
||||
} from "@mantine/core";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import toast from "react-hot-toast";
|
||||
import { AiOutlineEdit, AiOutlineLock, AiOutlinePlus, AiOutlineUnlock } from "react-icons/ai";
|
||||
import { AiOutlineLink } from "react-icons/ai";
|
||||
import { FaTrash } from "react-icons/fa";
|
||||
import { IoRocketSharp } from "react-icons/io5";
|
||||
import { MdFileOpen } from "react-icons/md";
|
||||
import { VscAdd, VscEdit } from "react-icons/vsc";
|
||||
import { deleteJson, getAllJson, saveToCloud, updateJson } from "src/services/json";
|
||||
import useFile, { File } from "src/store/useFile";
|
||||
import useModal from "src/store/useModal";
|
||||
import useUser from "src/store/useUser";
|
||||
import { FileFormat } from "src/types/models";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const StyledJsonCard = styled.a<{ active?: boolean; create?: boolean }>`
|
||||
display: ${({ create }) => (create ? "block" : "flex")};
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
line-height: 20px;
|
||||
padding: 6px;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
height: 60px;
|
||||
background: ${({ active }) => active && "rgb(48, 98, 197)"};
|
||||
`;
|
||||
|
||||
const StyledInfo = styled.div``;
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDetils = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
gap: 4px;
|
||||
`;
|
||||
|
||||
const StyledCreateWrapper = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.6;
|
||||
height: 30px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const StyledNameInput = styled.input`
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
width: 90%;
|
||||
color: ${({ theme }) => theme.SEAGREEN};
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const GraphCard: React.FC<{ data: File; refetch: () => void; active: boolean }> = ({
|
||||
data,
|
||||
refetch,
|
||||
active,
|
||||
...props
|
||||
}) => {
|
||||
const [editMode, setEditMode] = React.useState(false);
|
||||
const [name, setName] = React.useState(data.name);
|
||||
const colorByFormat: Record<FileFormat, DefaultMantineColor> = {
|
||||
json: "orange",
|
||||
yaml: "blue",
|
||||
xml: "red",
|
||||
toml: "dark",
|
||||
csv: "grape",
|
||||
};
|
||||
|
||||
const UpdateNameModal: React.FC<{
|
||||
file: File | null;
|
||||
onClose: () => void;
|
||||
refetch: () => void;
|
||||
}> = ({ file, onClose, refetch }) => {
|
||||
const [name, setName] = React.useState("");
|
||||
const onSubmit = () => {
|
||||
if (!file) return;
|
||||
toast
|
||||
.promise(updateJson(data._id, { name }), {
|
||||
.promise(updateJson(file.id, { name }), {
|
||||
loading: "Updating document...",
|
||||
error: "Error occurred while updating document!",
|
||||
success: `Renamed document to ${name}`,
|
||||
})
|
||||
.then(refetch);
|
||||
.then(() => refetch());
|
||||
|
||||
setEditMode(false);
|
||||
};
|
||||
|
||||
const onDeleteClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
toast
|
||||
.promise(deleteJson(data._id), {
|
||||
loading: "Deleting file...",
|
||||
error: "An error occurred while deleting the file!",
|
||||
success: `Deleted ${name}!`,
|
||||
})
|
||||
.then(refetch);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper withBorder w="100%">
|
||||
<StyledJsonCard
|
||||
href={`?json=${data._id}`}
|
||||
as={editMode ? "div" : "a"}
|
||||
active={active}
|
||||
{...props}
|
||||
>
|
||||
<StyledInfo>
|
||||
{editMode ? (
|
||||
<form onSubmit={onSubmit}>
|
||||
<StyledNameInput
|
||||
value={name}
|
||||
onChange={e => setName(e.currentTarget.value)}
|
||||
onClick={e => e.preventDefault()}
|
||||
autoFocus
|
||||
/>
|
||||
<input type="submit" hidden />
|
||||
</form>
|
||||
) : (
|
||||
<StyledTitle
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
setEditMode(true);
|
||||
}}
|
||||
>
|
||||
<span>{name}</span>
|
||||
<AiOutlineEdit />
|
||||
</StyledTitle>
|
||||
)}
|
||||
<StyledDetils>
|
||||
{data.private ? <AiOutlineLock /> : <AiOutlineUnlock />}
|
||||
Last modified {dayjs(data.updatedAt).fromNow()}
|
||||
</StyledDetils>
|
||||
</StyledInfo>
|
||||
<Button variant="subtle" color="red" onClick={onDeleteClick}>
|
||||
<FaTrash />
|
||||
</Button>
|
||||
</StyledJsonCard>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
const CreateCard: React.FC<{ reachedLimit: boolean }> = ({ reachedLimit }) => {
|
||||
const { replace } = useRouter();
|
||||
const isPremium = useUser(state => state.premium);
|
||||
const getContents = useFile(state => state.getContents);
|
||||
const setHasChanges = useFile(state => state.setHasChanges);
|
||||
const setVisible = useModal(state => state.setVisible);
|
||||
const getFormat = useFile(state => state.getFormat);
|
||||
|
||||
const onCreate = async () => {
|
||||
try {
|
||||
toast.loading("Saving document...", { id: "fileSave" });
|
||||
const res = await saveToCloud(null, getContents(), getFormat());
|
||||
|
||||
if (res.errors && res.errors.items.length > 0) throw res.errors;
|
||||
|
||||
toast.success("Document saved to cloud", { id: "fileSave" });
|
||||
setHasChanges(false);
|
||||
replace({ query: { json: res.data._id } });
|
||||
} catch (error: any) {
|
||||
if (error?.items?.length > 0) {
|
||||
return toast.error(error.items[0].message, { id: "fileSave", duration: 7000 });
|
||||
}
|
||||
toast.error("Failed to save document!", { id: "fileSave" });
|
||||
}
|
||||
};
|
||||
|
||||
if (!isPremium && reachedLimit)
|
||||
return (
|
||||
<StyledJsonCard onClick={() => setVisible("premium")(true)}>
|
||||
<StyledCreateWrapper>
|
||||
<IoRocketSharp size="18" />
|
||||
<Text fw="bold">You reached max limit, upgrade to premium for more!</Text>
|
||||
</StyledCreateWrapper>
|
||||
</StyledJsonCard>
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledJsonCard onClick={onCreate} create>
|
||||
<StyledCreateWrapper>
|
||||
<AiOutlinePlus size="20" />
|
||||
<Text fw="bold">Create New File</Text>
|
||||
</StyledCreateWrapper>
|
||||
</StyledJsonCard>
|
||||
<Modal title="Update Document name" opened={!!file} onClose={onClose} centered>
|
||||
<Stack>
|
||||
<Input
|
||||
value={name}
|
||||
placeholder={file?.name}
|
||||
onChange={e => setName(e.currentTarget.value)}
|
||||
/>
|
||||
<Group position="right">
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={onSubmit}>Update</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const CloudModal: React.FC<ModalProps> = ({ opened, onClose }) => {
|
||||
const { isReady, query } = useRouter();
|
||||
|
||||
const { data, isFetching, refetch } = useQuery(["allJson", query], () => getAllJson(), {
|
||||
const totalQuota = useUser(state => (state.premium ? 200 : 25));
|
||||
const getContents = useFile(state => state.getContents);
|
||||
const setHasChanges = useFile(state => state.setHasChanges);
|
||||
const getFormat = useFile(state => state.getFormat);
|
||||
const [currentFile, setCurrentFile] = React.useState<File | null>(null);
|
||||
const { isReady, query, replace } = useRouter();
|
||||
const { data, refetch } = useQuery(["allJson", query], () => getAllJson(), {
|
||||
enabled: isReady && opened,
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal title="Saved On The Cloud" opened={opened} onClose={onClose} centered>
|
||||
<ScrollArea h={360}>
|
||||
<Stack py="sm">
|
||||
{isFetching ? (
|
||||
<Center>
|
||||
<Loader />
|
||||
</Center>
|
||||
) : (
|
||||
<>
|
||||
<CreateCard reachedLimit={data ? data?.data.result.length > 15 : false} />
|
||||
{data?.data?.result?.map(json => (
|
||||
<GraphCard
|
||||
data={json}
|
||||
key={json._id}
|
||||
refetch={refetch}
|
||||
active={query?.json === json._id}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
const onCreate = async () => {
|
||||
try {
|
||||
toast.loading("Saving document...", { id: "fileSave" });
|
||||
const { data, error } = await saveToCloud({ contents: getContents(), format: getFormat() });
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
toast.success("Document saved to cloud", { id: "fileSave" });
|
||||
setHasChanges(false);
|
||||
replace({ query: { json: data[0].id } });
|
||||
onClose();
|
||||
} catch (error: any) {
|
||||
toast.error("Failed to save document!", { id: "fileSave" });
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const onDeleteClick = React.useCallback(
|
||||
(file: File) => {
|
||||
toast
|
||||
.promise(deleteJson(file.id), {
|
||||
loading: "Deleting file...",
|
||||
error: "An error occurred while deleting the file!",
|
||||
success: `Deleted ${file.name}!`,
|
||||
})
|
||||
.then(() => refetch());
|
||||
},
|
||||
[refetch]
|
||||
);
|
||||
|
||||
const copyShareLink = React.useCallback((fileId: string) => {
|
||||
const shareLink = `${window.location.origin}/?json=${fileId}`;
|
||||
navigator.clipboard.writeText(shareLink);
|
||||
toast.success("Copied share link to clipboard!");
|
||||
}, []);
|
||||
|
||||
const rows = React.useMemo(
|
||||
() =>
|
||||
data?.map(element => (
|
||||
<tr key={element.id}>
|
||||
<td>{element.id}</td>
|
||||
<td>
|
||||
<Flex align="center" justify="space-between">
|
||||
{element.name}
|
||||
<ActionIcon color="cyan" onClick={() => setCurrentFile(element)}>
|
||||
<VscEdit />
|
||||
</ActionIcon>
|
||||
</Flex>
|
||||
</td>
|
||||
<td>{dayjs(element.created_at).format("DD.MM.YYYY")}</td>
|
||||
<td>
|
||||
<Badge variant="light" color={colorByFormat[element.format]} size="sm">
|
||||
{element.format.toUpperCase()}
|
||||
</Badge>
|
||||
</td>
|
||||
<td>{element.views.toLocaleString("en-US")}</td>
|
||||
<td>
|
||||
<Flex gap="xs">
|
||||
<Link href={`?json=${element.id}`} prefetch={false}>
|
||||
<ActionIcon color="blue" onClick={onClose}>
|
||||
<MdFileOpen size="18" />
|
||||
</ActionIcon>
|
||||
</Link>
|
||||
<ActionIcon color="red" onClick={() => onDeleteClick(element)}>
|
||||
<FaTrash size="18" />
|
||||
</ActionIcon>
|
||||
<ActionIcon color="green" onClick={() => copyShareLink(element.id)}>
|
||||
<AiOutlineLink />
|
||||
</ActionIcon>
|
||||
</Flex>
|
||||
</td>
|
||||
</tr>
|
||||
)),
|
||||
[data, copyShareLink, onClose, onDeleteClick]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Saved On The Cloud"
|
||||
opened={opened}
|
||||
size="xl"
|
||||
onClose={onClose}
|
||||
centered
|
||||
hidden={!!currentFile}
|
||||
>
|
||||
<Paper>
|
||||
<ScrollArea h={360} offsetScrollbars>
|
||||
<Table fontSize="xs" verticalSpacing="xs" striped>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Create Date</th>
|
||||
<th>Format</th>
|
||||
<th>Views</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows}</tbody>
|
||||
</Table>
|
||||
</ScrollArea>
|
||||
</Paper>
|
||||
{data && (
|
||||
<Flex gap="md">
|
||||
<Paper my="lg" withBorder radius="md" p="xs" w="100%">
|
||||
<Group>
|
||||
<RingProgress
|
||||
size={60}
|
||||
roundCaps
|
||||
thickness={8}
|
||||
sections={[
|
||||
{
|
||||
value: (data.length * 100) / totalQuota,
|
||||
color: data.length > totalQuota / 1.5 ? "red" : "blue",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div>
|
||||
<Text color="dimmed" size="xs" transform="uppercase" weight={700}>
|
||||
Total Quota
|
||||
</Text>
|
||||
<Text weight={700} size="xl">
|
||||
{data.length} / {totalQuota}
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Paper>
|
||||
<Paper my="lg" withBorder radius="md" p="xs" w={250}>
|
||||
<UnstyledButton fw="bold" w="100%" h="100%" onClick={onCreate}>
|
||||
<Text fz="md" align="center" color="blue">
|
||||
<Flex align="center" justify="center">
|
||||
<VscAdd size="24" />
|
||||
Create New Document
|
||||
</Flex>
|
||||
</Text>
|
||||
</UnstyledButton>
|
||||
</Paper>
|
||||
</Flex>
|
||||
)}
|
||||
<Divider py="xs" />
|
||||
<Group position="right">
|
||||
<Text fz="xs">
|
||||
Cloud Save feature is for ease-of-access only and not recommended to store sensitive data,
|
||||
we don't guarantee protection of your data.
|
||||
</Text>
|
||||
</Group>
|
||||
<Text fz="xs">
|
||||
The Cloud Save feature is primarily designed for convenient access and is not advisable for
|
||||
storing sensitive data.
|
||||
</Text>
|
||||
<UpdateNameModal file={currentFile} onClose={() => setCurrentFile(null)} refetch={refetch} />
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
@ -13,7 +13,6 @@ import {
|
||||
Badge,
|
||||
} from "@mantine/core";
|
||||
import { BsCheck } from "react-icons/bs";
|
||||
import { paymentURL } from "src/constants/data";
|
||||
|
||||
export const PremiumModal: React.FC<ModalProps> = ({ opened, onClose }) => {
|
||||
return (
|
||||
@ -53,7 +52,7 @@ export const PremiumModal: React.FC<ModalProps> = ({ opened, onClose }) => {
|
||||
</Title>
|
||||
<Button
|
||||
component="a"
|
||||
href={paymentURL()}
|
||||
href="/pricing"
|
||||
variant="filled"
|
||||
color="teal"
|
||||
size="md"
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
Text,
|
||||
ModalProps,
|
||||
} from "@mantine/core";
|
||||
import { FiExternalLink } from "react-icons/fi";
|
||||
import { MdCheck, MdCopyAll } from "react-icons/md";
|
||||
|
||||
export const ShareModal: React.FC<ModalProps> = ({ opened, onClose }) => {
|
||||
@ -42,7 +43,14 @@ export const ShareModal: React.FC<ModalProps> = ({ opened, onClose }) => {
|
||||
<Text fz="sm" fw={700}>
|
||||
Embed into your website
|
||||
</Text>
|
||||
<Button component="a" color="green" target="_blank" href="/docs" fullWidth>
|
||||
<Button
|
||||
component="a"
|
||||
color="green"
|
||||
target="_blank"
|
||||
href="/docs"
|
||||
leftIcon={<FiExternalLink />}
|
||||
fullWidth
|
||||
>
|
||||
Learn How to Embed
|
||||
</Button>
|
||||
</Stack>
|
||||
|
@ -3,6 +3,7 @@ import Link from "next/link";
|
||||
import styled from "styled-components";
|
||||
import { Button } from "@mantine/core";
|
||||
import { FaStar } from "react-icons/fa";
|
||||
import useUser from "src/store/useUser";
|
||||
import { JSONCrackLogo } from "../JsonCrackLogo";
|
||||
|
||||
const StyledNavbarWrapper = styled.div`
|
||||
@ -43,6 +44,8 @@ const Right = styled.div`
|
||||
`;
|
||||
|
||||
export const Navbar = () => {
|
||||
const isAuthenticated = useUser(state => state.isAuthenticated);
|
||||
|
||||
return (
|
||||
<StyledNavbarWrapper>
|
||||
<StyledNavbar>
|
||||
@ -86,11 +89,13 @@ export const Navbar = () => {
|
||||
>
|
||||
Star us on GitHub
|
||||
</Button>
|
||||
<Link href="/sign-in" prefetch={false}>
|
||||
<Button variant="light" radius="md" className="hide-mobile">
|
||||
Sign In
|
||||
</Button>
|
||||
</Link>
|
||||
{!isAuthenticated && (
|
||||
<Link href="/sign-in" prefetch={false}>
|
||||
<Button variant="light" radius="md" className="hide-mobile">
|
||||
Sign In
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
<Link href="/editor" prefetch={false}>
|
||||
<Button color="pink" radius="md">
|
||||
Editor
|
||||
|
10
src/lib/api/supabase.ts
Normal file
10
src/lib/api/supabase.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { createPagesBrowserClient } from "@supabase/auth-helpers-nextjs";
|
||||
|
||||
// Create a single supabase client for interacting with your database
|
||||
const supabase = createPagesBrowserClient({
|
||||
supabaseUrl: "https://bxkgqurwqjmvrqekcbws.supabase.co",
|
||||
supabaseKey:
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJ4a2dxdXJ3cWptdnJxZWtjYndzIiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTA2NDU0MjUsImV4cCI6MjAwNjIyMTQyNX0.3nZ0yhuFjnI3yHbAL8S9UtK-Ny-6F5AylNHgo1tymTU",
|
||||
});
|
||||
|
||||
export { supabase };
|
@ -4,10 +4,13 @@ import dynamic from "next/dynamic";
|
||||
import { useRouter } from "next/router";
|
||||
import { StyleSheetManager, ThemeProvider } from "styled-components";
|
||||
import { MantineProvider, MantineThemeOverride } from "@mantine/core";
|
||||
import { SessionContextProvider, Session } from "@supabase/auth-helpers-react";
|
||||
import { pageview } from "react-ga";
|
||||
import { monaSans } from "src/constants/fonts";
|
||||
import GlobalStyle from "src/constants/globalStyle";
|
||||
import { lightTheme } from "src/constants/theme";
|
||||
import { supabase } from "src/lib/api/supabase";
|
||||
import useUser from "src/store/useUser";
|
||||
|
||||
const Toaster = dynamic(() => import("react-hot-toast").then(c => c.Toaster));
|
||||
const ExternalMode = dynamic(() => import("src/layout/ExternalMode"));
|
||||
@ -21,8 +24,20 @@ const mantineTheme: MantineThemeOverride = {
|
||||
primaryShade: 8,
|
||||
};
|
||||
|
||||
function JsonCrack({ Component, pageProps }: AppProps) {
|
||||
function JsonCrack({
|
||||
Component,
|
||||
pageProps,
|
||||
}: AppProps<{
|
||||
initialSession: Session;
|
||||
}>) {
|
||||
const router = useRouter();
|
||||
const setSession = useUser(state => state.setSession);
|
||||
|
||||
React.useEffect(() => {
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
if (session) setSession(session);
|
||||
});
|
||||
}, [setSession]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
@ -37,7 +52,7 @@ function JsonCrack({ Component, pageProps }: AppProps) {
|
||||
}, [router.events]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SessionContextProvider supabaseClient={supabase}>
|
||||
<GoogleAnalytics />
|
||||
<StyleSheetManager>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
@ -63,7 +78,7 @@ function JsonCrack({ Component, pageProps }: AppProps) {
|
||||
</MantineProvider>
|
||||
</ThemeProvider>
|
||||
</StyleSheetManager>
|
||||
</>
|
||||
</SessionContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -39,17 +39,17 @@ const StyledContentBody = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledHighlight = styled.span<{ link?: boolean; alert?: boolean }>`
|
||||
const StyledHighlight = styled.span<{ $link?: boolean; $alert?: boolean }>`
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
color: ${({ theme, link, alert }) =>
|
||||
alert ? theme.DANGER : link ? theme.BLURPLE : theme.TEXT_POSITIVE};
|
||||
color: ${({ theme, $link, $alert }) =>
|
||||
$alert ? theme.DANGER : $link ? theme.BLURPLE : theme.TEXT_POSITIVE};
|
||||
background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
padding: 2px 4px;
|
||||
font-size: 14px;
|
||||
margin: ${({ alert }) => (alert ? "8px 0" : "1px")};
|
||||
margin: ${({ $alert }) => ($alert ? "8px 0" : "1px")};
|
||||
`;
|
||||
|
||||
const Docs = () => {
|
||||
@ -74,7 +74,7 @@ const Docs = () => {
|
||||
<StyledHighlight
|
||||
as="a"
|
||||
href="https://jsoncrack.com/editor?json=https://catfact.ninja/fact"
|
||||
link
|
||||
$link
|
||||
>
|
||||
https://jsoncrack.com/editor?json=https://catfact.ninja/fact
|
||||
</StyledHighlight>
|
||||
@ -103,7 +103,6 @@ const Docs = () => {
|
||||
<StyledHighlight>?json=639b65c5a82efc29a24b2de2</StyledHighlight>
|
||||
</StyledDescription>
|
||||
<StyledFrame
|
||||
scrolling="no"
|
||||
title="Untitled"
|
||||
src="https://codepen.io/AykutSarac/embed/vYaORgM?default-tab=html%2Cresult"
|
||||
loading="lazy"
|
||||
@ -125,7 +124,7 @@ const Docs = () => {
|
||||
<StyledHighlight
|
||||
as="a"
|
||||
href="https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage"
|
||||
link
|
||||
$link
|
||||
>
|
||||
MessagePort
|
||||
</StyledHighlight>
|
||||
|
@ -9,7 +9,6 @@ import { EditorWrapper } from "src/layout/EditorWrapper";
|
||||
import { Loading } from "src/layout/Loading";
|
||||
import useFile from "src/store/useFile";
|
||||
import useJson from "src/store/useJson";
|
||||
import useUser from "src/store/useUser";
|
||||
|
||||
const Panes = dynamic(() => import("src/containers/Editor/Panes"));
|
||||
|
||||
@ -29,15 +28,13 @@ export const StyledEditorWrapper = styled.div`
|
||||
`;
|
||||
|
||||
const EditorPage: React.FC = () => {
|
||||
const { query } = useRouter();
|
||||
const checkSession = useUser(state => state.checkSession);
|
||||
const { query, isReady } = useRouter();
|
||||
const checkEditorSession = useFile(state => state.checkEditorSession);
|
||||
const loading = useJson(state => state.loading);
|
||||
|
||||
React.useEffect(() => {
|
||||
checkSession();
|
||||
checkEditorSession({ url: query?.url, json: query?.json });
|
||||
}, [checkEditorSession, checkSession, query]);
|
||||
if (isReady) checkEditorSession({ url: query?.url, json: query?.json });
|
||||
}, [checkEditorSession, isReady, query]);
|
||||
|
||||
if (loading) return <Loading message="Fetching JSON from cloud..." />;
|
||||
|
||||
|
@ -3,12 +3,12 @@ import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import styled from "styled-components";
|
||||
import { PaperProps, Center, Button, Group, Paper, Stack, TextInput, Text } from "@mantine/core";
|
||||
import { useSession } from "@supabase/auth-helpers-react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { Footer } from "src/layout/Footer";
|
||||
import { JSONCrackLogo } from "src/layout/JsonCrackLogo";
|
||||
import { Navbar } from "src/layout/Navbar";
|
||||
import { altogic } from "src/lib/api/altogic";
|
||||
import useUser from "src/store/useUser";
|
||||
import { supabase } from "src/lib/api/supabase";
|
||||
|
||||
const StyledPageWrapper = styled.div`
|
||||
height: calc(100vh - 27px);
|
||||
@ -29,12 +29,11 @@ export function AuthenticationForm(props: PaperProps) {
|
||||
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
altogic.auth
|
||||
.sendResetPwdEmail(email)
|
||||
.then(res => {
|
||||
if (res.errors) {
|
||||
toast.error(res.errors.items[0].message);
|
||||
} else setSuccess(true);
|
||||
supabase.auth
|
||||
.resetPasswordForEmail(email, { redirectTo: "/reset-password" })
|
||||
.then(({ error }) => {
|
||||
if (error) return toast.error(error.message);
|
||||
setSuccess(true);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
@ -79,14 +78,12 @@ const StyledHeroSection = styled.section`
|
||||
`;
|
||||
|
||||
const SignIn = () => {
|
||||
const { isReady, replace } = useRouter();
|
||||
const checkSession = useUser(state => state.checkSession);
|
||||
const isAuthenticated = useUser(state => state.isAuthenticated);
|
||||
const { isReady, push } = useRouter();
|
||||
const session = useSession();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isReady) checkSession();
|
||||
if (isAuthenticated) replace("/");
|
||||
}, [isReady, isAuthenticated, replace, checkSession]);
|
||||
if (session) push("/editor");
|
||||
}, [isReady, session, push]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -20,16 +20,18 @@ import {
|
||||
} from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { useToggle, upperFirst } from "@mantine/hooks";
|
||||
import { useSession } from "@supabase/auth-helpers-react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { AiOutlineGithub, AiOutlineGoogle } from "react-icons/ai";
|
||||
import { Footer } from "src/layout/Footer";
|
||||
import { JSONCrackLogo } from "src/layout/JsonCrackLogo";
|
||||
import { Navbar } from "src/layout/Navbar";
|
||||
import { altogic } from "src/lib/api/altogic";
|
||||
import { supabase } from "src/lib/api/supabase";
|
||||
import useUser from "src/store/useUser";
|
||||
|
||||
export function AuthenticationForm(props: PaperProps) {
|
||||
const login = useUser(state => state.login);
|
||||
const setSession = useUser(state => state.setSession);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [type, toggle] = useToggle<"login" | "register">(["login", "register"]);
|
||||
const [done, setDone] = React.useState(false);
|
||||
@ -59,29 +61,34 @@ export function AuthenticationForm(props: PaperProps) {
|
||||
|
||||
setLoading(true);
|
||||
if (type === "login") {
|
||||
altogic.auth
|
||||
.signInWithEmail(form.values.email, form.values.password)
|
||||
.then(({ user, errors }) => {
|
||||
if (errors?.items.length || !user) return toast.error("Incorrect email or password!");
|
||||
login(user as any);
|
||||
supabase.auth
|
||||
.signInWithPassword({
|
||||
email: form.values.email,
|
||||
password: form.values.password,
|
||||
})
|
||||
.then(({ data, error }) => {
|
||||
if (error) return toast.error(error.message);
|
||||
setSession(data.session);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
} else {
|
||||
altogic.auth
|
||||
.signUpWithEmail(form.values.email, form.values.password, form.values.name)
|
||||
.then(({ errors }) => {
|
||||
if (errors?.items.length) {
|
||||
return errors.items.forEach(e => toast.error(e.message));
|
||||
}
|
||||
supabase.auth
|
||||
.signInWithPassword({
|
||||
email: form.values.email,
|
||||
password: form.values.password,
|
||||
})
|
||||
.then(({ data, error }) => {
|
||||
if (error) return toast.error(error.message);
|
||||
toast.success("Registration successful!");
|
||||
setDone(true);
|
||||
setSession(data.session);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginClick = (provider: "github" | "google") => {
|
||||
altogic.auth.signInWithProvider(provider);
|
||||
supabase.auth.signInWithOAuth({ provider });
|
||||
};
|
||||
|
||||
if (done) {
|
||||
@ -225,6 +232,10 @@ function ResetPassword(props: PaperProps) {
|
||||
e.preventDefault();
|
||||
if (query?.access_token && typeof query?.access_token === "string") {
|
||||
setLoading(true);
|
||||
// TODO: solve here
|
||||
supabase.auth.updateUser({
|
||||
password,
|
||||
});
|
||||
altogic.auth
|
||||
.resetPwdWithToken(query?.access_token, password)
|
||||
.then(({ errors }) => {
|
||||
@ -275,14 +286,12 @@ function ResetPassword(props: PaperProps) {
|
||||
|
||||
const SignIn = () => {
|
||||
const { isReady, push, query } = useRouter();
|
||||
const isAuthenticated = useUser(state => state.isAuthenticated);
|
||||
const checkSession = useUser(state => state.checkSession);
|
||||
const session = useSession();
|
||||
const isPasswordReset = query?.action === "reset-pwd" && !query?.error;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isAuthenticated) push("/editor");
|
||||
else checkSession();
|
||||
}, [isReady, isAuthenticated, push, checkSession]);
|
||||
if (session) push("/editor");
|
||||
}, [isReady, session, push]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -1,42 +1,50 @@
|
||||
import dayjs from "dayjs";
|
||||
import { decompressFromBase64 } from "lz-string";
|
||||
import { altogic, AltogicResponse } from "src/lib/api/altogic";
|
||||
import { PostgrestSingleResponse } from "@supabase/supabase-js";
|
||||
import toast from "react-hot-toast";
|
||||
import { supabase } from "src/lib/api/supabase";
|
||||
import { File } from "src/store/useFile";
|
||||
import useUser from "src/store/useUser";
|
||||
import { FileFormat } from "src/types/models";
|
||||
|
||||
const saveToCloud = async (
|
||||
id: string | null,
|
||||
contents: string,
|
||||
format = FileFormat.JSON
|
||||
): Promise<AltogicResponse<{ _id: string }>> => {
|
||||
if (id) return await altogic.endpoint.put(`json/${id}`, { json: contents, format });
|
||||
return await altogic.endpoint.post("json", { json: contents, format });
|
||||
type CloudSave = {
|
||||
id?: string;
|
||||
contents: string;
|
||||
format: FileFormat;
|
||||
};
|
||||
|
||||
const getFromCloud = async (id: string) => {
|
||||
const { data, errors } = await altogic.endpoint.get(`json/${id}`, undefined, {
|
||||
userid: altogic.auth.getUser()?._id,
|
||||
});
|
||||
const saveToCloud = async ({
|
||||
id,
|
||||
contents,
|
||||
format = FileFormat.JSON,
|
||||
}: CloudSave): Promise<PostgrestSingleResponse<{ id: string }[]>> => {
|
||||
return await supabase.from("document").upsert({ id, content: contents, format }).select("id");
|
||||
};
|
||||
|
||||
if (errors) throw errors;
|
||||
const getFromCloud = async (doc_id: string): Promise<PostgrestSingleResponse<File[]>> => {
|
||||
return await supabase.rpc("get_document_by_id", { doc_id });
|
||||
};
|
||||
|
||||
const isCompressed = dayjs("2023-04-20T07:04:25.255Z").isAfter(data?.updatedAt);
|
||||
const getAllJson = async (): Promise<File[]> => {
|
||||
const userId = useUser.getState().user?.id;
|
||||
if (!userId) return [];
|
||||
|
||||
if (isCompressed) {
|
||||
return { ...data, json: decompressFromBase64(data.json) };
|
||||
const { data, error } = await supabase
|
||||
.from("document")
|
||||
.select()
|
||||
.eq("owner_id", userId)
|
||||
.order("created_at", { ascending: false });
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const getAllJson = async (): Promise<AltogicResponse<{ result: File[] }>> =>
|
||||
await altogic.endpoint.get("json");
|
||||
const updateJson = async (id: string, data: object) => {
|
||||
return await supabase.from("document").update(data).eq("id", id).select("private");
|
||||
};
|
||||
|
||||
const updateJson = async (id: string, data: object) =>
|
||||
await altogic.endpoint.put(`json/${id}`, {
|
||||
...data,
|
||||
});
|
||||
|
||||
const deleteJson = async (id: string) => await altogic.endpoint.delete(`json/${id}`);
|
||||
const deleteJson = async (id: string) => await supabase.from("document").delete().eq("id", id);
|
||||
|
||||
export { saveToCloud, getFromCloud, getAllJson, updateJson, deleteJson };
|
||||
|
@ -7,7 +7,7 @@ import { create } from "zustand";
|
||||
import { defaultJson } from "src/constants/data";
|
||||
import { contentToJson, jsonToContent } from "src/lib/utils/json/jsonAdapter";
|
||||
import { isIframe } from "src/lib/utils/widget";
|
||||
import { getFromCloud, saveToCloud } from "src/services/json";
|
||||
import { getFromCloud } from "src/services/json";
|
||||
import { FileFormat } from "src/types/models";
|
||||
import useGraph from "./useGraph";
|
||||
import useJson from "./useJson";
|
||||
@ -29,7 +29,6 @@ interface JsonActions {
|
||||
setError: (error: object | null) => void;
|
||||
setHasChanges: (hasChanges: boolean) => void;
|
||||
setContents: (data: SetContents) => void;
|
||||
saveToCloud: (isNew?: boolean) => void;
|
||||
fetchFile: (fileId: string) => void;
|
||||
fetchUrl: (url: string) => void;
|
||||
editContents: (path: string, value: string, callback?: () => void) => void;
|
||||
@ -41,13 +40,15 @@ interface JsonActions {
|
||||
}
|
||||
|
||||
export type File = {
|
||||
_id: string;
|
||||
id: string;
|
||||
views: number;
|
||||
owner_id: string;
|
||||
name: string;
|
||||
json: string;
|
||||
content: string;
|
||||
private: boolean;
|
||||
format?: FileFormat;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
format: FileFormat;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
const initialStates = {
|
||||
@ -96,7 +97,7 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
|
||||
},
|
||||
setFile: fileData => {
|
||||
set({ fileData, format: fileData.format || FileFormat.JSON });
|
||||
get().setContents({ contents: fileData.json, hasChanges: false });
|
||||
get().setContents({ contents: fileData.content, hasChanges: false });
|
||||
},
|
||||
getContents: () => get().contents,
|
||||
getFormat: () => get().format,
|
||||
@ -109,7 +110,7 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
|
||||
const contentJson = await contentToJson(get().contents, prevFormat);
|
||||
const jsonContent = await jsonToContent(JSON.stringify(contentJson, null, 2), format);
|
||||
|
||||
get().setContents({ contents: jsonContent, hasChanges: false });
|
||||
get().setContents({ contents: jsonContent });
|
||||
|
||||
event({ action: "change_data_format", category: "User" });
|
||||
} catch (error) {
|
||||
@ -121,11 +122,12 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
|
||||
try {
|
||||
set({ ...(contents && { contents }), error: null, hasChanges });
|
||||
|
||||
const isFetchURL = window.location.href.includes("?");
|
||||
const json = await contentToJson(get().contents, get().format);
|
||||
|
||||
if (!useStored.getState().liveTransform && skipUpdate) return;
|
||||
|
||||
if (contents && contents.length < 80_000 && !isIframe()) {
|
||||
if (contents && contents.length < 80_000 && !isIframe() && !isFetchURL) {
|
||||
sessionStorage.setItem("content", contents);
|
||||
sessionStorage.setItem("format", get().format);
|
||||
}
|
||||
@ -140,30 +142,6 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
|
||||
},
|
||||
setError: error => set({ error }),
|
||||
setHasChanges: hasChanges => set({ hasChanges }),
|
||||
saveToCloud: async (isNew = true) => {
|
||||
try {
|
||||
const url = new URL(window.location.href);
|
||||
const params = new URLSearchParams(url.search);
|
||||
const jsonQuery = params.get("doc");
|
||||
|
||||
toast.loading("Saving File...", { id: "fileSave" });
|
||||
const res = await saveToCloud(isNew ? null : jsonQuery, get().contents, get().format);
|
||||
|
||||
if (res.errors && res.errors.items.length > 0) throw res.errors;
|
||||
|
||||
toast.success("File saved to cloud", { id: "fileSave" });
|
||||
set({ hasChanges: false });
|
||||
return res.data._id;
|
||||
} catch (error: any) {
|
||||
if (error?.items?.length > 0) {
|
||||
toast.error(error.items[0].message, { id: "fileSave", duration: 5000 });
|
||||
return undefined;
|
||||
}
|
||||
|
||||
toast.error("Failed to save File!", { id: "fileSave" });
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
fetchUrl: async url => {
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
@ -178,7 +156,7 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
|
||||
}
|
||||
},
|
||||
checkEditorSession: ({ url, json }) => {
|
||||
if (typeof url === "string") return get().fetchUrl(url);
|
||||
if (typeof url === "string" && isURL(url)) return get().fetchUrl(url);
|
||||
if (typeof json === "string") return get().fetchFile(json);
|
||||
|
||||
const sessionContent = sessionStorage.getItem("content") as string | null;
|
||||
@ -190,15 +168,14 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
|
||||
},
|
||||
fetchFile: async id => {
|
||||
try {
|
||||
if (isURL(id)) return get().fetchUrl(id);
|
||||
const { data, error } = await getFromCloud(id);
|
||||
if (error) throw error;
|
||||
|
||||
const file = await getFromCloud(id);
|
||||
|
||||
get().setFile(file);
|
||||
} catch (error) {
|
||||
useJson.setState({ loading: false });
|
||||
useGraph.setState({ loading: false });
|
||||
console.error(error);
|
||||
if (data?.length) get().setFile(data[0]);
|
||||
if (data?.length === 0) throw new Error("Document not found");
|
||||
} catch (error: any) {
|
||||
if (error?.message) toast.error(error?.message);
|
||||
get().setContents({ contents: defaultJson, hasChanges: false });
|
||||
}
|
||||
},
|
||||
editContents: async (path, value, callback) => {
|
||||
|
@ -27,7 +27,7 @@ const initialStates: ModalState = {
|
||||
cancelPremium: false,
|
||||
};
|
||||
|
||||
const authModals: Modal[] = ["cloud", "share", "account", "schema"];
|
||||
const authModals: Modal[] = ["cloud", "account", "schema"];
|
||||
const premiumModals: Modal[] = ["schema"];
|
||||
|
||||
const useModal = create<ModalState & ModalActions>()(set => ({
|
||||
|
@ -1,31 +1,25 @@
|
||||
import { setUser } from "@sentry/nextjs";
|
||||
import { set as gaSet } from "react-ga";
|
||||
import { Session, User } from "@supabase/supabase-js";
|
||||
import toast from "react-hot-toast";
|
||||
import { create } from "zustand";
|
||||
import { altogic } from "src/lib/api/altogic";
|
||||
import { AltogicAuth, User } from "src/types/altogic";
|
||||
import useModal from "./useModal";
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV === "development";
|
||||
import { supabase } from "src/lib/api/supabase";
|
||||
|
||||
interface UserActions {
|
||||
login: (response: User | AltogicAuth) => void;
|
||||
logout: () => void;
|
||||
setUser: (key: keyof typeof initialStates, value: any) => void;
|
||||
checkSession: () => void;
|
||||
setSession: (session: Session) => void;
|
||||
}
|
||||
|
||||
const devUser = {
|
||||
_id: "0",
|
||||
provider: "google",
|
||||
providerUserId: "115637229829349229857",
|
||||
email: "test@jsoncrack.com",
|
||||
name: "JSON Crack",
|
||||
profilePicture: "",
|
||||
signUpAt: "2022-12-04T11:07:32.000Z",
|
||||
lastLoginAt: "2023-05-13T09:56:02.915Z",
|
||||
updatedAt: "2023-05-06T16:19:47.486Z",
|
||||
} as User;
|
||||
interface Premium {
|
||||
id: 101;
|
||||
created_at: "2023-07-29T17:33:10.403228+00:00";
|
||||
user: null;
|
||||
status: "active";
|
||||
ends_at: null;
|
||||
subscription_id: null;
|
||||
variant_id: null;
|
||||
renews_at: null;
|
||||
cancelled: null;
|
||||
email: "aykutsarac0@gmail.com";
|
||||
}
|
||||
|
||||
interface UserStates {
|
||||
user: User | null;
|
||||
@ -43,68 +37,24 @@ const initialStates: UserStates = {
|
||||
|
||||
const useUser = create<UserStates & UserActions>()(set => ({
|
||||
...initialStates,
|
||||
setUser: (key, value) => set({ [key]: value }),
|
||||
setSession: async session => {
|
||||
const {
|
||||
data: { user },
|
||||
error,
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (error) return toast.error(error.message);
|
||||
|
||||
supabase.rpc("get_subscription", { email_arg: session.user.email }).then(({ data }) => {
|
||||
if (data) set({ premium: data[0].status === "active" });
|
||||
set({ user, isAuthenticated: true });
|
||||
});
|
||||
},
|
||||
logout: async () => {
|
||||
const session = altogic.auth.getSession();
|
||||
|
||||
if (!session) return;
|
||||
|
||||
const { errors } = await altogic.auth.signOut(session.token);
|
||||
|
||||
if (errors?.items) return console.error(errors);
|
||||
|
||||
toast.loading("Logging out...", { id: "logout" });
|
||||
await supabase.auth.signOut();
|
||||
set(initialStates);
|
||||
altogic.auth.clearLocalData();
|
||||
toast.success("Logged out.");
|
||||
useModal.setState({ account: false });
|
||||
},
|
||||
login: user => {
|
||||
set({ user: user as unknown as User, isAuthenticated: true });
|
||||
gaSet({ userId: (user as User)._id });
|
||||
},
|
||||
checkSession: async () => {
|
||||
if (isDevelopment) {
|
||||
return set({ user: devUser as User, isAuthenticated: true, premium: true });
|
||||
}
|
||||
|
||||
const currentSession = altogic.auth.getSession();
|
||||
|
||||
if (currentSession) {
|
||||
const { user, errors } = await altogic.auth.getUserFromDB();
|
||||
|
||||
if (errors?.items || !user) {
|
||||
altogic.auth.clearLocalData();
|
||||
return;
|
||||
}
|
||||
|
||||
altogic.auth.setUser(user);
|
||||
altogic.auth.setSession(currentSession);
|
||||
const { data: premiumData } = await altogic.endpoint.get("/isPremium");
|
||||
|
||||
setUser({ id: user._id, email: user.email, username: user.name });
|
||||
set({
|
||||
user: user as User,
|
||||
isAuthenticated: true,
|
||||
premium: premiumData.premium,
|
||||
premiumCancelled: premiumData?.status === "cancelled" || false,
|
||||
});
|
||||
gaSet({ userId: user._id });
|
||||
} else if (new URLSearchParams(window.location.search).get("access_token")) {
|
||||
const { errors, user } = await altogic.auth.getAuthGrant();
|
||||
|
||||
if (errors?.items) {
|
||||
toast.error(errors.items[0].message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (user) {
|
||||
const { data: premiumData } = await altogic.endpoint.get("/isPremium");
|
||||
|
||||
setUser({ id: user._id, email: user.email, username: user.name });
|
||||
set({ user: user as User, isAuthenticated: true, premium: premiumData.premium });
|
||||
gaSet({ userId: user._id });
|
||||
}
|
||||
}
|
||||
toast.success("Logged out.", { id: "logout" });
|
||||
},
|
||||
}));
|
||||
|
||||
|
221
yarn.lock
221
yarn.lock
@ -1955,6 +1955,75 @@
|
||||
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
|
||||
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
|
||||
|
||||
"@supabase/auth-helpers-nextjs@^0.7.3":
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.7.3.tgz#516ddda811c5f81328e50fb635fb11a437930d61"
|
||||
integrity sha512-ikuBGHFnvyfneToj0Y2EfiqUnEr1bdqR6JZiFydPnzACQ2Q7TkcOhkoUSc9/sMEeG7EKX5PhH8riAz42oPdKRg==
|
||||
dependencies:
|
||||
"@supabase/auth-helpers-shared" "0.4.1"
|
||||
set-cookie-parser "^2.6.0"
|
||||
|
||||
"@supabase/auth-helpers-react@^0.4.1":
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@supabase/auth-helpers-react/-/auth-helpers-react-0.4.1.tgz#9198cfe1ceae9935d04b2fdec84a7d806a8c711e"
|
||||
integrity sha512-Y9ZWzhpsOX93rokmywDdclzTDPbHjybU7o4/FmXlAqta75msFxHV9WVTR6//SZf2Bca2Cf65Oa/j+fYtMWraDQ==
|
||||
|
||||
"@supabase/auth-helpers-shared@0.4.1":
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.4.1.tgz#46f53f7ac1e80f6bcb69b5bc95e15b237a80d819"
|
||||
integrity sha512-IEDX9JzWkIjQiLUaP4Qy5YDiG0jFQatWfS+jw8cCQs6QfbNdEPd2Y3qonwGHnM90CZom9SvjuylBv2pFVAL7Lw==
|
||||
dependencies:
|
||||
jose "^4.14.3"
|
||||
|
||||
"@supabase/functions-js@^2.1.0":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@supabase/functions-js/-/functions-js-2.1.2.tgz#340a8d3845ef2014338b13a6d33cfa90eb745b14"
|
||||
integrity sha512-QCR6pwJs9exCl37bmpMisUd6mf+0SUBJ6mUpiAjEkSJ/+xW8TCuO14bvkWHADd5hElJK9MxNlMQXxSA4DRz9nQ==
|
||||
dependencies:
|
||||
cross-fetch "^3.1.5"
|
||||
|
||||
"@supabase/gotrue-js@^2.46.1":
|
||||
version "2.46.1"
|
||||
resolved "https://registry.yarnpkg.com/@supabase/gotrue-js/-/gotrue-js-2.46.1.tgz#c4ac5056a02498db6d9edccab4e4d6db4fe7e3e1"
|
||||
integrity sha512-tebFX3XvPqEJKHOVgkXTN20g9iUhLx6tebIYQvTggYTrqOT2af8oTpSBdgYzbwJ291G6P6CSpR6KY0cT9ade5A==
|
||||
dependencies:
|
||||
cross-fetch "^3.1.5"
|
||||
|
||||
"@supabase/postgrest-js@^1.7.0":
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@supabase/postgrest-js/-/postgrest-js-1.7.2.tgz#800814b98bb8cec840b65f0c545a3474f23c343a"
|
||||
integrity sha512-GK80JpRq8l6Qll85erICypAfQCied8tdlXfsDN14W844HqXCSOisk8AaE01DAwGJanieaoN5fuqhzA2yKxDvEQ==
|
||||
dependencies:
|
||||
cross-fetch "^3.1.5"
|
||||
|
||||
"@supabase/realtime-js@^2.7.3":
|
||||
version "2.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@supabase/realtime-js/-/realtime-js-2.7.3.tgz#cbcb84181add681ab99c87032bfe88101c6863b3"
|
||||
integrity sha512-c7TzL81sx2kqyxsxcDduJcHL9KJdCOoKimGP6lQSqiZKX42ATlBZpWbyy9KFGFBjAP4nyopMf5JhPi2ZH9jyNw==
|
||||
dependencies:
|
||||
"@types/phoenix" "^1.5.4"
|
||||
"@types/websocket" "^1.0.3"
|
||||
websocket "^1.0.34"
|
||||
|
||||
"@supabase/storage-js@^2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@supabase/storage-js/-/storage-js-2.5.1.tgz#16c4c088996e0395034717836e626f14df63a349"
|
||||
integrity sha512-nkR0fQA9ScAtIKA3vNoPEqbZv1k5B5HVRYEvRWdlP6mUpFphM9TwPL2jZ/ztNGMTG5xT6SrHr+H7Ykz8qzbhjw==
|
||||
dependencies:
|
||||
cross-fetch "^3.1.5"
|
||||
|
||||
"@supabase/supabase-js@^2.31.0":
|
||||
version "2.31.0"
|
||||
resolved "https://registry.yarnpkg.com/@supabase/supabase-js/-/supabase-js-2.31.0.tgz#49fe09652fa72b1173efe4ac086592e31c662cef"
|
||||
integrity sha512-W9/4s+KnSUX67wJKBn/3yLq+ieycnMzVjK3nNTLX5Wko3ypNT/081l2iFYrf+nsLQ1CiT4mA92I3dxCy6CmxTg==
|
||||
dependencies:
|
||||
"@supabase/functions-js" "^2.1.0"
|
||||
"@supabase/gotrue-js" "^2.46.1"
|
||||
"@supabase/postgrest-js" "^1.7.0"
|
||||
"@supabase/realtime-js" "^2.7.3"
|
||||
"@supabase/storage-js" "^2.5.1"
|
||||
cross-fetch "^3.1.5"
|
||||
|
||||
"@swc/helpers@0.4.14":
|
||||
version "0.4.14"
|
||||
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.14.tgz#1352ac6d95e3617ccb7c1498ff019654f1e12a74"
|
||||
@ -2112,6 +2181,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
|
||||
|
||||
"@types/phoenix@^1.5.4":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/phoenix/-/phoenix-1.6.0.tgz#eb7536259ee695646e75c4c7b0c9a857ea174781"
|
||||
integrity sha512-qwfpsHmFuhAS/dVd4uBIraMxRd56vwBUYQGZ6GpXnFuM2XMRFJbIyruFKKlW2daQliuYZwe0qfn/UjFCDKic5g==
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
||||
@ -2179,6 +2253,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
||||
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
||||
|
||||
"@types/websocket@^1.0.3":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.5.tgz#3fb80ed8e07f88e51961211cd3682a3a4a81569c"
|
||||
integrity sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@typescript-eslint/parser@^5.42.0":
|
||||
version "5.54.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.54.1.tgz#05761d7f777ef1c37c971d3af6631715099b084c"
|
||||
@ -2509,6 +2590,13 @@ buffer-from@~0.1.1:
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-0.1.2.tgz#15f4b9bcef012044df31142c14333caf6e0260d0"
|
||||
integrity sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==
|
||||
|
||||
bufferutil@^4.0.1:
|
||||
version "4.0.7"
|
||||
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad"
|
||||
integrity sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==
|
||||
dependencies:
|
||||
node-gyp-build "^4.3.0"
|
||||
|
||||
busboy@1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
|
||||
@ -2720,6 +2808,13 @@ cross-fetch@^3.1.4:
|
||||
dependencies:
|
||||
node-fetch "2.6.7"
|
||||
|
||||
cross-fetch@^3.1.5:
|
||||
version "3.1.8"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82"
|
||||
integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==
|
||||
dependencies:
|
||||
node-fetch "^2.6.12"
|
||||
|
||||
cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
@ -2770,6 +2865,14 @@ d3-shape@^3.0.1:
|
||||
dependencies:
|
||||
d3-path "^3.1.0"
|
||||
|
||||
d@1, d@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
|
||||
integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
|
||||
dependencies:
|
||||
es5-ext "^0.10.50"
|
||||
type "^1.0.1"
|
||||
|
||||
damerau-levenshtein@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
|
||||
@ -2787,6 +2890,13 @@ debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, d
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^2.2.0:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.2.7:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
||||
@ -3113,6 +3223,32 @@ es-to-primitive@^1.2.1:
|
||||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.2"
|
||||
|
||||
es5-ext@^0.10.35, es5-ext@^0.10.50:
|
||||
version "0.10.62"
|
||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5"
|
||||
integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==
|
||||
dependencies:
|
||||
es6-iterator "^2.0.3"
|
||||
es6-symbol "^3.1.3"
|
||||
next-tick "^1.1.0"
|
||||
|
||||
es6-iterator@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
|
||||
integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==
|
||||
dependencies:
|
||||
d "1"
|
||||
es5-ext "^0.10.35"
|
||||
es6-symbol "^3.1.1"
|
||||
|
||||
es6-symbol@^3.1.1, es6-symbol@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
|
||||
integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
|
||||
dependencies:
|
||||
d "^1.0.1"
|
||||
ext "^1.1.2"
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
@ -3370,6 +3506,13 @@ eventemitter3@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.0.tgz#084eb7f5b5388df1451e63f4c2aafd71b217ccb3"
|
||||
integrity sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg==
|
||||
|
||||
ext@^1.1.2:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f"
|
||||
integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==
|
||||
dependencies:
|
||||
type "^2.7.2"
|
||||
|
||||
fast-deep-equal@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
|
||||
@ -4075,6 +4218,11 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.9:
|
||||
gopd "^1.0.1"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-typedarray@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||
integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
|
||||
|
||||
is-weakmap@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2"
|
||||
@ -4127,6 +4275,11 @@ javascript-natural-sort@0.7.1:
|
||||
resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59"
|
||||
integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==
|
||||
|
||||
jose@^4.14.3:
|
||||
version "4.14.4"
|
||||
resolved "https://registry.yarnpkg.com/jose/-/jose-4.14.4.tgz#59e09204e2670c3164ee24cbfe7115c6f8bff9ca"
|
||||
integrity sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
@ -4471,6 +4624,11 @@ mrmime@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27"
|
||||
integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
@ -4504,6 +4662,11 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
next-tick@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
|
||||
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
|
||||
|
||||
next@13.3.0:
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/next/-/next-13.3.0.tgz#40632d303d74fc8521faa0a5bf4a033a392749b1"
|
||||
@ -4533,6 +4696,13 @@ node-fetch@2.6.7:
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-fetch@^2.6.12:
|
||||
version "2.6.12"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba"
|
||||
integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-fetch@^2.6.7:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6"
|
||||
@ -4540,6 +4710,11 @@ node-fetch@^2.6.7:
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-gyp-build@^4.3.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055"
|
||||
integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==
|
||||
|
||||
node-releases@^2.0.12:
|
||||
version "2.0.13"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"
|
||||
@ -5248,6 +5423,11 @@ semver@^7.3.8:
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
set-cookie-parser@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz#131921e50f62ff1a66a461d7d62d7b21d5d15a51"
|
||||
integrity sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==
|
||||
|
||||
shallowequal@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
|
||||
@ -5670,6 +5850,16 @@ type-fest@^0.7.1:
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
|
||||
integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==
|
||||
|
||||
type@^1.0.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
|
||||
integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
|
||||
|
||||
type@^2.7.2:
|
||||
version "2.7.2"
|
||||
resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0"
|
||||
integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==
|
||||
|
||||
typed-array-length@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb"
|
||||
@ -5679,6 +5869,13 @@ typed-array-length@^1.0.4:
|
||||
for-each "^0.3.3"
|
||||
is-typed-array "^1.1.9"
|
||||
|
||||
typedarray-to-buffer@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
|
||||
integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
|
||||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
typescript@5.1.6:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"
|
||||
@ -5792,6 +5989,13 @@ use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||
|
||||
utf-8-validate@^5.0.2:
|
||||
version "5.0.10"
|
||||
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2"
|
||||
integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==
|
||||
dependencies:
|
||||
node-gyp-build "^4.3.0"
|
||||
|
||||
util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
@ -5827,6 +6031,18 @@ webpack-bundle-analyzer@4.7.0:
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
|
||||
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
|
||||
|
||||
websocket@^1.0.34:
|
||||
version "1.0.34"
|
||||
resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111"
|
||||
integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==
|
||||
dependencies:
|
||||
bufferutil "^4.0.1"
|
||||
debug "^2.2.0"
|
||||
es5-ext "^0.10.50"
|
||||
typedarray-to-buffer "^3.1.5"
|
||||
utf-8-validate "^5.0.2"
|
||||
yaeti "^0.0.6"
|
||||
|
||||
whatwg-url@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
||||
@ -5912,6 +6128,11 @@ xtend@~2.1.1:
|
||||
dependencies:
|
||||
object-keys "~0.4.0"
|
||||
|
||||
yaeti@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577"
|
||||
integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==
|
||||
|
||||
yallist@^3.0.2:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||
|
Loading…
x
Reference in New Issue
Block a user