From 56eefc8419a3e4e5594dda5b13161e35e2b07c7a Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 4 Dec 2022 01:31:51 -0800 Subject: [PATCH] add basic admin page, misc fixes --- .../components/file-dropdown/index.tsx | 7 +- .../components/edit-document-list/index.tsx | 2 +- .../components/header/post-buttons/index.tsx | 78 ++++++++++ .../post-buttons/post-buttons.module.css | 12 ++ .../[id]/components/header/title/index.tsx | 48 ++++++ .../components/header/title/title.module.css | 35 +++++ .../post/[id]/components/post-page/index.tsx | 94 +---------- .../post-page/password-modal-wrapper.tsx | 2 + .../components/post-page/post-page.module.css | 57 ------- client/app/(posts)/post/[id]/loading.tsx | 14 ++ client/app/(posts)/post/[id]/page.tsx | 94 +++++++---- .../app/(posts)/post/[id]/styles.module.css | 17 ++ client/app/admin/admin.module.css | 9 ++ client/app/admin/layout.tsx | 17 ++ client/app/admin/loading.tsx | 13 ++ client/app/admin/page.tsx | 96 ++++++++++++ .../badges/created-ago-badge/index.tsx | 10 +- .../badges/expiration-badge/index.tsx | 14 +- .../badges/visibility-control/index.tsx | 7 +- .../app/components/header/header.module.css | 10 ++ client/app/components/header/index.tsx | 12 +- .../app/components/password-modal/index.tsx | 7 +- .../password-modal/modal.module.css | 1 + client/app/components/scroll-to-top/index.tsx | 2 + client/app/layout.tsx | 1 - client/app/mine/page.tsx | 9 +- client/app/styles/globals.css | 5 - client/lib/server/prisma.ts | 19 ++- client/next.config.mjs | 24 +-- client/package.json | 9 +- client/pages/api/admin/index.ts | 2 - client/pnpm-lock.yaml | 147 +++++++++--------- 32 files changed, 552 insertions(+), 322 deletions(-) create mode 100644 client/app/(posts)/post/[id]/components/header/post-buttons/index.tsx create mode 100644 client/app/(posts)/post/[id]/components/header/post-buttons/post-buttons.module.css create mode 100644 client/app/(posts)/post/[id]/components/header/title/index.tsx create mode 100644 client/app/(posts)/post/[id]/components/header/title/title.module.css delete mode 100644 client/app/(posts)/post/[id]/components/post-page/post-page.module.css create mode 100644 client/app/(posts)/post/[id]/loading.tsx create mode 100644 client/app/(posts)/post/[id]/styles.module.css create mode 100644 client/app/admin/admin.module.css create mode 100644 client/app/admin/layout.tsx create mode 100644 client/app/admin/loading.tsx create mode 100644 client/app/admin/page.tsx diff --git a/client/app/(posts)/components/file-dropdown/index.tsx b/client/app/(posts)/components/file-dropdown/index.tsx index bb8cb0a1..573f4048 100644 --- a/client/app/(posts)/components/file-dropdown/index.tsx +++ b/client/app/(posts)/components/file-dropdown/index.tsx @@ -5,12 +5,17 @@ import type { File } from "lib/server/prisma" import styles from "./dropdown.module.css" import buttonStyles from "@components/button/button.module.css" import { ChevronDown, Code, File as FileIcon } from "react-feather" +import { Spinner } from "@components/spinner" type Item = File & { icon: JSX.Element } -const FileDropdown = ({ files }: { files: File[] }) => { +const FileDropdown = ({ files, loading }: { files: File[], loading?: boolean }) => { + if (loading) { + return + } + const items = files.map((file) => { const extension = file.title.split(".").pop() if (codeFileExtensions.includes(extension || "")) { diff --git a/client/app/(posts)/new/components/edit-document-list/index.tsx b/client/app/(posts)/new/components/edit-document-list/index.tsx index 812fd967..f22abb4b 100644 --- a/client/app/(posts)/new/components/edit-document-list/index.tsx +++ b/client/app/(posts)/new/components/edit-document-list/index.tsx @@ -1,6 +1,6 @@ import type { Document } from "../new" import DocumentComponent from "./edit-document" -import { ChangeEvent, memo, useCallback } from "react" +import { ChangeEvent, useCallback } from "react" const DocumentList = ({ docs, diff --git a/client/app/(posts)/post/[id]/components/header/post-buttons/index.tsx b/client/app/(posts)/post/[id]/components/header/post-buttons/index.tsx new file mode 100644 index 00000000..d52b0fd9 --- /dev/null +++ b/client/app/(posts)/post/[id]/components/header/post-buttons/index.tsx @@ -0,0 +1,78 @@ +"use client" + +import Button from "@components/button" +import ButtonGroup from "@components/button-group" +import FileDropdown from "app/(posts)/components/file-dropdown" +import { Edit, ArrowUpCircle, Archive } from "react-feather" +import styles from "./post-buttons.module.css" +import { File } from "@prisma/client" +import { useRouter } from "next/navigation" + +export const PostButtons = ({ + title, + files, + loading, + postId, + parentId +}: { + title: string + files?: File[] + loading?: boolean + postId?: string + parentId?: string +}) => { + const router = useRouter() + const downloadClick = async () => { + if (!files?.length) return + const downloadZip = (await import("client-zip")).downloadZip + const blob = await downloadZip( + files.map((file: any) => { + return { + name: file.title, + input: file.content, + lastModified: new Date(file.updatedAt) + } + }) + ).blob() + const link = document.createElement("a") + link.href = URL.createObjectURL(blob) + link.download = `${title}.zip` + link.click() + link.remove() + } + + const editACopy = () => { + router.push(`/new/from/${postId}`) + } + + const viewParentClick = () => { + router.push(`/post/${parentId}`) + } + + return ( + + + + {viewParentClick && ( + + )} + + + + + ) +} diff --git a/client/app/(posts)/post/[id]/components/header/post-buttons/post-buttons.module.css b/client/app/(posts)/post/[id]/components/header/post-buttons/post-buttons.module.css new file mode 100644 index 00000000..36da29bc --- /dev/null +++ b/client/app/(posts)/post/[id]/components/header/post-buttons/post-buttons.module.css @@ -0,0 +1,12 @@ +.buttons { + display: flex; + justify-content: flex-end; + margin-bottom: var(--gap); +} + +@media screen and (max-width: 768px) { + .buttons { + display: flex; + justify-content: center; + } +} diff --git a/client/app/(posts)/post/[id]/components/header/title/index.tsx b/client/app/(posts)/post/[id]/components/header/title/index.tsx new file mode 100644 index 00000000..4269f6cd --- /dev/null +++ b/client/app/(posts)/post/[id]/components/header/title/index.tsx @@ -0,0 +1,48 @@ +import CreatedAgoBadge from '@components/badges/created-ago-badge' +import ExpirationBadge from '@components/badges/expiration-badge' +import VisibilityBadge from '@components/badges/visibility-badge' +import Skeleton from '@components/skeleton' +import styles from './title.module.css' + +type TitleProps = { + title: string + loading?: boolean + displayName?: string + visibility?: string + createdAt?: Date + expiresAt?: Date +} + +export const PostTitle = ({ + title, + displayName, + visibility, + createdAt, + expiresAt, + loading +}: TitleProps) => { + return ( + +

+ {title}{" "} + + by {displayName || "anonymous"} + +

+ {!loading && ( + + {visibility && } + {createdAt && } + {expiresAt && } + + )} + {loading && ( + + + + + + )} +
+ ) +} diff --git a/client/app/(posts)/post/[id]/components/header/title/title.module.css b/client/app/(posts)/post/[id]/components/header/title/title.module.css new file mode 100644 index 00000000..613afacb --- /dev/null +++ b/client/app/(posts)/post/[id]/components/header/title/title.module.css @@ -0,0 +1,35 @@ +.title { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +.title .badges { + display: flex; + gap: var(--gap-half); + flex-wrap: wrap; +} + +.title h3 { + margin: 0; + padding: 0; + display: inline-block; +} + +@media screen and (max-width: 768px) { + .title { + flex-direction: column; + gap: var(--gap-half); + } + + .title .badges { + align-items: center; + justify-content: center; + } + + .buttons { + display: flex; + justify-content: center; + } +} diff --git a/client/app/(posts)/post/[id]/components/post-page/index.tsx b/client/app/(posts)/post/[id]/components/post-page/index.tsx index 92945c61..d4862c2a 100644 --- a/client/app/(posts)/post/[id]/components/post-page/index.tsx +++ b/client/app/(posts)/post/[id]/components/post-page/index.tsx @@ -1,21 +1,11 @@ "use client" -import VisibilityBadge from "@components/badges/visibility-badge" import DocumentComponent from "./view-document" -import styles from "./post-page.module.css" import { useEffect, useState } from "react" -import FileDropdown from "app/(posts)/components/file-dropdown" -import ScrollToTop from "@components/scroll-to-top" import { useRouter } from "next/navigation" -import ExpirationBadge from "@components/badges/expiration-badge" -import CreatedAgoBadge from "@components/badges/created-ago-badge" import PasswordModalPage from "./password-modal-wrapper" -import VisibilityControl from "@components/badges/visibility-control" import { File, PostWithFilesAndAuthor } from "@lib/server/prisma" -import ButtonGroup from "@components/button-group" -import Button from "@components/button" -import { Archive, ArrowUpCircle, Edit } from "react-feather" type Props = { post: string | PostWithFilesAndAuthor @@ -28,7 +18,6 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => { typeof initialPost === "string" ? JSON.parse(initialPost) : initialPost ) - const [visibility, setVisibility] = useState(post.visibility) const router = useRouter() useEffect(() => { @@ -68,80 +57,8 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => { return } - const download = async () => { - if (!post.files) return - const downloadZip = (await import("client-zip")).downloadZip - const blob = await downloadZip( - post.files.map((file: any) => { - return { - name: file.title, - input: file.content, - lastModified: new Date(file.updatedAt) - } - }) - ).blob() - const link = document.createElement("a") - link.href = URL.createObjectURL(blob) - link.download = `${post.title}.zip` - link.click() - link.remove() - } - - const editACopy = () => { - router.push(`/new/from/${post.id}`) - } - - const viewParentClick = () => { - router.push(`/post/${post.parentId}`) - } - return ( <> -
- - - - {post.parentId && ( - - )} - - - - - -

- {post.title}{" "} - - by {post.author?.displayName} - -

- - - - - -
-
- {post.description && ( -
-

{post.description}

-
- )} - {/* {post.files.length > 1 && } */} {post.files?.map(({ id, content, title, html }: File) => ( { preview={html} /> ))} - {isAuthor && ( - - - - )} - + ) } diff --git a/client/app/(posts)/post/[id]/components/post-page/password-modal-wrapper.tsx b/client/app/(posts)/post/[id]/components/post-page/password-modal-wrapper.tsx index 39baf84d..03c465ea 100644 --- a/client/app/(posts)/post/[id]/components/post-page/password-modal-wrapper.tsx +++ b/client/app/(posts)/post/[id]/components/post-page/password-modal-wrapper.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Post, PostWithFilesAndAuthor } from "@lib/server/prisma" import PasswordModal from "@components/password-modal" import { useRouter } from "next/navigation" diff --git a/client/app/(posts)/post/[id]/components/post-page/post-page.module.css b/client/app/(posts)/post/[id]/components/post-page/post-page.module.css deleted file mode 100644 index 5cbe1437..00000000 --- a/client/app/(posts)/post/[id]/components/post-page/post-page.module.css +++ /dev/null @@ -1,57 +0,0 @@ -.header .title { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; -} - -.header .title .badges { - display: flex; - gap: var(--gap-half); - flex-wrap: wrap; -} - -.header .title h3 { - margin: 0; - padding: 0; - display: inline-block; -} - -.header .buttons { - display: flex; - justify-content: flex-end; - margin-bottom: var(--gap); -} - -.controls { - display: flex; - justify-content: flex-end; -} - -@media screen and (max-width: 900px) { - .header { - flex-direction: column; - gap: var(--gap); - } -} - -@media screen and (max-width: 768px) { - .header .title { - flex-direction: column; - gap: var(--gap-half); - } - - .header .title .badges { - align-items: center; - justify-content: center; - } - - .header .buttons { - display: flex; - justify-content: center; - } - - .controls { - justify-content: center; - } -} diff --git a/client/app/(posts)/post/[id]/loading.tsx b/client/app/(posts)/post/[id]/loading.tsx new file mode 100644 index 00000000..135d50f7 --- /dev/null +++ b/client/app/(posts)/post/[id]/loading.tsx @@ -0,0 +1,14 @@ +import { PostButtons } from "./components/header/post-buttons" +import { PostTitle } from "./components/header/title" +import styles from "./styles.module.css" + +export default function PostLoading() { + return ( + <> +
+ + +
+ + ) +} diff --git a/client/app/(posts)/post/[id]/page.tsx b/client/app/(posts)/post/[id]/page.tsx index a786cef0..9bc5b3c6 100644 --- a/client/app/(posts)/post/[id]/page.tsx +++ b/client/app/(posts)/post/[id]/page.tsx @@ -1,41 +1,45 @@ -import PostPage from "app/(posts)/post/[id]/components/post-page" +import PostPage from "./components/post-page" import { notFound, redirect } from "next/navigation" -import { getAllPosts, getPostById, Post } from "@lib/server/prisma" +import { getPostById, Post, PostWithFilesAndAuthor } from "@lib/server/prisma" import { getCurrentUser } from "@lib/server/session" +import ScrollToTop from "@components/scroll-to-top" +import { title } from "process" +import { PostButtons } from "./components/header/post-buttons" +import styles from "./styles.module.css" +import { PostTitle } from "./components/header/title" +import VisibilityControl from "@components/badges/visibility-control" export type PostProps = { post: Post isProtected?: boolean } -export async function generateStaticParams() { - const posts = await getAllPosts({ - where: { - visibility: { - equals: "public" - } - } - }) +// export async function generateStaticParams() { +// const posts = await getAllPosts({ +// where: { +// visibility: { +// equals: "public" +// } +// } +// }) - return posts.map((post) => ({ - id: post.id - })) +// return posts.map((post) => ({ +// id: post.id +// })) +// } + +const fetchOptions = { + withFiles: true, + withAuthor: true } const getPost = async (id: string) => { - const post = await getPostById(id, { - withFiles: true, - withAuthor: true - }) + const post = (await getPostById(id, fetchOptions)) as PostWithFilesAndAuthor if (!post) { return notFound() } - if (post.visibility === "public") { - return { post } - } - const user = await getCurrentUser() const isAuthorOrAdmin = user?.id === post?.authorId || user?.role === "admin" @@ -55,7 +59,15 @@ const getPost = async (id: string) => { return { post: { visibility: "protected", - id: post.id + id: post.id, + files: [], + parentId: "", + title: "", + createdAt: new Date("1970-01-01"), + author: { + displayName: "" + }, + description: "" }, isProtected: true, isAuthor: isAuthorOrAdmin @@ -83,12 +95,40 @@ const PostView = async ({ const { post, isProtected, isAuthor } = await getPost(params.id) // TODO: serialize dates in prisma middleware instead of passing as JSON const stringifiedPost = JSON.stringify(post) + return ( - + <> +
+ + +
+ {post.description && ( +
+

{post.description}

+
+ )} + + {isAuthor && ( + + + + )} + + ) } diff --git a/client/app/(posts)/post/[id]/styles.module.css b/client/app/(posts)/post/[id]/styles.module.css new file mode 100644 index 00000000..8494b004 --- /dev/null +++ b/client/app/(posts)/post/[id]/styles.module.css @@ -0,0 +1,17 @@ +@media screen and (max-width: 900px) { + .header { + flex-direction: column; + gap: var(--gap); + } +} + +.controls { + display: flex; + justify-content: flex-end; +} + +@media screen and (max-width: 768px) { + .controls { + justify-content: center; + } +} diff --git a/client/app/admin/admin.module.css b/client/app/admin/admin.module.css new file mode 100644 index 00000000..4182821a --- /dev/null +++ b/client/app/admin/admin.module.css @@ -0,0 +1,9 @@ +.table { + width: 100%; + display: block; + white-space: nowrap; +} + +.table thead th { + font-weight: bold; +} diff --git a/client/app/admin/layout.tsx b/client/app/admin/layout.tsx new file mode 100644 index 00000000..92a478f9 --- /dev/null +++ b/client/app/admin/layout.tsx @@ -0,0 +1,17 @@ +import { getCurrentUser } from "@lib/server/session" +import { redirect } from "next/navigation" + +export default async function AdminLayout({ + children +}: { + children: React.ReactNode +}) { + const user = await getCurrentUser() + const isAdmin = user?.role === "admin" + + if (!isAdmin) { + return redirect("/") + } + + return children +} diff --git a/client/app/admin/loading.tsx b/client/app/admin/loading.tsx new file mode 100644 index 00000000..2d516c49 --- /dev/null +++ b/client/app/admin/loading.tsx @@ -0,0 +1,13 @@ +import { PostTable, UserTable } from "./page" + +export default function AdminLoading() { + return ( +
+

Admin

+

Users

+ +

Posts

+ +
+ ) +} diff --git a/client/app/admin/page.tsx b/client/app/admin/page.tsx new file mode 100644 index 00000000..e0a7aefb --- /dev/null +++ b/client/app/admin/page.tsx @@ -0,0 +1,96 @@ +import { getAllPosts, getAllUsers } from "@lib/server/prisma" +import { Spinner } from "@components/spinner" +import styles from "./admin.module.css" + +export function UserTable({ + users +}: { + users?: Awaited> +}) { + return ( + + + + + + + + + + + {users?.map((user) => ( + + + + + + + ))} + {!users && ( + + + + )} + +
NameEmailRoleUser ID
{user.name ? user.name : "no name"}{user.email}{user.role}{user.id}
+ +
+ ) +} + +export function PostTable({ + posts +}: { + posts?: Awaited> +}) { + return ( + + + + + + + + + + + + {posts?.map((post) => ( + + + + + + + + ))} + {!posts && ( + + + + )} + +
TitleAuthorCreatedVisibilityPost ID
{post.title}{"author" in post ? post.author.name : "no author"}{post.createdAt.toLocaleDateString()}{post.visibility}{post.id}
+ +
+ ) +} + +export default async function AdminPage() { + const usersPromise = getAllUsers() + const postsPromise = getAllPosts({ + withAuthor: true + }) + + const [users, posts] = await Promise.all([usersPromise, postsPromise]) + + return ( +
+

Admin

+

Users

+ +

Posts

+ +
+ ) +} diff --git a/client/app/components/badges/created-ago-badge/index.tsx b/client/app/components/badges/created-ago-badge/index.tsx index d2675143..2ff6722a 100644 --- a/client/app/components/badges/created-ago-badge/index.tsx +++ b/client/app/components/badges/created-ago-badge/index.tsx @@ -1,5 +1,5 @@ 'use client' -// import Tooltip from "@components/tooltip" +import Tooltip from "@components/tooltip" import { timeAgo } from "@lib/time-ago" import { useMemo, useState, useEffect } from "react" import Badge from "../badge" @@ -15,15 +15,15 @@ const CreatedAgoBadge = ({ createdAt }: { createdAt: string | Date }) => { return () => clearInterval(interval) }, [createdDate]) - // const formattedTime = `${createdDate.toLocaleDateString()} ${createdDate.toLocaleTimeString()}` + const formattedTime = `${createdDate.toLocaleDateString()} ${createdDate.toLocaleTimeString()}` return ( - // TODO: investigate tooltip - // + // TODO: investigate tooltip not showing + {" "} <>{time} - // + ) } diff --git a/client/app/components/badges/expiration-badge/index.tsx b/client/app/components/badges/expiration-badge/index.tsx index ef42f185..9b3309f8 100644 --- a/client/app/components/badges/expiration-badge/index.tsx +++ b/client/app/components/badges/expiration-badge/index.tsx @@ -1,3 +1,5 @@ +"use client" + import Tooltip from "@components/tooltip" import { timeUntil } from "@lib/time-ago" import { useEffect, useMemo, useState } from "react" @@ -5,8 +7,7 @@ import Badge from "../badge" const ExpirationBadge = ({ postExpirationDate -}: // onExpires -{ +}: { postExpirationDate: Date | string | null onExpires?: () => void }) => { @@ -39,15 +40,6 @@ const ExpirationBadge = ({ return timeUntilString === "in 0 seconds" }, [timeUntilString]) - // useEffect(() => { - // // check if expired every - // if (isExpired) { - // if (onExpires) { - // onExpires(); - // } - // } - // }, [isExpired, onExpires]) - if (!expirationDate) { return null } diff --git a/client/app/components/badges/visibility-control/index.tsx b/client/app/components/badges/visibility-control/index.tsx index ae653801..07e61cc4 100644 --- a/client/app/components/badges/visibility-control/index.tsx +++ b/client/app/components/badges/visibility-control/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import PasswordModal from "@components/password-modal" import { useCallback, useState } from "react" import ButtonGroup from "@components/button-group" @@ -8,10 +10,11 @@ import { Spinner } from "@components/spinner" type Props = { postId: string visibility: string - setVisibility: (visibility: string) => void } -const VisibilityControl = ({ postId, visibility, setVisibility }: Props) => { +const VisibilityControl = ({ postId, visibility: postVisibility }: Props) => { + const [visibility, setVisibility] = useState(postVisibility) + const [isSubmitting, setSubmitting] = useState() const [passwordModalVisible, setPasswordModalVisible] = useState(false) const { setToast } = useToasts() diff --git a/client/app/components/header/header.module.css b/client/app/components/header/header.module.css index 397a986e..7de531e5 100644 --- a/client/app/components/header/header.module.css +++ b/client/app/components/header/header.module.css @@ -36,6 +36,16 @@ width: min-content; } +.header { + transition: opacity 0.2s ease-in-out; + opacity: 0; +} + +.header:not(.loading) { + opacity: 1; +} + + .selectContent { width: auto; height: 18px; diff --git a/client/app/components/header/index.tsx b/client/app/components/header/index.tsx index 93c3202d..dfb5be69 100644 --- a/client/app/components/header/index.tsx +++ b/client/app/components/header/index.tsx @@ -34,8 +34,10 @@ type Tab = { const Header = () => { const session = useSession() - const signedIn = session?.status === "authenticated" + const isSignedIn = session?.status === "authenticated" const isAdmin = session?.data?.user?.role === "admin" + const isLoading = session?.status === "loading" + const pathname = usePathname() const { setTheme, resolvedTheme } = useTheme() @@ -98,7 +100,7 @@ const Header = () => { value: "theme" }) - if (signedIn) + if (isSignedIn) return [ { name: "New", @@ -151,7 +153,7 @@ const Header = () => { href: "/signup" } ] - }, [isAdmin, resolvedTheme, signedIn, setTheme]) + }, [isAdmin, resolvedTheme, isSignedIn, setTheme]) // // TODO: this should not be necessary. // if (!clientHydrated) { @@ -165,7 +167,9 @@ const Header = () => { const buttons = pages.map(getButton) return ( -
+
{buttons}
diff --git a/client/app/components/password-modal/index.tsx b/client/app/components/password-modal/index.tsx index 57494f7c..21e71f66 100644 --- a/client/app/components/password-modal/index.tsx +++ b/client/app/components/password-modal/index.tsx @@ -45,7 +45,6 @@ const PasswordModal = ({ if (!open) onClose() }} > - {/* Enter a password */} )} - - - - + + diff --git a/client/app/components/password-modal/modal.module.css b/client/app/components/password-modal/modal.module.css index bc04098b..ea258b67 100644 --- a/client/app/components/password-modal/modal.module.css +++ b/client/app/components/password-modal/modal.module.css @@ -45,6 +45,7 @@ display: flex; justify-content: flex-end; width: 100%; + gap: var(--gap-half); } @keyframes overlayShow { diff --git a/client/app/components/scroll-to-top/index.tsx b/client/app/components/scroll-to-top/index.tsx index 3caa3728..86fbc918 100644 --- a/client/app/components/scroll-to-top/index.tsx +++ b/client/app/components/scroll-to-top/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import Button from "@components/button" import Tooltip from "@components/tooltip" import { useEffect, useState } from "react" diff --git a/client/app/layout.tsx b/client/app/layout.tsx index 50c792e1..d4c7aa2a 100644 --- a/client/app/layout.tsx +++ b/client/app/layout.tsx @@ -1,6 +1,5 @@ import "@styles/globals.css" import { LayoutWrapper } from "./root-layout-wrapper" -import { getSession } from "@lib/server/session" import { ServerThemeProvider } from "@wits/next-themes" interface RootLayoutProps { diff --git a/client/app/mine/page.tsx b/client/app/mine/page.tsx index 979811b6..1f0dcb53 100644 --- a/client/app/mine/page.tsx +++ b/client/app/mine/page.tsx @@ -1,8 +1,13 @@ import { redirect } from "next/navigation" -import { getPostsByUser } from "@lib/server/prisma" +import { getPostsByUser, User } from "@lib/server/prisma" import PostList from "@components/post-list" import { getCurrentUser } from "@lib/server/session" import { authOptions } from "@lib/server/auth" +import { cache } from "react" + +const cachedGetPostsByUser = cache( + async (userId: User["id"]) => await getPostsByUser(userId, true) +) export default async function Mine() { const userId = (await getCurrentUser())?.id @@ -11,7 +16,7 @@ export default async function Mine() { return redirect(authOptions.pages?.signIn || "/new") } - const posts = await getPostsByUser(userId, true) + const posts = await cachedGetPostsByUser(userId) const hasMore = false const stringifiedPosts = JSON.stringify(posts) diff --git a/client/app/styles/globals.css b/client/app/styles/globals.css index 98229d1e..38c97d2b 100644 --- a/client/app/styles/globals.css +++ b/client/app/styles/globals.css @@ -78,11 +78,6 @@ box-sizing: border-box; } -// TODO: replace this with an accessible alternative -*:focus-visible { - outline: none !important; -} - ::selection { text-shadow: none; background: var(--fg) !important; diff --git a/client/lib/server/prisma.ts b/client/lib/server/prisma.ts index b8f598a5..3122fa6f 100644 --- a/client/lib/server/prisma.ts +++ b/client/lib/server/prisma.ts @@ -146,7 +146,7 @@ type GetPostByIdOptions = { export const getPostById = async ( postId: Post["id"], options?: GetPostByIdOptions -) => { +): Promise => { const post = await prisma.post.findUnique({ where: { id: postId @@ -164,26 +164,35 @@ export const getPostById = async ( } }) - return post as PostWithFiles + return post } export const getAllPosts = async ({ withFiles = false, + withAuthor = false, take = 100, ...rest }: { withFiles?: boolean -} & Prisma.PostFindManyArgs = {}) => { + withAuthor?: boolean +} & Prisma.PostFindManyArgs = {}): Promise< + Post[] | PostWithFiles[] | PostWithFilesAndAuthor[] +> => { const posts = await prisma.post.findMany({ include: { - files: withFiles + files: withFiles, + author: withAuthor }, // TODO: optimize which to grab take, ...rest }) - return posts as PostWithFiles[] + return posts as typeof withFiles extends true + ? typeof withAuthor extends true + ? PostWithFilesAndAuthor[] + : PostWithFiles[] + : Post[] } export type UserWithPosts = User & { diff --git a/client/next.config.mjs b/client/next.config.mjs index 77f6ab66..cc3e2d13 100644 --- a/client/next.config.mjs +++ b/client/next.config.mjs @@ -8,29 +8,7 @@ const nextConfig = { // esmExternals: true, appDir: true }, - webpack: (config, { dev, isServer }) => { - if (!dev && !isServer) { - // TODO: enabling Preact causes the file switcher to hang the browser process - // Object.assign(config.resolve.alias, { - // react: "preact/compat", - // "react-dom/test-utils": "preact/test-utils", - // "react-dom": "preact/compat" - // }) - } - return config - }, - async rewrites() { - return [ - { - source: "/file/raw/:id", - destination: `/api/file/raw/:id` - }, - { - source: "/home", - destination: "/", - } - ] - } + } export default bundleAnalyzer({ enabled: process.env.ANALYZE === "true" })( diff --git a/client/package.json b/client/package.json index 94475fab..22df3236 100644 --- a/client/package.json +++ b/client/package.json @@ -22,12 +22,12 @@ "@radix-ui/react-tabs": "^1.0.1", "@radix-ui/react-tooltip": "^1.0.2", "@wcj/markdown-to-html": "^2.1.2", - "@wits/next-themes": "^0.2.14", + "@wits/next-themes": "0.2.12", "bcrypt": "^5.1.0", "client-zip": "2.2.1", "jest": "^29.3.1", - "next": "13.0.6-canary.4", - "next-auth": "^4.17.0", + "next": "13.0.7-canary.1", + "next-auth": "^4.18.0", "prisma": "^4.6.1", "react": "18.2.0", "react-datepicker": "4.8.0", @@ -35,7 +35,7 @@ "react-dropzone": "14.2.3", "react-feather": "^2.0.10", "react-hot-toast": "^2.4.0", - "textarea-markdown-editor": "0.1.13", + "textarea-markdown-editor": "1.0.4", "ts-jest": "^29.0.3" }, "devDependencies": { @@ -47,6 +47,7 @@ "@types/react-dom": "18.0.3", "clsx": "^1.2.1", "cross-env": "7.0.3", + "csstype": "^3.1.1", "eslint": "8.27.0", "eslint-config-next": "13.0.3", "next-unused": "0.0.6", diff --git a/client/pages/api/admin/index.ts b/client/pages/api/admin/index.ts index a1475dca..812f4c41 100644 --- a/client/pages/api/admin/index.ts +++ b/client/pages/api/admin/index.ts @@ -1,5 +1,3 @@ -// ts nextjs api handler - import { withMethods } from "@lib/api-middleware/with-methods" import { parseQueryParam } from "@lib/server/parse-query-param" import { NextApiRequest, NextApiResponse } from "next" diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index dc564fcd..d3ca6ef3 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -16,16 +16,17 @@ specifiers: '@types/react-datepicker': 4.4.1 '@types/react-dom': 18.0.3 '@wcj/markdown-to-html': ^2.1.2 - '@wits/next-themes': ^0.2.14 + '@wits/next-themes': 0.2.12 bcrypt: ^5.1.0 client-zip: 2.2.1 clsx: ^1.2.1 cross-env: 7.0.3 + csstype: ^3.1.1 eslint: 8.27.0 eslint-config-next: 13.0.3 jest: ^29.3.1 - next: 13.0.6-canary.4 - next-auth: ^4.17.0 + next: 13.0.7-canary.1 + next-auth: ^4.18.0 next-unused: 0.0.6 prettier: 2.6.2 prisma: ^4.6.1 @@ -36,13 +37,13 @@ specifiers: react-feather: ^2.0.10 react-hot-toast: ^2.4.0 sharp: ^0.31.2 - textarea-markdown-editor: 0.1.13 + textarea-markdown-editor: 1.0.4 ts-jest: ^29.0.3 typescript: 4.6.4 typescript-plugin-css-modules: 3.4.0 dependencies: - '@next-auth/prisma-adapter': 1.0.5_o53gfpk3vz2btjrokqfjjwwn3m + '@next-auth/prisma-adapter': 1.0.5_qwexivae5olc6wqfcmxswm7qjy '@next/eslint-plugin-next': 13.0.5-canary.3 '@prisma/client': 4.6.1_prisma@4.6.1 '@radix-ui/react-dialog': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u @@ -51,20 +52,20 @@ dependencies: '@radix-ui/react-tabs': 1.0.1_biqbaboplfbrettd7655fr4n2y '@radix-ui/react-tooltip': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u '@wcj/markdown-to-html': 2.1.2 - '@wits/next-themes': 0.2.14_wq5w3t2dm6pp5l2gadmp4osgce + '@wits/next-themes': 0.2.12_ihvxcpofhpc4k2aqfys2drrlkq bcrypt: 5.1.0 client-zip: 2.2.1 jest: 29.3.1_@types+node@17.0.23 - next: 13.0.6-canary.4_biqbaboplfbrettd7655fr4n2y - next-auth: 4.17.0_wq5w3t2dm6pp5l2gadmp4osgce + next: 13.0.7-canary.1_biqbaboplfbrettd7655fr4n2y + next-auth: 4.18.0_ihvxcpofhpc4k2aqfys2drrlkq prisma: 4.6.1 react: 18.2.0 react-datepicker: 4.8.0_biqbaboplfbrettd7655fr4n2y react-dom: 18.2.0_react@18.2.0 react-dropzone: 14.2.3_react@18.2.0 react-feather: 2.0.10_react@18.2.0 - react-hot-toast: 2.4.0_biqbaboplfbrettd7655fr4n2y - textarea-markdown-editor: 0.1.13_biqbaboplfbrettd7655fr4n2y + react-hot-toast: 2.4.0_owo25xnefcwdq3zjgtohz6dbju + textarea-markdown-editor: 1.0.4_biqbaboplfbrettd7655fr4n2y ts-jest: 29.0.3_7hcmezpa7bajbjecov7p46z4aa optionalDependencies: @@ -79,6 +80,7 @@ devDependencies: '@types/react-dom': 18.0.3 clsx: 1.2.1 cross-env: 7.0.3 + csstype: 3.1.1 eslint: 8.27.0 eslint-config-next: 13.0.3_hsmo2rtalirsvadpuxki35bq2i next-unused: 0.0.6 @@ -783,14 +785,14 @@ packages: - supports-color dev: false - /@next-auth/prisma-adapter/1.0.5_o53gfpk3vz2btjrokqfjjwwn3m: + /@next-auth/prisma-adapter/1.0.5_qwexivae5olc6wqfcmxswm7qjy: resolution: {integrity: sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==} peerDependencies: '@prisma/client': '>=2.26.0 || >=3' next-auth: ^4 dependencies: '@prisma/client': 4.6.1_prisma@4.6.1 - next-auth: 4.17.0_wq5w3t2dm6pp5l2gadmp4osgce + next-auth: 4.18.0_ihvxcpofhpc4k2aqfys2drrlkq dev: false /@next/bundle-analyzer/12.1.6: @@ -802,8 +804,8 @@ packages: - utf-8-validate dev: true - /@next/env/13.0.6-canary.4: - resolution: {integrity: sha512-3SMpcpiHIPlUBdWOyf130lgiOZ56l1Jhhq1zbZbLa42No2czgLSo5R9X3dy21ozPNIzDMj7K5FwhhkoO0CQP+Q==} + /@next/env/13.0.7-canary.1: + resolution: {integrity: sha512-pjFCstWLbHpO3wAI4H6Jueiqb9s1IB++w8e79RJvry5K2ElzRpUPXeTkjI/oLSZ6W9DDG8DTOMmJtat2o+h3jA==} dev: false /@next/eslint-plugin-next/13.0.3: @@ -818,8 +820,8 @@ packages: glob: 7.1.7 dev: false - /@next/swc-android-arm-eabi/13.0.6-canary.4: - resolution: {integrity: sha512-FfjHzjjiJPHHwEGpgj8dO3Js3CMoO/emVb6XrzutZjbDI5kMvOLEFRiCKsAvcy5Do7nb1tkj1dc4wHE/ZgVGsQ==} + /@next/swc-android-arm-eabi/13.0.7-canary.1: + resolution: {integrity: sha512-bmUIfXap+EwEpkWqGso3fMScXpbbUHecFByjnnmWOXU21e1bhE7UfCDtXzEn3utwt8MlUwA/h/5CGf6wMFUU8w==} engines: {node: '>= 10'} cpu: [arm] os: [android] @@ -827,8 +829,8 @@ packages: dev: false optional: true - /@next/swc-android-arm64/13.0.6-canary.4: - resolution: {integrity: sha512-r01HaiYopunrkG6PiNljTkVulnmhYukJKQjOd+EsCRAq4aTN5geTQq4l1ZdaSJTVGeMmxa7SbtXQgZxH8KmhQQ==} + /@next/swc-android-arm64/13.0.7-canary.1: + resolution: {integrity: sha512-k0Wo/NgoAj1Bcp7X7fYc8C4G4Y+qiLrjqWGTQ38Cx5NHJfMJf6gUTfgc2OTBG96tKj21LwKhhg6BEqV9mRuzOg==} engines: {node: '>= 10'} cpu: [arm64] os: [android] @@ -836,8 +838,8 @@ packages: dev: false optional: true - /@next/swc-darwin-arm64/13.0.6-canary.4: - resolution: {integrity: sha512-sGBYihzRUIIlGfi70L9uWNRFgM1+d9KDa+tDp/goCBmzty6KExxet3DT2drOnGlGOKPV/+fooQCEAV6cqkMVkg==} + /@next/swc-darwin-arm64/13.0.7-canary.1: + resolution: {integrity: sha512-phrROUvPXYouNl4Bs7kRmkTcU18V2gxIbwiWonQYWROfCQJckELHM0MFOAfLbkJYRT/vcyp/o2bgiPkWv/fP8A==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -845,8 +847,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64/13.0.6-canary.4: - resolution: {integrity: sha512-KhkVYdLH288jje1cA8IZIvwPHhiuXsmXcDhUHNg437phhL+Vn52UD2WjvkPDAJCNgpsM23hTTHbxtHELjlXcNw==} + /@next/swc-darwin-x64/13.0.7-canary.1: + resolution: {integrity: sha512-ERpeI2zWlTj4xKdhwq8h9gyMWHSCh5UNm3ekX/MCgq1Mg1cLsv/kINeVQuvVP5II5HSHoEjnw2GvAMB4ayhcUA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -854,8 +856,8 @@ packages: dev: false optional: true - /@next/swc-freebsd-x64/13.0.6-canary.4: - resolution: {integrity: sha512-rOs64ugbLQd8pPjrVpaGZVZZ5xgn/plFNLdjFdldBS3E0/fwZZWBZgKsPf9ItrNgXPO4PKRk/KmoXBwZwGS3Ng==} + /@next/swc-freebsd-x64/13.0.7-canary.1: + resolution: {integrity: sha512-L/YIIzaoV58UHLgiR8jfr0V9HXmUvHf1a2+1esSsTlMXZ0Y3SzcczuLgEu0/AYKEgHcfl+vcng9FBeqXtVlYyQ==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] @@ -863,8 +865,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm-gnueabihf/13.0.6-canary.4: - resolution: {integrity: sha512-isuWwAJhXRJSlv0RCmrMdtlShGaDygQlPD0T6MzEdAG79b216FwZDu3yWzkny+bhxx/5E6w2SiJfjS9nuICLxw==} + /@next/swc-linux-arm-gnueabihf/13.0.7-canary.1: + resolution: {integrity: sha512-KVB7lAgtUgqrroqozYSCZIwVQITHhjbe99n/C6A9BYIAUtwITrLIn8Sj7D0a0sEhdDL8Y/rzXZGWMqL7f1Hg3A==} engines: {node: '>= 10'} cpu: [arm] os: [linux] @@ -872,8 +874,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu/13.0.6-canary.4: - resolution: {integrity: sha512-LYfHVdHjadJ3a+MiFhjhM6ou5Gx1hlGPN2xPshMTPY/KvESofPiBg1m3bJhjNaaNheSmQDJJ1QhVWM+EMBEZNA==} + /@next/swc-linux-arm64-gnu/13.0.7-canary.1: + resolution: {integrity: sha512-tA4yYk1+2fPgs0q6r94d7sKQosf9jZGTMXIS0yOykk246L3+npsDqyBrdCusaJv9q3Fm5S8lfwp4vqoLNtcFLg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -881,8 +883,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl/13.0.6-canary.4: - resolution: {integrity: sha512-Kvqw+9p0/amAHN7Q3LoZ0GJbyBYHUJm5C23ojihsz0UHemO057q+MwgwZbmz6ufwmOrUCfoq3RQo4C5vUzeNCw==} + /@next/swc-linux-arm64-musl/13.0.7-canary.1: + resolution: {integrity: sha512-KKN/nd2g2Cixs+av1mLeiNvhm+8T8ZiuzZHRrA4h4OWwreI+weS0iXBa1sBGvNp863MxE1mxKOv2xFhSbWK/CQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -890,8 +892,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu/13.0.6-canary.4: - resolution: {integrity: sha512-LisCGX5oUXPrc43tyc/rsv3rKI2Yxqd5Eq7LsXI3TWIjH5xLeRi1LWxGxEc3asAI28dKBjgizyUzGbPwBE2KdQ==} + /@next/swc-linux-x64-gnu/13.0.7-canary.1: + resolution: {integrity: sha512-1crxMrvO2pHmsDxSkVknchiyLHYpkKKkwhnrFYKP06bZSEONAry6VTYJ6l73PK9mp1kzFAtke5k9yG4LG0fbAQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -899,8 +901,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl/13.0.6-canary.4: - resolution: {integrity: sha512-rXRilFn8Tqop6G7c7CluIgXGhyr1k4aqEJBir4mlHWdtcW3JMolWZCJ/agXSQ7+JyS1LROI93XN0LKRu9vapIw==} + /@next/swc-linux-x64-musl/13.0.7-canary.1: + resolution: {integrity: sha512-1incysWrn+PEK6XRE1QnK2UI7//N6gfmeaFC1KIlRyt0JmyF8U3V+I6Qcar9nHz9hY9e8yszFQY0A9X0jsfkUQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -908,8 +910,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc/13.0.6-canary.4: - resolution: {integrity: sha512-LVlG39FDY/VOhRs5gKvSDUjgPpdWyDyGQjROFP+WGxGgKd+NZU7dk4sXkAo71jOZbmqT+EcGI69dJMK5gDO12Q==} + /@next/swc-win32-arm64-msvc/13.0.7-canary.1: + resolution: {integrity: sha512-AE5NYCeXilQnzIOma7y3cNcYVQsHJsEZ3r4/DTKvmFvuFVBkxza7Uxzi5rwD67ewSbOzir1xr+LBtI6vCmQ/Fw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -917,8 +919,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc/13.0.6-canary.4: - resolution: {integrity: sha512-XWpmQngO9Z5kTnUum4dnXg1jOP2H36qOEofvNj87BtlUJFxAbMzrs9Zbnix+1CmGbBg88mNTuaH1LAYupc+utQ==} + /@next/swc-win32-ia32-msvc/13.0.7-canary.1: + resolution: {integrity: sha512-kG84cAm/FZsK3u2vgcUpQRT28NEA+vMTMrp4ufdHPu+c0o0aEcLqh3yQstWqw+hGpYQxiB0EF95K9bbRfHkgOQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -926,8 +928,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc/13.0.6-canary.4: - resolution: {integrity: sha512-E6FnI9m40oQAYgcwsY7i9HLHSCo0Lh6m+TzXUEdMkjl7WVpo96dtvEVt4/8wDnVPA+x/PkM6Qo8MKooZmCeeZw==} + /@next/swc-win32-x64-msvc/13.0.7-canary.1: + resolution: {integrity: sha512-FrEMvjaPJ3g2BcQp0aovr4Jj5L/KnvWlnvw5fIPMMoDmUYuMkbR4ZbAvIrOaLGCRiO0862kcoCcdhZ75AwzU2g==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1709,14 +1711,14 @@ packages: - supports-color dev: false - /@wits/next-themes/0.2.14_wq5w3t2dm6pp5l2gadmp4osgce: - resolution: {integrity: sha512-fHKb/tRcWbYNblGHZtfvAQztDhzUB9d7ZkYOny0BisSPh6EABcsqxKB48ABUQztcmKywlp2zEMkLcSRj/PQBSw==} + /@wits/next-themes/0.2.12_ihvxcpofhpc4k2aqfys2drrlkq: + resolution: {integrity: sha512-P/qtLW68n4xBLT8UfLkCD/0jmF0yWxdf3xpGCDbfR6WuvK2brJDQ0DhPbhsucGkJ42ArA6ItKqcIo7/cnKzhGg==} peerDependencies: next: '*' react: '*' react-dom: '*' dependencies: - next: 13.0.6-canary.4_biqbaboplfbrettd7655fr4n2y + next: 13.0.7-canary.1_biqbaboplfbrettd7655fr4n2y react: 18.2.0 react-dom: 18.2.0_react@18.2.0 dev: false @@ -3423,10 +3425,12 @@ packages: minimist: 1.2.7 dev: true - /goober/2.1.11: + /goober/2.1.11_csstype@3.1.1: resolution: {integrity: sha512-5SS2lmxbhqH0u9ABEWq7WPU69a4i2pYcHeCxqaNq6Cw3mnrF0ghWNM4tEGid4dKy8XNIAUbuThuozDHHKJVh3A==} peerDependencies: csstype: ^3.0.10 + dependencies: + csstype: 3.1.1 dev: false /graceful-fs/4.2.10: @@ -5198,8 +5202,8 @@ packages: dev: true optional: true - /next-auth/4.17.0_wq5w3t2dm6pp5l2gadmp4osgce: - resolution: {integrity: sha512-aN2tdnjS0MDeUpB2tBDOaWnegkgeMWrsccujbXRGMJ607b+EwRcy63MFGSr0OAboDJEe0902piXQkt94GqF8Qw==} + /next-auth/4.18.0_ihvxcpofhpc4k2aqfys2drrlkq: + resolution: {integrity: sha512-lqJtusYqUwDiwzO4+B+lx/vKCuf/akcdhxT5R47JmS5gvI9O6Y4CZYc8coysY7XaMGHCxfttvTSEw76RA8gNTg==} engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0} peerDependencies: next: ^12.2.5 || ^13 @@ -5214,7 +5218,7 @@ packages: '@panva/hkdf': 1.0.2 cookie: 0.5.0 jose: 4.11.0 - next: 13.0.6-canary.4_biqbaboplfbrettd7655fr4n2y + next: 13.0.7-canary.1_biqbaboplfbrettd7655fr4n2y oauth: 0.9.15 openid-client: 5.3.0 preact: 10.11.2 @@ -5235,8 +5239,8 @@ packages: - supports-color dev: true - /next/13.0.6-canary.4_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-41koM6+K/ywM2N5beni1x/vP2huDVTBbHikP46eLAmJK7KU+CHwoyblWDjqeg0c6PAkoAHybk5LjYbfjiFiBCw==} + /next/13.0.7-canary.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-xfLT5Ikty2zcFCYSsYaQta3Dik09BJmwwj5a3i/ceh+51rJ+I3lP9+BbB9dUCUmgftOgxyyFUkzIZJ/gi3fUiQ==} engines: {node: '>=14.6.0'} hasBin: true peerDependencies: @@ -5253,7 +5257,7 @@ packages: sass: optional: true dependencies: - '@next/env': 13.0.6-canary.4 + '@next/env': 13.0.7-canary.1 '@swc/helpers': 0.4.14 caniuse-lite: 1.0.30001431 postcss: 8.4.14 @@ -5261,19 +5265,19 @@ packages: react-dom: 18.2.0_react@18.2.0 styled-jsx: 5.1.0_react@18.2.0 optionalDependencies: - '@next/swc-android-arm-eabi': 13.0.6-canary.4 - '@next/swc-android-arm64': 13.0.6-canary.4 - '@next/swc-darwin-arm64': 13.0.6-canary.4 - '@next/swc-darwin-x64': 13.0.6-canary.4 - '@next/swc-freebsd-x64': 13.0.6-canary.4 - '@next/swc-linux-arm-gnueabihf': 13.0.6-canary.4 - '@next/swc-linux-arm64-gnu': 13.0.6-canary.4 - '@next/swc-linux-arm64-musl': 13.0.6-canary.4 - '@next/swc-linux-x64-gnu': 13.0.6-canary.4 - '@next/swc-linux-x64-musl': 13.0.6-canary.4 - '@next/swc-win32-arm64-msvc': 13.0.6-canary.4 - '@next/swc-win32-ia32-msvc': 13.0.6-canary.4 - '@next/swc-win32-x64-msvc': 13.0.6-canary.4 + '@next/swc-android-arm-eabi': 13.0.7-canary.1 + '@next/swc-android-arm64': 13.0.7-canary.1 + '@next/swc-darwin-arm64': 13.0.7-canary.1 + '@next/swc-darwin-x64': 13.0.7-canary.1 + '@next/swc-freebsd-x64': 13.0.7-canary.1 + '@next/swc-linux-arm-gnueabihf': 13.0.7-canary.1 + '@next/swc-linux-arm64-gnu': 13.0.7-canary.1 + '@next/swc-linux-arm64-musl': 13.0.7-canary.1 + '@next/swc-linux-x64-gnu': 13.0.7-canary.1 + '@next/swc-linux-x64-musl': 13.0.7-canary.1 + '@next/swc-win32-arm64-msvc': 13.0.7-canary.1 + '@next/swc-win32-ia32-msvc': 13.0.7-canary.1 + '@next/swc-win32-x64-msvc': 13.0.7-canary.1 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -5926,14 +5930,14 @@ packages: react: 18.2.0 dev: false - /react-hot-toast/2.4.0_biqbaboplfbrettd7655fr4n2y: + /react-hot-toast/2.4.0_owo25xnefcwdq3zjgtohz6dbju: resolution: {integrity: sha512-qnnVbXropKuwUpriVVosgo8QrB+IaPJCpL8oBI6Ov84uvHZ5QQcTp2qg6ku2wNfgJl6rlQXJIQU5q+5lmPOutA==} engines: {node: '>=10'} peerDependencies: react: '>=16' react-dom: '>=16' dependencies: - goober: 2.1.11 + goober: 2.1.11_csstype@3.1.1 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 transitivePeerDependencies: @@ -6022,10 +6026,6 @@ packages: tslib: 2.4.1 dev: false - /react-trigger-change/1.0.2: - resolution: {integrity: sha512-3x2i/CTQZZlzTuDIfOrJ12r1IqcNWLxHVHXKTtlb4L0slt6Zy3YF3smimx4kdX4X/ASSuQnI/n1q4cTqDwWkPA==} - dev: false - /react/18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -6732,16 +6732,15 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true - /textarea-markdown-editor/0.1.13_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-2r1gTPFA/wwAzt+Aa6LVZWjJNvL0aXfR6Z9T6eQBpJ1AK6gtPVCZgkO97KIrqpAmMcwgNCz0ToYj2AqPufdVeg==} + /textarea-markdown-editor/1.0.4_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-4uA8EZ0FkIL0dq89+xiA0BEo832/rKdtoi2T4Wab0wLZfHys82JE1i5YJf8BKAr/IQELF2NxQ5LITYkb8BGIFA==} peerDependencies: - react: ^16.9.0 || ^17.0 - react-dom: ^16.9.0 || ^17.0 + react: ^16.8.0 || ^17.0.0 || || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: mousetrap: 1.6.5 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - react-trigger-change: 1.0.2 dev: false /tmpl/1.0.5: