feat: setup supabase

This commit is contained in:
AykutSarac 2023-07-31 21:38:33 +03:00
parent 23b3eefad2
commit ef82c68eeb
No known key found for this signature in database
19 changed files with 662 additions and 463 deletions

View File

@ -11,11 +11,6 @@
"linebreak-style": ["error", "unix"], "linebreak-style": ["error", "unix"],
"quotes": ["error", "double", { "avoidEscape": true }], "quotes": ["error", "double", { "avoidEscape": true }],
"semi": ["error", "always"], "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": [ "space-before-function-paren": [
"error", "error",
{ {

View File

@ -22,6 +22,9 @@
"@mantine/prism": "^6.0.17", "@mantine/prism": "^6.0.17",
"@monaco-editor/react": "^4.5.1", "@monaco-editor/react": "^4.5.1",
"@sentry/nextjs": "^7.60.0", "@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", "@tanstack/react-query": "^4.32.0",
"allotment": "^1.19.2", "allotment": "^1.19.2",
"altogic": "^2.3.9", "altogic": "^2.3.9",

View File

@ -30,6 +30,7 @@ const StyledBottomBar = styled.div`
max-height: 27px; max-height: 27px;
height: 27px; height: 27px;
padding: 0 6px; padding: 0 6px;
z-index: 35;
@media screen and (max-width: 320px) { @media screen and (max-width: 320px) {
display: none; display: none;
@ -73,7 +74,7 @@ const StyledBottomBarItem = styled.button<{ $error?: boolean }>`
} }
&:disabled { &:disabled {
opacity: 0.4; opacity: 0.6;
cursor: default; cursor: default;
} }
`; `;
@ -116,19 +117,20 @@ export const BottomBar = () => {
try { try {
setIsUpdating(true); setIsUpdating(true);
toast.loading("Saving document...", { id: "fileSave" }); 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; const { data, error } = await saveToCloud({
if (res.data._id) replace({ query: { json: res.data._id } }); 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" }); toast.success("Document saved to cloud", { id: "fileSave" });
setHasChanges(false); setHasChanges(false);
} catch (error: any) { } catch (error: any) {
if (error?.items?.length > 0) { toast.error(error.message, { id: "fileSave" });
return toast.error(error.items[0].message, { id: "fileSave", duration: 5000 });
}
toast.error("Failed to save document!", { id: "fileSave" });
} finally { } finally {
setIsUpdating(false); setIsUpdating(false);
} }
@ -155,14 +157,18 @@ export const BottomBar = () => {
if (!query.json) return handleSaveJson(); if (!query.json) return handleSaveJson();
setIsUpdating(true); 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) { if (error) return toast.error(error.message);
setIsPrivate(res.data.private);
if (updatedJsonData[0]) {
setIsPrivate(updatedJsonData[0].private);
toast.success(`Document set to ${isPrivate ? "public" : "private"}.`); toast.success(`Document set to ${isPrivate ? "public" : "private"}.`);
} else throw res.errors; } else throw error;
} catch (error) { } catch (error) {
toast.error("An error occurred while updating document!"); console.error(error);
} finally { } finally {
setIsUpdating(false); setIsUpdating(false);
} }
@ -178,7 +184,7 @@ export const BottomBar = () => {
<StyledLeft> <StyledLeft>
<StyledBottomBarItem onClick={handleLoginClick}> <StyledBottomBarItem onClick={handleLoginClick}>
<VscAccount /> <VscAccount />
{user ? user.name : "Login"} {user?.user_metadata.name ?? "Login"}
{premium && ( {premium && (
<Badge size="sm" color="orange" radius="sm" fw="bold"> <Badge size="sm" color="orange" radius="sm" fw="bold">
PREMIUM PREMIUM
@ -211,24 +217,25 @@ export const BottomBar = () => {
</Flex> </Flex>
)} )}
</StyledBottomBarItem> </StyledBottomBarItem>
<StyledBottomBarItem onClick={handleSaveJson} disabled={isUpdating}> {(data?.owner_id === user?.id || (!data && user)) && (
{hasChanges ? <AiOutlineCloudUpload /> : <AiOutlineCloudSync />} <StyledBottomBarItem onClick={handleSaveJson} disabled={isUpdating}>
{hasChanges ? (query?.json ? "Unsaved Changes" : "Create Document") : "Saved"} {hasChanges ? <AiOutlineCloudUpload /> : <AiOutlineCloudSync />}
</StyledBottomBarItem> {hasChanges ? (query?.json ? "Unsaved Changes" : "Create Document") : "Saved"}
{data && ( </StyledBottomBarItem>
<>
{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 && (
<StyledBottomBarItem onClick={setPrivate} disabled={isUpdating}>
{isPrivate ? <AiOutlineLock /> : <AiOutlineUnlock />}
{isPrivate ? "Private" : "Public"}
</StyledBottomBarItem>
)}
<StyledBottomBarItem
onClick={() => setVisible("share")(true)}
disabled={isPrivate || !data}
>
<AiOutlineLink />
Share
</StyledBottomBarItem>
{liveTransform ? ( {liveTransform ? (
<StyledBottomBarItem onClick={() => toggleLiveTransform(false)}> <StyledBottomBarItem onClick={() => toggleLiveTransform(false)}>
<VscSync /> <VscSync />

View File

@ -1,6 +1,8 @@
import React from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { Modal, Group, Button, Badge, Avatar, Grid, Divider, ModalProps } from "@mantine/core"; 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 { IoRocketSharp } from "react-icons/io5";
import useModal from "src/store/useModal"; import useModal from "src/store/useModal";
import useUser from "src/store/useUser"; import useUser from "src/store/useUser";
@ -43,7 +45,7 @@ const StyledContainer = styled.div`
`; `;
export const AccountModal: React.FC<ModalProps> = ({ opened, onClose }) => { export const AccountModal: React.FC<ModalProps> = ({ opened, onClose }) => {
const user = useUser(state => state.user); const user = useSupaUser();
const isPremium = useUser(state => state.premium); const isPremium = useUser(state => state.premium);
const premiumCancelled = useUser(state => state.premiumCancelled); const premiumCancelled = useUser(state => state.premiumCancelled);
const setVisible = useModal(state => state.setVisible); const setVisible = useModal(state => state.setVisible);
@ -51,16 +53,21 @@ export const AccountModal: React.FC<ModalProps> = ({ opened, onClose }) => {
return ( return (
<Modal title="Account" opened={opened} onClose={onClose} centered> <Modal title="Account" opened={opened} onClose={onClose} centered>
<StyledTitle>Hello, {user?.name}!</StyledTitle> <StyledTitle>Hello, {user?.user_metadata.name}!</StyledTitle>
<Group py="sm"> <Group py="sm">
<Grid gutter="xs"> <Grid gutter="xs">
<Grid.Col span={2}> <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>
<Grid.Col span={4}> <Grid.Col span={4}>
<StyledContainer> <StyledContainer>
USERNAME USERNAME
<div>{user?.name}</div> <div>{user?.user_metadata.name}</div>
</StyledContainer> </StyledContainer>
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
@ -82,15 +89,17 @@ export const AccountModal: React.FC<ModalProps> = ({ opened, onClose }) => {
<Grid.Col span={6}> <Grid.Col span={6}>
<StyledContainer> <StyledContainer>
EMAIL EMAIL
<div>{user?.email}</div> <div>{user?.user_metadata.email}</div>
</StyledContainer>
</Grid.Col>
<Grid.Col span={4}>
<StyledContainer>
REGISTRATION
<div>{user?.signUpAt && new Date(user.signUpAt).toDateString()}</div>
</StyledContainer> </StyledContainer>
</Grid.Col> </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> </Grid>
</Group> </Group>
<Divider py="xs" /> <Divider py="xs" />

View File

@ -1,252 +1,242 @@
import React from "react"; import React from "react";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import styled from "styled-components";
import { import {
Modal, Modal,
Group,
Button,
Text, Text,
Stack,
Loader,
Center,
Divider, Divider,
ScrollArea, ScrollArea,
ModalProps, ModalProps,
Table,
ActionIcon,
Badge,
Paper, Paper,
Flex,
DefaultMantineColor,
Input,
Button,
Group,
Stack,
RingProgress,
UnstyledButton,
} from "@mantine/core"; } from "@mantine/core";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import dayjs from "dayjs"; import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime"; import relativeTime from "dayjs/plugin/relativeTime";
import toast from "react-hot-toast"; 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 { 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 { deleteJson, getAllJson, saveToCloud, updateJson } from "src/services/json";
import useFile, { File } from "src/store/useFile"; import useFile, { File } from "src/store/useFile";
import useModal from "src/store/useModal";
import useUser from "src/store/useUser"; import useUser from "src/store/useUser";
import { FileFormat } from "src/types/models";
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
const StyledJsonCard = styled.a<{ active?: boolean; create?: boolean }>` const colorByFormat: Record<FileFormat, DefaultMantineColor> = {
display: ${({ create }) => (create ? "block" : "flex")}; json: "orange",
align-items: center; yaml: "blue",
justify-content: space-between; xml: "red",
line-height: 20px; toml: "dark",
padding: 6px; csv: "grape",
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 UpdateNameModal: React.FC<{
file: File | null;
onClose: () => void;
refetch: () => void;
}> = ({ file, onClose, refetch }) => {
const [name, setName] = React.useState("");
const onSubmit = () => { const onSubmit = () => {
if (!file) return;
toast toast
.promise(updateJson(data._id, { name }), { .promise(updateJson(file.id, { name }), {
loading: "Updating document...", loading: "Updating document...",
error: "Error occurred while updating document!", error: "Error occurred while updating document!",
success: `Renamed document to ${name}`, success: `Renamed document to ${name}`,
}) })
.then(refetch); .then(() => refetch());
setEditMode(false); onClose();
};
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);
}; };
return ( return (
<Paper withBorder w="100%"> <Modal title="Update Document name" opened={!!file} onClose={onClose} centered>
<StyledJsonCard <Stack>
href={`?json=${data._id}`} <Input
as={editMode ? "div" : "a"} value={name}
active={active} placeholder={file?.name}
{...props} onChange={e => setName(e.currentTarget.value)}
> />
<StyledInfo> <Group position="right">
{editMode ? ( <Button variant="outline" onClick={onClose}>
<form onSubmit={onSubmit}> Cancel
<StyledNameInput </Button>
value={name} <Button onClick={onSubmit}>Update</Button>
onChange={e => setName(e.currentTarget.value)} </Group>
onClick={e => e.preventDefault()} </Stack>
autoFocus </Modal>
/>
<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>
); );
}; };
export const CloudModal: React.FC<ModalProps> = ({ opened, onClose }) => { export const CloudModal: React.FC<ModalProps> = ({ opened, onClose }) => {
const { isReady, query } = useRouter(); const totalQuota = useUser(state => (state.premium ? 200 : 25));
const getContents = useFile(state => state.getContents);
const { data, isFetching, refetch } = useQuery(["allJson", query], () => getAllJson(), { 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, enabled: isReady && opened,
}); });
return ( const onCreate = async () => {
<Modal title="Saved On The Cloud" opened={opened} onClose={onClose} centered> try {
<ScrollArea h={360}> toast.loading("Saving document...", { id: "fileSave" });
<Stack py="sm"> const { data, error } = await saveToCloud({ contents: getContents(), format: getFormat() });
{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>
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" /> <Divider py="xs" />
<Group position="right"> <Text fz="xs">
<Text fz="xs"> The Cloud Save feature is primarily designed for convenient access and is not advisable for
Cloud Save feature is for ease-of-access only and not recommended to store sensitive data, storing sensitive data.
we don&apos;t guarantee protection of your data. </Text>
</Text> <UpdateNameModal file={currentFile} onClose={() => setCurrentFile(null)} refetch={refetch} />
</Group>
</Modal> </Modal>
); );
}; };

View File

@ -13,7 +13,6 @@ import {
Badge, Badge,
} from "@mantine/core"; } from "@mantine/core";
import { BsCheck } from "react-icons/bs"; import { BsCheck } from "react-icons/bs";
import { paymentURL } from "src/constants/data";
export const PremiumModal: React.FC<ModalProps> = ({ opened, onClose }) => { export const PremiumModal: React.FC<ModalProps> = ({ opened, onClose }) => {
return ( return (
@ -53,7 +52,7 @@ export const PremiumModal: React.FC<ModalProps> = ({ opened, onClose }) => {
</Title> </Title>
<Button <Button
component="a" component="a"
href={paymentURL()} href="/pricing"
variant="filled" variant="filled"
color="teal" color="teal"
size="md" size="md"

View File

@ -11,6 +11,7 @@ import {
Text, Text,
ModalProps, ModalProps,
} from "@mantine/core"; } from "@mantine/core";
import { FiExternalLink } from "react-icons/fi";
import { MdCheck, MdCopyAll } from "react-icons/md"; import { MdCheck, MdCopyAll } from "react-icons/md";
export const ShareModal: React.FC<ModalProps> = ({ opened, onClose }) => { 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}> <Text fz="sm" fw={700}>
Embed into your website Embed into your website
</Text> </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 Learn How to Embed
</Button> </Button>
</Stack> </Stack>

View File

@ -3,6 +3,7 @@ import Link from "next/link";
import styled from "styled-components"; import styled from "styled-components";
import { Button } from "@mantine/core"; import { Button } from "@mantine/core";
import { FaStar } from "react-icons/fa"; import { FaStar } from "react-icons/fa";
import useUser from "src/store/useUser";
import { JSONCrackLogo } from "../JsonCrackLogo"; import { JSONCrackLogo } from "../JsonCrackLogo";
const StyledNavbarWrapper = styled.div` const StyledNavbarWrapper = styled.div`
@ -43,6 +44,8 @@ const Right = styled.div`
`; `;
export const Navbar = () => { export const Navbar = () => {
const isAuthenticated = useUser(state => state.isAuthenticated);
return ( return (
<StyledNavbarWrapper> <StyledNavbarWrapper>
<StyledNavbar> <StyledNavbar>
@ -86,11 +89,13 @@ export const Navbar = () => {
> >
Star us on GitHub Star us on GitHub
</Button> </Button>
<Link href="/sign-in" prefetch={false}> {!isAuthenticated && (
<Button variant="light" radius="md" className="hide-mobile"> <Link href="/sign-in" prefetch={false}>
Sign In <Button variant="light" radius="md" className="hide-mobile">
</Button> Sign In
</Link> </Button>
</Link>
)}
<Link href="/editor" prefetch={false}> <Link href="/editor" prefetch={false}>
<Button color="pink" radius="md"> <Button color="pink" radius="md">
Editor Editor

10
src/lib/api/supabase.ts Normal file
View 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 };

View File

@ -4,10 +4,13 @@ import dynamic from "next/dynamic";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { StyleSheetManager, ThemeProvider } from "styled-components"; import { StyleSheetManager, ThemeProvider } from "styled-components";
import { MantineProvider, MantineThemeOverride } from "@mantine/core"; import { MantineProvider, MantineThemeOverride } from "@mantine/core";
import { SessionContextProvider, Session } from "@supabase/auth-helpers-react";
import { pageview } from "react-ga"; import { pageview } from "react-ga";
import { monaSans } from "src/constants/fonts"; import { monaSans } from "src/constants/fonts";
import GlobalStyle from "src/constants/globalStyle"; import GlobalStyle from "src/constants/globalStyle";
import { lightTheme } from "src/constants/theme"; 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 Toaster = dynamic(() => import("react-hot-toast").then(c => c.Toaster));
const ExternalMode = dynamic(() => import("src/layout/ExternalMode")); const ExternalMode = dynamic(() => import("src/layout/ExternalMode"));
@ -21,8 +24,20 @@ const mantineTheme: MantineThemeOverride = {
primaryShade: 8, primaryShade: 8,
}; };
function JsonCrack({ Component, pageProps }: AppProps) { function JsonCrack({
Component,
pageProps,
}: AppProps<{
initialSession: Session;
}>) {
const router = useRouter(); const router = useRouter();
const setSession = useUser(state => state.setSession);
React.useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
if (session) setSession(session);
});
}, [setSession]);
React.useEffect(() => { React.useEffect(() => {
const handleRouteChange = (url: string) => { const handleRouteChange = (url: string) => {
@ -37,7 +52,7 @@ function JsonCrack({ Component, pageProps }: AppProps) {
}, [router.events]); }, [router.events]);
return ( return (
<> <SessionContextProvider supabaseClient={supabase}>
<GoogleAnalytics /> <GoogleAnalytics />
<StyleSheetManager> <StyleSheetManager>
<ThemeProvider theme={lightTheme}> <ThemeProvider theme={lightTheme}>
@ -63,7 +78,7 @@ function JsonCrack({ Component, pageProps }: AppProps) {
</MantineProvider> </MantineProvider>
</ThemeProvider> </ThemeProvider>
</StyleSheetManager> </StyleSheetManager>
</> </SessionContextProvider>
); );
} }

View File

@ -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; display: inline-block;
text-align: left; text-align: left;
color: ${({ theme, link, alert }) => color: ${({ theme, $link, $alert }) =>
alert ? theme.DANGER : link ? theme.BLURPLE : theme.TEXT_POSITIVE}; $alert ? theme.DANGER : $link ? theme.BLURPLE : theme.TEXT_POSITIVE};
background: ${({ theme }) => theme.BACKGROUND_TERTIARY}; background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
border-radius: 4px; border-radius: 4px;
font-weight: 500; font-weight: 500;
padding: 2px 4px; padding: 2px 4px;
font-size: 14px; font-size: 14px;
margin: ${({ alert }) => (alert ? "8px 0" : "1px")}; margin: ${({ $alert }) => ($alert ? "8px 0" : "1px")};
`; `;
const Docs = () => { const Docs = () => {
@ -74,7 +74,7 @@ const Docs = () => {
<StyledHighlight <StyledHighlight
as="a" as="a"
href="https://jsoncrack.com/editor?json=https://catfact.ninja/fact" href="https://jsoncrack.com/editor?json=https://catfact.ninja/fact"
link $link
> >
https://jsoncrack.com/editor?json=https://catfact.ninja/fact https://jsoncrack.com/editor?json=https://catfact.ninja/fact
</StyledHighlight> </StyledHighlight>
@ -103,7 +103,6 @@ const Docs = () => {
<StyledHighlight>?json=639b65c5a82efc29a24b2de2</StyledHighlight> <StyledHighlight>?json=639b65c5a82efc29a24b2de2</StyledHighlight>
</StyledDescription> </StyledDescription>
<StyledFrame <StyledFrame
scrolling="no"
title="Untitled" title="Untitled"
src="https://codepen.io/AykutSarac/embed/vYaORgM?default-tab=html%2Cresult" src="https://codepen.io/AykutSarac/embed/vYaORgM?default-tab=html%2Cresult"
loading="lazy" loading="lazy"
@ -125,7 +124,7 @@ const Docs = () => {
<StyledHighlight <StyledHighlight
as="a" as="a"
href="https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage" href="https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage"
link $link
> >
MessagePort MessagePort
</StyledHighlight> </StyledHighlight>

View File

@ -9,7 +9,6 @@ import { EditorWrapper } from "src/layout/EditorWrapper";
import { Loading } from "src/layout/Loading"; import { Loading } from "src/layout/Loading";
import useFile from "src/store/useFile"; import useFile from "src/store/useFile";
import useJson from "src/store/useJson"; import useJson from "src/store/useJson";
import useUser from "src/store/useUser";
const Panes = dynamic(() => import("src/containers/Editor/Panes")); const Panes = dynamic(() => import("src/containers/Editor/Panes"));
@ -29,15 +28,13 @@ export const StyledEditorWrapper = styled.div`
`; `;
const EditorPage: React.FC = () => { const EditorPage: React.FC = () => {
const { query } = useRouter(); const { query, isReady } = useRouter();
const checkSession = useUser(state => state.checkSession);
const checkEditorSession = useFile(state => state.checkEditorSession); const checkEditorSession = useFile(state => state.checkEditorSession);
const loading = useJson(state => state.loading); const loading = useJson(state => state.loading);
React.useEffect(() => { React.useEffect(() => {
checkSession(); if (isReady) checkEditorSession({ url: query?.url, json: query?.json });
checkEditorSession({ url: query?.url, json: query?.json }); }, [checkEditorSession, isReady, query]);
}, [checkEditorSession, checkSession, query]);
if (loading) return <Loading message="Fetching JSON from cloud..." />; if (loading) return <Loading message="Fetching JSON from cloud..." />;

View File

@ -3,12 +3,12 @@ import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import styled from "styled-components"; import styled from "styled-components";
import { PaperProps, Center, Button, Group, Paper, Stack, TextInput, Text } from "@mantine/core"; 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 { toast } from "react-hot-toast";
import { Footer } from "src/layout/Footer"; import { Footer } from "src/layout/Footer";
import { JSONCrackLogo } from "src/layout/JsonCrackLogo"; import { JSONCrackLogo } from "src/layout/JsonCrackLogo";
import { Navbar } from "src/layout/Navbar"; 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";
const StyledPageWrapper = styled.div` const StyledPageWrapper = styled.div`
height: calc(100vh - 27px); height: calc(100vh - 27px);
@ -29,12 +29,11 @@ export function AuthenticationForm(props: PaperProps) {
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => { const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
setLoading(true); setLoading(true);
altogic.auth supabase.auth
.sendResetPwdEmail(email) .resetPasswordForEmail(email, { redirectTo: "/reset-password" })
.then(res => { .then(({ error }) => {
if (res.errors) { if (error) return toast.error(error.message);
toast.error(res.errors.items[0].message); setSuccess(true);
} else setSuccess(true);
}) })
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}; };
@ -79,14 +78,12 @@ const StyledHeroSection = styled.section`
`; `;
const SignIn = () => { const SignIn = () => {
const { isReady, replace } = useRouter(); const { isReady, push } = useRouter();
const checkSession = useUser(state => state.checkSession); const session = useSession();
const isAuthenticated = useUser(state => state.isAuthenticated);
React.useEffect(() => { React.useEffect(() => {
if (!isReady) checkSession(); if (session) push("/editor");
if (isAuthenticated) replace("/"); }, [isReady, session, push]);
}, [isReady, isAuthenticated, replace, checkSession]);
return ( return (
<div> <div>

View File

@ -20,16 +20,18 @@ import {
} from "@mantine/core"; } from "@mantine/core";
import { useForm } from "@mantine/form"; import { useForm } from "@mantine/form";
import { useToggle, upperFirst } from "@mantine/hooks"; import { useToggle, upperFirst } from "@mantine/hooks";
import { useSession } from "@supabase/auth-helpers-react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { AiOutlineGithub, AiOutlineGoogle } from "react-icons/ai"; import { AiOutlineGithub, AiOutlineGoogle } from "react-icons/ai";
import { Footer } from "src/layout/Footer"; import { Footer } from "src/layout/Footer";
import { JSONCrackLogo } from "src/layout/JsonCrackLogo"; import { JSONCrackLogo } from "src/layout/JsonCrackLogo";
import { Navbar } from "src/layout/Navbar"; import { Navbar } from "src/layout/Navbar";
import { altogic } from "src/lib/api/altogic"; import { altogic } from "src/lib/api/altogic";
import { supabase } from "src/lib/api/supabase";
import useUser from "src/store/useUser"; import useUser from "src/store/useUser";
export function AuthenticationForm(props: PaperProps) { export function AuthenticationForm(props: PaperProps) {
const login = useUser(state => state.login); const setSession = useUser(state => state.setSession);
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
const [type, toggle] = useToggle<"login" | "register">(["login", "register"]); const [type, toggle] = useToggle<"login" | "register">(["login", "register"]);
const [done, setDone] = React.useState(false); const [done, setDone] = React.useState(false);
@ -59,29 +61,34 @@ export function AuthenticationForm(props: PaperProps) {
setLoading(true); setLoading(true);
if (type === "login") { if (type === "login") {
altogic.auth supabase.auth
.signInWithEmail(form.values.email, form.values.password) .signInWithPassword({
.then(({ user, errors }) => { email: form.values.email,
if (errors?.items.length || !user) return toast.error("Incorrect email or password!"); password: form.values.password,
login(user as any); })
.then(({ data, error }) => {
if (error) return toast.error(error.message);
setSession(data.session);
}) })
.finally(() => setLoading(false)); .finally(() => setLoading(false));
} else { } else {
altogic.auth supabase.auth
.signUpWithEmail(form.values.email, form.values.password, form.values.name) .signInWithPassword({
.then(({ errors }) => { email: form.values.email,
if (errors?.items.length) { password: form.values.password,
return errors.items.forEach(e => toast.error(e.message)); })
} .then(({ data, error }) => {
if (error) return toast.error(error.message);
toast.success("Registration successful!"); toast.success("Registration successful!");
setDone(true); setDone(true);
setSession(data.session);
}) })
.finally(() => setLoading(false)); .finally(() => setLoading(false));
} }
}; };
const handleLoginClick = (provider: "github" | "google") => { const handleLoginClick = (provider: "github" | "google") => {
altogic.auth.signInWithProvider(provider); supabase.auth.signInWithOAuth({ provider });
}; };
if (done) { if (done) {
@ -225,6 +232,10 @@ function ResetPassword(props: PaperProps) {
e.preventDefault(); e.preventDefault();
if (query?.access_token && typeof query?.access_token === "string") { if (query?.access_token && typeof query?.access_token === "string") {
setLoading(true); setLoading(true);
// TODO: solve here
supabase.auth.updateUser({
password,
});
altogic.auth altogic.auth
.resetPwdWithToken(query?.access_token, password) .resetPwdWithToken(query?.access_token, password)
.then(({ errors }) => { .then(({ errors }) => {
@ -275,14 +286,12 @@ function ResetPassword(props: PaperProps) {
const SignIn = () => { const SignIn = () => {
const { isReady, push, query } = useRouter(); const { isReady, push, query } = useRouter();
const isAuthenticated = useUser(state => state.isAuthenticated); const session = useSession();
const checkSession = useUser(state => state.checkSession);
const isPasswordReset = query?.action === "reset-pwd" && !query?.error; const isPasswordReset = query?.action === "reset-pwd" && !query?.error;
React.useEffect(() => { React.useEffect(() => {
if (isAuthenticated) push("/editor"); if (session) push("/editor");
else checkSession(); }, [isReady, session, push]);
}, [isReady, isAuthenticated, push, checkSession]);
return ( return (
<div> <div>

View File

@ -1,42 +1,50 @@
import dayjs from "dayjs"; import { PostgrestSingleResponse } from "@supabase/supabase-js";
import { decompressFromBase64 } from "lz-string"; import toast from "react-hot-toast";
import { altogic, AltogicResponse } from "src/lib/api/altogic"; import { supabase } from "src/lib/api/supabase";
import { File } from "src/store/useFile"; import { File } from "src/store/useFile";
import useUser from "src/store/useUser";
import { FileFormat } from "src/types/models"; import { FileFormat } from "src/types/models";
const saveToCloud = async ( type CloudSave = {
id: string | null, id?: string;
contents: string, contents: string;
format = FileFormat.JSON format: FileFormat;
): 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 });
}; };
const getFromCloud = async (id: string) => { const saveToCloud = async ({
const { data, errors } = await altogic.endpoint.get(`json/${id}`, undefined, { id,
userid: altogic.auth.getUser()?._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) { const { data, error } = await supabase
return { ...data, json: decompressFromBase64(data.json) }; .from("document")
.select()
.eq("owner_id", userId)
.order("created_at", { ascending: false });
if (error) {
toast.error(error.message);
return [];
} }
return data; return data;
}; };
const getAllJson = async (): Promise<AltogicResponse<{ result: File[] }>> => const updateJson = async (id: string, data: object) => {
await altogic.endpoint.get("json"); return await supabase.from("document").update(data).eq("id", id).select("private");
};
const updateJson = async (id: string, data: object) => const deleteJson = async (id: string) => await supabase.from("document").delete().eq("id", id);
await altogic.endpoint.put(`json/${id}`, {
...data,
});
const deleteJson = async (id: string) => await altogic.endpoint.delete(`json/${id}`);
export { saveToCloud, getFromCloud, getAllJson, updateJson, deleteJson }; export { saveToCloud, getFromCloud, getAllJson, updateJson, deleteJson };

View File

@ -7,7 +7,7 @@ import { create } from "zustand";
import { defaultJson } from "src/constants/data"; import { defaultJson } from "src/constants/data";
import { contentToJson, jsonToContent } from "src/lib/utils/json/jsonAdapter"; import { contentToJson, jsonToContent } from "src/lib/utils/json/jsonAdapter";
import { isIframe } from "src/lib/utils/widget"; 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 { FileFormat } from "src/types/models";
import useGraph from "./useGraph"; import useGraph from "./useGraph";
import useJson from "./useJson"; import useJson from "./useJson";
@ -29,7 +29,6 @@ interface JsonActions {
setError: (error: object | null) => void; setError: (error: object | null) => void;
setHasChanges: (hasChanges: boolean) => void; setHasChanges: (hasChanges: boolean) => void;
setContents: (data: SetContents) => void; setContents: (data: SetContents) => void;
saveToCloud: (isNew?: boolean) => void;
fetchFile: (fileId: string) => void; fetchFile: (fileId: string) => void;
fetchUrl: (url: string) => void; fetchUrl: (url: string) => void;
editContents: (path: string, value: string, callback?: () => void) => void; editContents: (path: string, value: string, callback?: () => void) => void;
@ -41,13 +40,15 @@ interface JsonActions {
} }
export type File = { export type File = {
_id: string; id: string;
views: number;
owner_id: string;
name: string; name: string;
json: string; content: string;
private: boolean; private: boolean;
format?: FileFormat; format: FileFormat;
createdAt: string; created_at: string;
updatedAt: string; updated_at: string;
}; };
const initialStates = { const initialStates = {
@ -96,7 +97,7 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
}, },
setFile: fileData => { setFile: fileData => {
set({ fileData, format: fileData.format || FileFormat.JSON }); set({ fileData, format: fileData.format || FileFormat.JSON });
get().setContents({ contents: fileData.json, hasChanges: false }); get().setContents({ contents: fileData.content, hasChanges: false });
}, },
getContents: () => get().contents, getContents: () => get().contents,
getFormat: () => get().format, getFormat: () => get().format,
@ -109,7 +110,7 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
const contentJson = await contentToJson(get().contents, prevFormat); const contentJson = await contentToJson(get().contents, prevFormat);
const jsonContent = await jsonToContent(JSON.stringify(contentJson, null, 2), format); 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" }); event({ action: "change_data_format", category: "User" });
} catch (error) { } catch (error) {
@ -121,11 +122,12 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
try { try {
set({ ...(contents && { contents }), error: null, hasChanges }); set({ ...(contents && { contents }), error: null, hasChanges });
const isFetchURL = window.location.href.includes("?");
const json = await contentToJson(get().contents, get().format); const json = await contentToJson(get().contents, get().format);
if (!useStored.getState().liveTransform && skipUpdate) return; 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("content", contents);
sessionStorage.setItem("format", get().format); sessionStorage.setItem("format", get().format);
} }
@ -140,30 +142,6 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
}, },
setError: error => set({ error }), setError: error => set({ error }),
setHasChanges: hasChanges => set({ hasChanges }), 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 => { fetchUrl: async url => {
try { try {
const res = await fetch(url); const res = await fetch(url);
@ -178,7 +156,7 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
} }
}, },
checkEditorSession: ({ url, json }) => { 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); if (typeof json === "string") return get().fetchFile(json);
const sessionContent = sessionStorage.getItem("content") as string | null; const sessionContent = sessionStorage.getItem("content") as string | null;
@ -190,15 +168,14 @@ const useFile = create<FileStates & JsonActions>()((set, get) => ({
}, },
fetchFile: async id => { fetchFile: async id => {
try { try {
if (isURL(id)) return get().fetchUrl(id); const { data, error } = await getFromCloud(id);
if (error) throw error;
const file = await getFromCloud(id); if (data?.length) get().setFile(data[0]);
if (data?.length === 0) throw new Error("Document not found");
get().setFile(file); } catch (error: any) {
} catch (error) { if (error?.message) toast.error(error?.message);
useJson.setState({ loading: false }); get().setContents({ contents: defaultJson, hasChanges: false });
useGraph.setState({ loading: false });
console.error(error);
} }
}, },
editContents: async (path, value, callback) => { editContents: async (path, value, callback) => {

View File

@ -27,7 +27,7 @@ const initialStates: ModalState = {
cancelPremium: false, cancelPremium: false,
}; };
const authModals: Modal[] = ["cloud", "share", "account", "schema"]; const authModals: Modal[] = ["cloud", "account", "schema"];
const premiumModals: Modal[] = ["schema"]; const premiumModals: Modal[] = ["schema"];
const useModal = create<ModalState & ModalActions>()(set => ({ const useModal = create<ModalState & ModalActions>()(set => ({

View File

@ -1,31 +1,25 @@
import { setUser } from "@sentry/nextjs"; import { Session, User } from "@supabase/supabase-js";
import { set as gaSet } from "react-ga";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { create } from "zustand"; import { create } from "zustand";
import { altogic } from "src/lib/api/altogic"; import { supabase } from "src/lib/api/supabase";
import { AltogicAuth, User } from "src/types/altogic";
import useModal from "./useModal";
const isDevelopment = process.env.NODE_ENV === "development";
interface UserActions { interface UserActions {
login: (response: User | AltogicAuth) => void;
logout: () => void; logout: () => void;
setUser: (key: keyof typeof initialStates, value: any) => void; setSession: (session: Session) => void;
checkSession: () => void;
} }
const devUser = { interface Premium {
_id: "0", id: 101;
provider: "google", created_at: "2023-07-29T17:33:10.403228+00:00";
providerUserId: "115637229829349229857", user: null;
email: "test@jsoncrack.com", status: "active";
name: "JSON Crack", ends_at: null;
profilePicture: "", subscription_id: null;
signUpAt: "2022-12-04T11:07:32.000Z", variant_id: null;
lastLoginAt: "2023-05-13T09:56:02.915Z", renews_at: null;
updatedAt: "2023-05-06T16:19:47.486Z", cancelled: null;
} as User; email: "aykutsarac0@gmail.com";
}
interface UserStates { interface UserStates {
user: User | null; user: User | null;
@ -43,68 +37,24 @@ const initialStates: UserStates = {
const useUser = create<UserStates & UserActions>()(set => ({ const useUser = create<UserStates & UserActions>()(set => ({
...initialStates, ...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 () => { logout: async () => {
const session = altogic.auth.getSession(); toast.loading("Logging out...", { id: "logout" });
await supabase.auth.signOut();
if (!session) return;
const { errors } = await altogic.auth.signOut(session.token);
if (errors?.items) return console.error(errors);
set(initialStates); set(initialStates);
altogic.auth.clearLocalData(); toast.success("Logged out.", { id: "logout" });
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 });
}
}
}, },
})); }));

221
yarn.lock
View File

@ -1955,6 +1955,75 @@
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== 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": "@swc/helpers@0.4.14":
version "0.4.14" version "0.4.14"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.14.tgz#1352ac6d95e3617ccb7c1498ff019654f1e12a74" 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" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== 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@*": "@types/prop-types@*":
version "15.7.5" version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" 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" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== 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": "@typescript-eslint/parser@^5.42.0":
version "5.54.1" version "5.54.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.54.1.tgz#05761d7f777ef1c37c971d3af6631715099b084c" 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" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-0.1.2.tgz#15f4b9bcef012044df31142c14333caf6e0260d0"
integrity sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg== 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: busboy@1.6.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
@ -2720,6 +2808,13 @@ cross-fetch@^3.1.4:
dependencies: dependencies:
node-fetch "2.6.7" 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: cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@ -2770,6 +2865,14 @@ d3-shape@^3.0.1:
dependencies: dependencies:
d3-path "^3.1.0" 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: damerau-levenshtein@^1.0.8:
version "1.0.8" version "1.0.8"
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" 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: dependencies:
ms "2.1.2" 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: debug@^3.2.7:
version "3.2.7" version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" 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-date-object "^1.0.1"
is-symbol "^1.0.2" 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: escalade@^3.1.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 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" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.0.tgz#084eb7f5b5388df1451e63f4c2aafd71b217ccb3"
integrity sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg== 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: fast-deep-equal@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" 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" gopd "^1.0.1"
has-tostringtag "^1.0.0" 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: is-weakmap@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" 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" resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59"
integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== 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: "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 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" resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27"
integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== 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: ms@2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 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" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== 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: next@13.3.0:
version "13.3.0" version "13.3.0"
resolved "https://registry.yarnpkg.com/next/-/next-13.3.0.tgz#40632d303d74fc8521faa0a5bf4a033a392749b1" resolved "https://registry.yarnpkg.com/next/-/next-13.3.0.tgz#40632d303d74fc8521faa0a5bf4a033a392749b1"
@ -4533,6 +4696,13 @@ node-fetch@2.6.7:
dependencies: dependencies:
whatwg-url "^5.0.0" 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: node-fetch@^2.6.7:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6"
@ -4540,6 +4710,11 @@ node-fetch@^2.6.7:
dependencies: dependencies:
whatwg-url "^5.0.0" 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: node-releases@^2.0.12:
version "2.0.13" version "2.0.13"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"
@ -5248,6 +5423,11 @@ semver@^7.3.8:
dependencies: dependencies:
lru-cache "^6.0.0" 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: shallowequal@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" 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" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== 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: typed-array-length@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" 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" for-each "^0.3.3"
is-typed-array "^1.1.9" 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: typescript@5.1.6:
version "5.1.6" version "5.1.6"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" 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" 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== 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: util-deprecate@~1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 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" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== 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: whatwg-url@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
@ -5912,6 +6128,11 @@ xtend@~2.1.1:
dependencies: dependencies:
object-keys "~0.4.0" 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: yallist@^3.0.2:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"