add basic admin page, misc fixes
This commit is contained in:
parent
9b593c849e
commit
56eefc8419
32 changed files with 552 additions and 322 deletions
|
@ -5,12 +5,17 @@ import type { File } from "lib/server/prisma"
|
||||||
import styles from "./dropdown.module.css"
|
import styles from "./dropdown.module.css"
|
||||||
import buttonStyles from "@components/button/button.module.css"
|
import buttonStyles from "@components/button/button.module.css"
|
||||||
import { ChevronDown, Code, File as FileIcon } from "react-feather"
|
import { ChevronDown, Code, File as FileIcon } from "react-feather"
|
||||||
|
import { Spinner } from "@components/spinner"
|
||||||
|
|
||||||
type Item = File & {
|
type Item = File & {
|
||||||
icon: JSX.Element
|
icon: JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileDropdown = ({ files }: { files: File[] }) => {
|
const FileDropdown = ({ files, loading }: { files: File[], loading?: boolean }) => {
|
||||||
|
if (loading) {
|
||||||
|
return <Spinner />
|
||||||
|
}
|
||||||
|
|
||||||
const items = files.map((file) => {
|
const items = files.map((file) => {
|
||||||
const extension = file.title.split(".").pop()
|
const extension = file.title.split(".").pop()
|
||||||
if (codeFileExtensions.includes(extension || "")) {
|
if (codeFileExtensions.includes(extension || "")) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Document } from "../new"
|
import type { Document } from "../new"
|
||||||
import DocumentComponent from "./edit-document"
|
import DocumentComponent from "./edit-document"
|
||||||
import { ChangeEvent, memo, useCallback } from "react"
|
import { ChangeEvent, useCallback } from "react"
|
||||||
|
|
||||||
const DocumentList = ({
|
const DocumentList = ({
|
||||||
docs,
|
docs,
|
||||||
|
|
|
@ -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 (
|
||||||
|
<span className={styles.buttons}>
|
||||||
|
<ButtonGroup verticalIfMobile>
|
||||||
|
<Button
|
||||||
|
iconLeft={<Edit />}
|
||||||
|
onClick={editACopy}
|
||||||
|
style={{ textTransform: "none" }}
|
||||||
|
>
|
||||||
|
Edit a Copy
|
||||||
|
</Button>
|
||||||
|
{viewParentClick && (
|
||||||
|
<Button iconLeft={<ArrowUpCircle />} onClick={viewParentClick}>
|
||||||
|
View Parent
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={downloadClick}
|
||||||
|
iconLeft={<Archive />}
|
||||||
|
style={{ textTransform: "none" }}
|
||||||
|
>
|
||||||
|
Download as ZIP Archive
|
||||||
|
</Button>
|
||||||
|
<FileDropdown loading={loading} files={files || []} />
|
||||||
|
</ButtonGroup>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 (
|
||||||
|
<span className={styles.title}>
|
||||||
|
<h3>
|
||||||
|
{title}{" "}
|
||||||
|
<span style={{ color: "var(--gray)" }}>
|
||||||
|
by {displayName || "anonymous"}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
{!loading && (
|
||||||
|
<span className={styles.badges}>
|
||||||
|
{visibility && <VisibilityBadge visibility={visibility} />}
|
||||||
|
{createdAt && <CreatedAgoBadge createdAt={createdAt} />}
|
||||||
|
{expiresAt && <ExpirationBadge postExpirationDate={expiresAt} />}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{loading && (
|
||||||
|
<span className={styles.badges}>
|
||||||
|
<Skeleton width={100} height={20} />
|
||||||
|
<Skeleton width={100} height={20} />
|
||||||
|
<Skeleton width={100} height={20} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,11 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import VisibilityBadge from "@components/badges/visibility-badge"
|
|
||||||
import DocumentComponent from "./view-document"
|
import DocumentComponent from "./view-document"
|
||||||
import styles from "./post-page.module.css"
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
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 { 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 PasswordModalPage from "./password-modal-wrapper"
|
||||||
import VisibilityControl from "@components/badges/visibility-control"
|
|
||||||
import { File, PostWithFilesAndAuthor } from "@lib/server/prisma"
|
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 = {
|
type Props = {
|
||||||
post: string | PostWithFilesAndAuthor
|
post: string | PostWithFilesAndAuthor
|
||||||
|
@ -28,7 +18,6 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => {
|
||||||
typeof initialPost === "string" ? JSON.parse(initialPost) : initialPost
|
typeof initialPost === "string" ? JSON.parse(initialPost) : initialPost
|
||||||
)
|
)
|
||||||
|
|
||||||
const [visibility, setVisibility] = useState<string>(post.visibility)
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -68,80 +57,8 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => {
|
||||||
return <PasswordModalPage setPost={setPost} postId={post.id} />
|
return <PasswordModalPage setPost={setPost} postId={post.id} />
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.header}>
|
|
||||||
<span className={styles.buttons}>
|
|
||||||
<ButtonGroup verticalIfMobile>
|
|
||||||
<Button
|
|
||||||
iconLeft={<Edit />}
|
|
||||||
onClick={editACopy}
|
|
||||||
style={{ textTransform: "none" }}
|
|
||||||
>
|
|
||||||
Edit a Copy
|
|
||||||
</Button>
|
|
||||||
{post.parentId && (
|
|
||||||
<Button iconLeft={<ArrowUpCircle />} onClick={viewParentClick}>
|
|
||||||
View Parent
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
onClick={download}
|
|
||||||
iconLeft={<Archive />}
|
|
||||||
style={{ textTransform: "none" }}
|
|
||||||
>
|
|
||||||
Download as ZIP Archive
|
|
||||||
</Button>
|
|
||||||
<FileDropdown files={post.files || []} />
|
|
||||||
</ButtonGroup>
|
|
||||||
</span>
|
|
||||||
<span className={styles.title}>
|
|
||||||
<h3>
|
|
||||||
{post.title}{" "}
|
|
||||||
<span style={{ color: "var(--gray)" }}>
|
|
||||||
by {post.author?.displayName}
|
|
||||||
</span>
|
|
||||||
</h3>
|
|
||||||
<span className={styles.badges}>
|
|
||||||
<VisibilityBadge visibility={visibility} />
|
|
||||||
<CreatedAgoBadge createdAt={post.createdAt} />
|
|
||||||
<ExpirationBadge postExpirationDate={post.expiresAt} />
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{post.description && (
|
|
||||||
<div>
|
|
||||||
<p>{post.description}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* {post.files.length > 1 && <FileTree files={post.files} />} */}
|
|
||||||
{post.files?.map(({ id, content, title, html }: File) => (
|
{post.files?.map(({ id, content, title, html }: File) => (
|
||||||
<DocumentComponent
|
<DocumentComponent
|
||||||
key={id}
|
key={id}
|
||||||
|
@ -152,16 +69,7 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => {
|
||||||
preview={html}
|
preview={html}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{isAuthor && (
|
|
||||||
<span className={styles.controls}>
|
|
||||||
<VisibilityControl
|
|
||||||
postId={post.id}
|
|
||||||
visibility={visibility}
|
|
||||||
setVisibility={setVisibility}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<ScrollToTop />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
import { Post, PostWithFilesAndAuthor } from "@lib/server/prisma"
|
import { Post, PostWithFilesAndAuthor } from "@lib/server/prisma"
|
||||||
import PasswordModal from "@components/password-modal"
|
import PasswordModal from "@components/password-modal"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
14
client/app/(posts)/post/[id]/loading.tsx
Normal file
14
client/app/(posts)/post/[id]/loading.tsx
Normal file
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<PostButtons loading title="" />
|
||||||
|
<PostTitle title="" loading />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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 { 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 { 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 = {
|
export type PostProps = {
|
||||||
post: Post
|
post: Post
|
||||||
isProtected?: boolean
|
isProtected?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
// export async function generateStaticParams() {
|
||||||
const posts = await getAllPosts({
|
// const posts = await getAllPosts({
|
||||||
where: {
|
// where: {
|
||||||
visibility: {
|
// visibility: {
|
||||||
equals: "public"
|
// equals: "public"
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
|
|
||||||
return posts.map((post) => ({
|
// return posts.map((post) => ({
|
||||||
id: post.id
|
// id: post.id
|
||||||
}))
|
// }))
|
||||||
|
// }
|
||||||
|
|
||||||
|
const fetchOptions = {
|
||||||
|
withFiles: true,
|
||||||
|
withAuthor: true
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPost = async (id: string) => {
|
const getPost = async (id: string) => {
|
||||||
const post = await getPostById(id, {
|
const post = (await getPostById(id, fetchOptions)) as PostWithFilesAndAuthor
|
||||||
withFiles: true,
|
|
||||||
withAuthor: true
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!post) {
|
if (!post) {
|
||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post.visibility === "public") {
|
|
||||||
return { post }
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await getCurrentUser()
|
const user = await getCurrentUser()
|
||||||
const isAuthorOrAdmin = user?.id === post?.authorId || user?.role === "admin"
|
const isAuthorOrAdmin = user?.id === post?.authorId || user?.role === "admin"
|
||||||
|
|
||||||
|
@ -55,7 +59,15 @@ const getPost = async (id: string) => {
|
||||||
return {
|
return {
|
||||||
post: {
|
post: {
|
||||||
visibility: "protected",
|
visibility: "protected",
|
||||||
id: post.id
|
id: post.id,
|
||||||
|
files: [],
|
||||||
|
parentId: "",
|
||||||
|
title: "",
|
||||||
|
createdAt: new Date("1970-01-01"),
|
||||||
|
author: {
|
||||||
|
displayName: ""
|
||||||
|
},
|
||||||
|
description: ""
|
||||||
},
|
},
|
||||||
isProtected: true,
|
isProtected: true,
|
||||||
isAuthor: isAuthorOrAdmin
|
isAuthor: isAuthorOrAdmin
|
||||||
|
@ -83,12 +95,40 @@ const PostView = async ({
|
||||||
const { post, isProtected, isAuthor } = await getPost(params.id)
|
const { post, isProtected, isAuthor } = await getPost(params.id)
|
||||||
// TODO: serialize dates in prisma middleware instead of passing as JSON
|
// TODO: serialize dates in prisma middleware instead of passing as JSON
|
||||||
const stringifiedPost = JSON.stringify(post)
|
const stringifiedPost = JSON.stringify(post)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PostPage
|
<>
|
||||||
isAuthor={isAuthor}
|
<div className={styles.header}>
|
||||||
isProtected={isProtected}
|
<PostButtons
|
||||||
post={stringifiedPost}
|
parentId={post.parentId || undefined}
|
||||||
/>
|
postId={post.id}
|
||||||
|
files={post.files}
|
||||||
|
title={title}
|
||||||
|
/>
|
||||||
|
<PostTitle
|
||||||
|
title={post.title}
|
||||||
|
createdAt={post.createdAt}
|
||||||
|
displayName={post.author?.displayName || ""}
|
||||||
|
visibility={post.visibility}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{post.description && (
|
||||||
|
<div>
|
||||||
|
<p>{post.description}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<PostPage
|
||||||
|
isAuthor={isAuthor}
|
||||||
|
isProtected={isProtected}
|
||||||
|
post={stringifiedPost}
|
||||||
|
/>
|
||||||
|
{isAuthor && (
|
||||||
|
<span className={styles.controls}>
|
||||||
|
<VisibilityControl postId={post.id} visibility={post.visibility} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<ScrollToTop />
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
client/app/(posts)/post/[id]/styles.module.css
Normal file
17
client/app/(posts)/post/[id]/styles.module.css
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
9
client/app/admin/admin.module.css
Normal file
9
client/app/admin/admin.module.css
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
17
client/app/admin/layout.tsx
Normal file
17
client/app/admin/layout.tsx
Normal file
|
@ -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
|
||||||
|
}
|
13
client/app/admin/loading.tsx
Normal file
13
client/app/admin/loading.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { PostTable, UserTable } from "./page"
|
||||||
|
|
||||||
|
export default function AdminLoading() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Admin</h1>
|
||||||
|
<h2>Users</h2>
|
||||||
|
<UserTable />
|
||||||
|
<h2>Posts</h2>
|
||||||
|
<PostTable />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
96
client/app/admin/page.tsx
Normal file
96
client/app/admin/page.tsx
Normal file
|
@ -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<ReturnType<typeof getAllUsers>>
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<table className={styles.table}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>User ID</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{users?.map((user) => (
|
||||||
|
<tr key={user.id}>
|
||||||
|
<td>{user.name ? user.name : "no name"}</td>
|
||||||
|
<td>{user.email}</td>
|
||||||
|
<td>{user.role}</td>
|
||||||
|
<td className={styles.id}>{user.id}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{!users && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={4}>
|
||||||
|
<Spinner />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PostTable({
|
||||||
|
posts
|
||||||
|
}: {
|
||||||
|
posts?: Awaited<ReturnType<typeof getAllPosts>>
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<table className={styles.table}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Author</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Visibility</th>
|
||||||
|
<th className={styles.id}>Post ID</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{posts?.map((post) => (
|
||||||
|
<tr key={post.id}>
|
||||||
|
<td><a href={`/post/${post.id}`} target="_blank" rel="noreferrer">{post.title}</a></td>
|
||||||
|
<td>{"author" in post ? post.author.name : "no author"}</td>
|
||||||
|
<td>{post.createdAt.toLocaleDateString()}</td>
|
||||||
|
<td>{post.visibility}</td>
|
||||||
|
<td>{post.id}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{!posts && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={5}>
|
||||||
|
<Spinner />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function AdminPage() {
|
||||||
|
const usersPromise = getAllUsers()
|
||||||
|
const postsPromise = getAllPosts({
|
||||||
|
withAuthor: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const [users, posts] = await Promise.all([usersPromise, postsPromise])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<h1>Admin</h1>
|
||||||
|
<h2>Users</h2>
|
||||||
|
<UserTable users={users} />
|
||||||
|
<h2>Posts</h2>
|
||||||
|
<PostTable posts={posts} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
'use client'
|
'use client'
|
||||||
// import Tooltip from "@components/tooltip"
|
import Tooltip from "@components/tooltip"
|
||||||
import { timeAgo } from "@lib/time-ago"
|
import { timeAgo } from "@lib/time-ago"
|
||||||
import { useMemo, useState, useEffect } from "react"
|
import { useMemo, useState, useEffect } from "react"
|
||||||
import Badge from "../badge"
|
import Badge from "../badge"
|
||||||
|
@ -15,15 +15,15 @@ const CreatedAgoBadge = ({ createdAt }: { createdAt: string | Date }) => {
|
||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [createdDate])
|
}, [createdDate])
|
||||||
|
|
||||||
// const formattedTime = `${createdDate.toLocaleDateString()} ${createdDate.toLocaleTimeString()}`
|
const formattedTime = `${createdDate.toLocaleDateString()} ${createdDate.toLocaleTimeString()}`
|
||||||
return (
|
return (
|
||||||
// TODO: investigate tooltip
|
// TODO: investigate tooltip not showing
|
||||||
// <Tooltip content={formattedTime}>
|
<Tooltip content={formattedTime}>
|
||||||
<Badge type="secondary">
|
<Badge type="secondary">
|
||||||
{" "}
|
{" "}
|
||||||
<>{time}</>
|
<>{time}</>
|
||||||
</Badge>
|
</Badge>
|
||||||
// </Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
import Tooltip from "@components/tooltip"
|
import Tooltip from "@components/tooltip"
|
||||||
import { timeUntil } from "@lib/time-ago"
|
import { timeUntil } from "@lib/time-ago"
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
|
@ -5,8 +7,7 @@ import Badge from "../badge"
|
||||||
|
|
||||||
const ExpirationBadge = ({
|
const ExpirationBadge = ({
|
||||||
postExpirationDate
|
postExpirationDate
|
||||||
}: // onExpires
|
}: {
|
||||||
{
|
|
||||||
postExpirationDate: Date | string | null
|
postExpirationDate: Date | string | null
|
||||||
onExpires?: () => void
|
onExpires?: () => void
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -39,15 +40,6 @@ const ExpirationBadge = ({
|
||||||
return timeUntilString === "in 0 seconds"
|
return timeUntilString === "in 0 seconds"
|
||||||
}, [timeUntilString])
|
}, [timeUntilString])
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// // check if expired every
|
|
||||||
// if (isExpired) {
|
|
||||||
// if (onExpires) {
|
|
||||||
// onExpires();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }, [isExpired, onExpires])
|
|
||||||
|
|
||||||
if (!expirationDate) {
|
if (!expirationDate) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
import PasswordModal from "@components/password-modal"
|
import PasswordModal from "@components/password-modal"
|
||||||
import { useCallback, useState } from "react"
|
import { useCallback, useState } from "react"
|
||||||
import ButtonGroup from "@components/button-group"
|
import ButtonGroup from "@components/button-group"
|
||||||
|
@ -8,10 +10,11 @@ import { Spinner } from "@components/spinner"
|
||||||
type Props = {
|
type Props = {
|
||||||
postId: string
|
postId: string
|
||||||
visibility: string
|
visibility: string
|
||||||
setVisibility: (visibility: string) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const VisibilityControl = ({ postId, visibility, setVisibility }: Props) => {
|
const VisibilityControl = ({ postId, visibility: postVisibility }: Props) => {
|
||||||
|
const [visibility, setVisibility] = useState<string>(postVisibility)
|
||||||
|
|
||||||
const [isSubmitting, setSubmitting] = useState<string | null>()
|
const [isSubmitting, setSubmitting] = useState<string | null>()
|
||||||
const [passwordModalVisible, setPasswordModalVisible] = useState(false)
|
const [passwordModalVisible, setPasswordModalVisible] = useState(false)
|
||||||
const { setToast } = useToasts()
|
const { setToast } = useToasts()
|
||||||
|
|
|
@ -36,6 +36,16 @@
|
||||||
width: min-content;
|
width: min-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header:not(.loading) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.selectContent {
|
.selectContent {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
|
|
@ -34,8 +34,10 @@ type Tab = {
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const session = useSession()
|
const session = useSession()
|
||||||
const signedIn = session?.status === "authenticated"
|
const isSignedIn = session?.status === "authenticated"
|
||||||
const isAdmin = session?.data?.user?.role === "admin"
|
const isAdmin = session?.data?.user?.role === "admin"
|
||||||
|
const isLoading = session?.status === "loading"
|
||||||
|
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const { setTheme, resolvedTheme } = useTheme()
|
const { setTheme, resolvedTheme } = useTheme()
|
||||||
|
|
||||||
|
@ -98,7 +100,7 @@ const Header = () => {
|
||||||
value: "theme"
|
value: "theme"
|
||||||
})
|
})
|
||||||
|
|
||||||
if (signedIn)
|
if (isSignedIn)
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: "New",
|
name: "New",
|
||||||
|
@ -151,7 +153,7 @@ const Header = () => {
|
||||||
href: "/signup"
|
href: "/signup"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, [isAdmin, resolvedTheme, signedIn, setTheme])
|
}, [isAdmin, resolvedTheme, isSignedIn, setTheme])
|
||||||
|
|
||||||
// // TODO: this should not be necessary.
|
// // TODO: this should not be necessary.
|
||||||
// if (!clientHydrated) {
|
// if (!clientHydrated) {
|
||||||
|
@ -165,7 +167,9 @@ const Header = () => {
|
||||||
const buttons = pages.map(getButton)
|
const buttons = pages.map(getButton)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header>
|
<header className={clsx(styles.header, {
|
||||||
|
[styles.loading]: isLoading,
|
||||||
|
})}>
|
||||||
<div className={styles.tabs}>
|
<div className={styles.tabs}>
|
||||||
<div className={styles.buttons}>{buttons}</div>
|
<div className={styles.buttons}>{buttons}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -45,7 +45,6 @@ const PasswordModal = ({
|
||||||
if (!open) onClose()
|
if (!open) onClose()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* <Dialog.Trigger asChild>Enter a password</Dialog.Trigger> */}
|
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<Dialog.Overlay className={styles.overlay} />
|
<Dialog.Overlay className={styles.overlay} />
|
||||||
<Dialog.Content
|
<Dialog.Content
|
||||||
|
@ -85,10 +84,8 @@ const PasswordModal = ({
|
||||||
</Note>
|
</Note>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<Dialog.Close className={styles.close}>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
<Button onClick={onSubmit}>Submit</Button>
|
<Button onClick={onSubmit}>Submit</Button>
|
||||||
<Button>Cancel</Button>
|
|
||||||
</Dialog.Close>
|
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Portal>
|
</Dialog.Portal>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
gap: var(--gap-half);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes overlayShow {
|
@keyframes overlayShow {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
import Button from "@components/button"
|
import Button from "@components/button"
|
||||||
import Tooltip from "@components/tooltip"
|
import Tooltip from "@components/tooltip"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import "@styles/globals.css"
|
import "@styles/globals.css"
|
||||||
import { LayoutWrapper } from "./root-layout-wrapper"
|
import { LayoutWrapper } from "./root-layout-wrapper"
|
||||||
import { getSession } from "@lib/server/session"
|
|
||||||
import { ServerThemeProvider } from "@wits/next-themes"
|
import { ServerThemeProvider } from "@wits/next-themes"
|
||||||
|
|
||||||
interface RootLayoutProps {
|
interface RootLayoutProps {
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import { redirect } from "next/navigation"
|
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 PostList from "@components/post-list"
|
||||||
import { getCurrentUser } from "@lib/server/session"
|
import { getCurrentUser } from "@lib/server/session"
|
||||||
import { authOptions } from "@lib/server/auth"
|
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() {
|
export default async function Mine() {
|
||||||
const userId = (await getCurrentUser())?.id
|
const userId = (await getCurrentUser())?.id
|
||||||
|
@ -11,7 +16,7 @@ export default async function Mine() {
|
||||||
return redirect(authOptions.pages?.signIn || "/new")
|
return redirect(authOptions.pages?.signIn || "/new")
|
||||||
}
|
}
|
||||||
|
|
||||||
const posts = await getPostsByUser(userId, true)
|
const posts = await cachedGetPostsByUser(userId)
|
||||||
|
|
||||||
const hasMore = false
|
const hasMore = false
|
||||||
const stringifiedPosts = JSON.stringify(posts)
|
const stringifiedPosts = JSON.stringify(posts)
|
||||||
|
|
|
@ -78,11 +78,6 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace this with an accessible alternative
|
|
||||||
*:focus-visible {
|
|
||||||
outline: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
background: var(--fg) !important;
|
background: var(--fg) !important;
|
||||||
|
|
|
@ -146,7 +146,7 @@ type GetPostByIdOptions = {
|
||||||
export const getPostById = async (
|
export const getPostById = async (
|
||||||
postId: Post["id"],
|
postId: Post["id"],
|
||||||
options?: GetPostByIdOptions
|
options?: GetPostByIdOptions
|
||||||
) => {
|
): Promise<Post | PostWithFiles | PostWithFilesAndAuthor | null> => {
|
||||||
const post = await prisma.post.findUnique({
|
const post = await prisma.post.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: postId
|
id: postId
|
||||||
|
@ -164,26 +164,35 @@ export const getPostById = async (
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return post as PostWithFiles
|
return post
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAllPosts = async ({
|
export const getAllPosts = async ({
|
||||||
withFiles = false,
|
withFiles = false,
|
||||||
|
withAuthor = false,
|
||||||
take = 100,
|
take = 100,
|
||||||
...rest
|
...rest
|
||||||
}: {
|
}: {
|
||||||
withFiles?: boolean
|
withFiles?: boolean
|
||||||
} & Prisma.PostFindManyArgs = {}) => {
|
withAuthor?: boolean
|
||||||
|
} & Prisma.PostFindManyArgs = {}): Promise<
|
||||||
|
Post[] | PostWithFiles[] | PostWithFilesAndAuthor[]
|
||||||
|
> => {
|
||||||
const posts = await prisma.post.findMany({
|
const posts = await prisma.post.findMany({
|
||||||
include: {
|
include: {
|
||||||
files: withFiles
|
files: withFiles,
|
||||||
|
author: withAuthor
|
||||||
},
|
},
|
||||||
// TODO: optimize which to grab
|
// TODO: optimize which to grab
|
||||||
take,
|
take,
|
||||||
...rest
|
...rest
|
||||||
})
|
})
|
||||||
|
|
||||||
return posts as PostWithFiles[]
|
return posts as typeof withFiles extends true
|
||||||
|
? typeof withAuthor extends true
|
||||||
|
? PostWithFilesAndAuthor[]
|
||||||
|
: PostWithFiles[]
|
||||||
|
: Post[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserWithPosts = User & {
|
export type UserWithPosts = User & {
|
||||||
|
|
|
@ -8,29 +8,7 @@ const nextConfig = {
|
||||||
// esmExternals: true,
|
// esmExternals: true,
|
||||||
appDir: 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" })(
|
export default bundleAnalyzer({ enabled: process.env.ANALYZE === "true" })(
|
||||||
|
|
|
@ -22,12 +22,12 @@
|
||||||
"@radix-ui/react-tabs": "^1.0.1",
|
"@radix-ui/react-tabs": "^1.0.1",
|
||||||
"@radix-ui/react-tooltip": "^1.0.2",
|
"@radix-ui/react-tooltip": "^1.0.2",
|
||||||
"@wcj/markdown-to-html": "^2.1.2",
|
"@wcj/markdown-to-html": "^2.1.2",
|
||||||
"@wits/next-themes": "^0.2.14",
|
"@wits/next-themes": "0.2.12",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
"client-zip": "2.2.1",
|
"client-zip": "2.2.1",
|
||||||
"jest": "^29.3.1",
|
"jest": "^29.3.1",
|
||||||
"next": "13.0.6-canary.4",
|
"next": "13.0.7-canary.1",
|
||||||
"next-auth": "^4.17.0",
|
"next-auth": "^4.18.0",
|
||||||
"prisma": "^4.6.1",
|
"prisma": "^4.6.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-datepicker": "4.8.0",
|
"react-datepicker": "4.8.0",
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
"react-dropzone": "14.2.3",
|
"react-dropzone": "14.2.3",
|
||||||
"react-feather": "^2.0.10",
|
"react-feather": "^2.0.10",
|
||||||
"react-hot-toast": "^2.4.0",
|
"react-hot-toast": "^2.4.0",
|
||||||
"textarea-markdown-editor": "0.1.13",
|
"textarea-markdown-editor": "1.0.4",
|
||||||
"ts-jest": "^29.0.3"
|
"ts-jest": "^29.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -47,6 +47,7 @@
|
||||||
"@types/react-dom": "18.0.3",
|
"@types/react-dom": "18.0.3",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
|
"csstype": "^3.1.1",
|
||||||
"eslint": "8.27.0",
|
"eslint": "8.27.0",
|
||||||
"eslint-config-next": "13.0.3",
|
"eslint-config-next": "13.0.3",
|
||||||
"next-unused": "0.0.6",
|
"next-unused": "0.0.6",
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// ts nextjs api handler
|
|
||||||
|
|
||||||
import { withMethods } from "@lib/api-middleware/with-methods"
|
import { withMethods } from "@lib/api-middleware/with-methods"
|
||||||
import { parseQueryParam } from "@lib/server/parse-query-param"
|
import { parseQueryParam } from "@lib/server/parse-query-param"
|
||||||
import { NextApiRequest, NextApiResponse } from "next"
|
import { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
|
@ -16,16 +16,17 @@ specifiers:
|
||||||
'@types/react-datepicker': 4.4.1
|
'@types/react-datepicker': 4.4.1
|
||||||
'@types/react-dom': 18.0.3
|
'@types/react-dom': 18.0.3
|
||||||
'@wcj/markdown-to-html': ^2.1.2
|
'@wcj/markdown-to-html': ^2.1.2
|
||||||
'@wits/next-themes': ^0.2.14
|
'@wits/next-themes': 0.2.12
|
||||||
bcrypt: ^5.1.0
|
bcrypt: ^5.1.0
|
||||||
client-zip: 2.2.1
|
client-zip: 2.2.1
|
||||||
clsx: ^1.2.1
|
clsx: ^1.2.1
|
||||||
cross-env: 7.0.3
|
cross-env: 7.0.3
|
||||||
|
csstype: ^3.1.1
|
||||||
eslint: 8.27.0
|
eslint: 8.27.0
|
||||||
eslint-config-next: 13.0.3
|
eslint-config-next: 13.0.3
|
||||||
jest: ^29.3.1
|
jest: ^29.3.1
|
||||||
next: 13.0.6-canary.4
|
next: 13.0.7-canary.1
|
||||||
next-auth: ^4.17.0
|
next-auth: ^4.18.0
|
||||||
next-unused: 0.0.6
|
next-unused: 0.0.6
|
||||||
prettier: 2.6.2
|
prettier: 2.6.2
|
||||||
prisma: ^4.6.1
|
prisma: ^4.6.1
|
||||||
|
@ -36,13 +37,13 @@ specifiers:
|
||||||
react-feather: ^2.0.10
|
react-feather: ^2.0.10
|
||||||
react-hot-toast: ^2.4.0
|
react-hot-toast: ^2.4.0
|
||||||
sharp: ^0.31.2
|
sharp: ^0.31.2
|
||||||
textarea-markdown-editor: 0.1.13
|
textarea-markdown-editor: 1.0.4
|
||||||
ts-jest: ^29.0.3
|
ts-jest: ^29.0.3
|
||||||
typescript: 4.6.4
|
typescript: 4.6.4
|
||||||
typescript-plugin-css-modules: 3.4.0
|
typescript-plugin-css-modules: 3.4.0
|
||||||
|
|
||||||
dependencies:
|
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
|
'@next/eslint-plugin-next': 13.0.5-canary.3
|
||||||
'@prisma/client': 4.6.1_prisma@4.6.1
|
'@prisma/client': 4.6.1_prisma@4.6.1
|
||||||
'@radix-ui/react-dialog': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
|
'@radix-ui/react-dialog': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
|
||||||
|
@ -51,20 +52,20 @@ dependencies:
|
||||||
'@radix-ui/react-tabs': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
'@radix-ui/react-tabs': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||||
'@radix-ui/react-tooltip': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
|
'@radix-ui/react-tooltip': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
|
||||||
'@wcj/markdown-to-html': 2.1.2
|
'@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
|
bcrypt: 5.1.0
|
||||||
client-zip: 2.2.1
|
client-zip: 2.2.1
|
||||||
jest: 29.3.1_@types+node@17.0.23
|
jest: 29.3.1_@types+node@17.0.23
|
||||||
next: 13.0.6-canary.4_biqbaboplfbrettd7655fr4n2y
|
next: 13.0.7-canary.1_biqbaboplfbrettd7655fr4n2y
|
||||||
next-auth: 4.17.0_wq5w3t2dm6pp5l2gadmp4osgce
|
next-auth: 4.18.0_ihvxcpofhpc4k2aqfys2drrlkq
|
||||||
prisma: 4.6.1
|
prisma: 4.6.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-datepicker: 4.8.0_biqbaboplfbrettd7655fr4n2y
|
react-datepicker: 4.8.0_biqbaboplfbrettd7655fr4n2y
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
react-dropzone: 14.2.3_react@18.2.0
|
react-dropzone: 14.2.3_react@18.2.0
|
||||||
react-feather: 2.0.10_react@18.2.0
|
react-feather: 2.0.10_react@18.2.0
|
||||||
react-hot-toast: 2.4.0_biqbaboplfbrettd7655fr4n2y
|
react-hot-toast: 2.4.0_owo25xnefcwdq3zjgtohz6dbju
|
||||||
textarea-markdown-editor: 0.1.13_biqbaboplfbrettd7655fr4n2y
|
textarea-markdown-editor: 1.0.4_biqbaboplfbrettd7655fr4n2y
|
||||||
ts-jest: 29.0.3_7hcmezpa7bajbjecov7p46z4aa
|
ts-jest: 29.0.3_7hcmezpa7bajbjecov7p46z4aa
|
||||||
|
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
@ -79,6 +80,7 @@ devDependencies:
|
||||||
'@types/react-dom': 18.0.3
|
'@types/react-dom': 18.0.3
|
||||||
clsx: 1.2.1
|
clsx: 1.2.1
|
||||||
cross-env: 7.0.3
|
cross-env: 7.0.3
|
||||||
|
csstype: 3.1.1
|
||||||
eslint: 8.27.0
|
eslint: 8.27.0
|
||||||
eslint-config-next: 13.0.3_hsmo2rtalirsvadpuxki35bq2i
|
eslint-config-next: 13.0.3_hsmo2rtalirsvadpuxki35bq2i
|
||||||
next-unused: 0.0.6
|
next-unused: 0.0.6
|
||||||
|
@ -783,14 +785,14 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next-auth/prisma-adapter/1.0.5_o53gfpk3vz2btjrokqfjjwwn3m:
|
/@next-auth/prisma-adapter/1.0.5_qwexivae5olc6wqfcmxswm7qjy:
|
||||||
resolution: {integrity: sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==}
|
resolution: {integrity: sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@prisma/client': '>=2.26.0 || >=3'
|
'@prisma/client': '>=2.26.0 || >=3'
|
||||||
next-auth: ^4
|
next-auth: ^4
|
||||||
dependencies:
|
dependencies:
|
||||||
'@prisma/client': 4.6.1_prisma@4.6.1
|
'@prisma/client': 4.6.1_prisma@4.6.1
|
||||||
next-auth: 4.17.0_wq5w3t2dm6pp5l2gadmp4osgce
|
next-auth: 4.18.0_ihvxcpofhpc4k2aqfys2drrlkq
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/bundle-analyzer/12.1.6:
|
/@next/bundle-analyzer/12.1.6:
|
||||||
|
@ -802,8 +804,8 @@ packages:
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@next/env/13.0.6-canary.4:
|
/@next/env/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-3SMpcpiHIPlUBdWOyf130lgiOZ56l1Jhhq1zbZbLa42No2czgLSo5R9X3dy21ozPNIzDMj7K5FwhhkoO0CQP+Q==}
|
resolution: {integrity: sha512-pjFCstWLbHpO3wAI4H6Jueiqb9s1IB++w8e79RJvry5K2ElzRpUPXeTkjI/oLSZ6W9DDG8DTOMmJtat2o+h3jA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/eslint-plugin-next/13.0.3:
|
/@next/eslint-plugin-next/13.0.3:
|
||||||
|
@ -818,8 +820,8 @@ packages:
|
||||||
glob: 7.1.7
|
glob: 7.1.7
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/swc-android-arm-eabi/13.0.6-canary.4:
|
/@next/swc-android-arm-eabi/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-FfjHzjjiJPHHwEGpgj8dO3Js3CMoO/emVb6XrzutZjbDI5kMvOLEFRiCKsAvcy5Do7nb1tkj1dc4wHE/ZgVGsQ==}
|
resolution: {integrity: sha512-bmUIfXap+EwEpkWqGso3fMScXpbbUHecFByjnnmWOXU21e1bhE7UfCDtXzEn3utwt8MlUwA/h/5CGf6wMFUU8w==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
@ -827,8 +829,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-android-arm64/13.0.6-canary.4:
|
/@next/swc-android-arm64/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-r01HaiYopunrkG6PiNljTkVulnmhYukJKQjOd+EsCRAq4aTN5geTQq4l1ZdaSJTVGeMmxa7SbtXQgZxH8KmhQQ==}
|
resolution: {integrity: sha512-k0Wo/NgoAj1Bcp7X7fYc8C4G4Y+qiLrjqWGTQ38Cx5NHJfMJf6gUTfgc2OTBG96tKj21LwKhhg6BEqV9mRuzOg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
@ -836,8 +838,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-arm64/13.0.6-canary.4:
|
/@next/swc-darwin-arm64/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-sGBYihzRUIIlGfi70L9uWNRFgM1+d9KDa+tDp/goCBmzty6KExxet3DT2drOnGlGOKPV/+fooQCEAV6cqkMVkg==}
|
resolution: {integrity: sha512-phrROUvPXYouNl4Bs7kRmkTcU18V2gxIbwiWonQYWROfCQJckELHM0MFOAfLbkJYRT/vcyp/o2bgiPkWv/fP8A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
@ -845,8 +847,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-x64/13.0.6-canary.4:
|
/@next/swc-darwin-x64/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-KhkVYdLH288jje1cA8IZIvwPHhiuXsmXcDhUHNg437phhL+Vn52UD2WjvkPDAJCNgpsM23hTTHbxtHELjlXcNw==}
|
resolution: {integrity: sha512-ERpeI2zWlTj4xKdhwq8h9gyMWHSCh5UNm3ekX/MCgq1Mg1cLsv/kINeVQuvVP5II5HSHoEjnw2GvAMB4ayhcUA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
@ -854,8 +856,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-freebsd-x64/13.0.6-canary.4:
|
/@next/swc-freebsd-x64/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-rOs64ugbLQd8pPjrVpaGZVZZ5xgn/plFNLdjFdldBS3E0/fwZZWBZgKsPf9ItrNgXPO4PKRk/KmoXBwZwGS3Ng==}
|
resolution: {integrity: sha512-L/YIIzaoV58UHLgiR8jfr0V9HXmUvHf1a2+1esSsTlMXZ0Y3SzcczuLgEu0/AYKEgHcfl+vcng9FBeqXtVlYyQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
|
@ -863,8 +865,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm-gnueabihf/13.0.6-canary.4:
|
/@next/swc-linux-arm-gnueabihf/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-isuWwAJhXRJSlv0RCmrMdtlShGaDygQlPD0T6MzEdAG79b216FwZDu3yWzkny+bhxx/5E6w2SiJfjS9nuICLxw==}
|
resolution: {integrity: sha512-KVB7lAgtUgqrroqozYSCZIwVQITHhjbe99n/C6A9BYIAUtwITrLIn8Sj7D0a0sEhdDL8Y/rzXZGWMqL7f1Hg3A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -872,8 +874,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-gnu/13.0.6-canary.4:
|
/@next/swc-linux-arm64-gnu/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-LYfHVdHjadJ3a+MiFhjhM6ou5Gx1hlGPN2xPshMTPY/KvESofPiBg1m3bJhjNaaNheSmQDJJ1QhVWM+EMBEZNA==}
|
resolution: {integrity: sha512-tA4yYk1+2fPgs0q6r94d7sKQosf9jZGTMXIS0yOykk246L3+npsDqyBrdCusaJv9q3Fm5S8lfwp4vqoLNtcFLg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -881,8 +883,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-musl/13.0.6-canary.4:
|
/@next/swc-linux-arm64-musl/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-Kvqw+9p0/amAHN7Q3LoZ0GJbyBYHUJm5C23ojihsz0UHemO057q+MwgwZbmz6ufwmOrUCfoq3RQo4C5vUzeNCw==}
|
resolution: {integrity: sha512-KKN/nd2g2Cixs+av1mLeiNvhm+8T8ZiuzZHRrA4h4OWwreI+weS0iXBa1sBGvNp863MxE1mxKOv2xFhSbWK/CQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -890,8 +892,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-gnu/13.0.6-canary.4:
|
/@next/swc-linux-x64-gnu/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-LisCGX5oUXPrc43tyc/rsv3rKI2Yxqd5Eq7LsXI3TWIjH5xLeRi1LWxGxEc3asAI28dKBjgizyUzGbPwBE2KdQ==}
|
resolution: {integrity: sha512-1crxMrvO2pHmsDxSkVknchiyLHYpkKKkwhnrFYKP06bZSEONAry6VTYJ6l73PK9mp1kzFAtke5k9yG4LG0fbAQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -899,8 +901,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-musl/13.0.6-canary.4:
|
/@next/swc-linux-x64-musl/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-rXRilFn8Tqop6G7c7CluIgXGhyr1k4aqEJBir4mlHWdtcW3JMolWZCJ/agXSQ7+JyS1LROI93XN0LKRu9vapIw==}
|
resolution: {integrity: sha512-1incysWrn+PEK6XRE1QnK2UI7//N6gfmeaFC1KIlRyt0JmyF8U3V+I6Qcar9nHz9hY9e8yszFQY0A9X0jsfkUQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -908,8 +910,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-arm64-msvc/13.0.6-canary.4:
|
/@next/swc-win32-arm64-msvc/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-LVlG39FDY/VOhRs5gKvSDUjgPpdWyDyGQjROFP+WGxGgKd+NZU7dk4sXkAo71jOZbmqT+EcGI69dJMK5gDO12Q==}
|
resolution: {integrity: sha512-AE5NYCeXilQnzIOma7y3cNcYVQsHJsEZ3r4/DTKvmFvuFVBkxza7Uxzi5rwD67ewSbOzir1xr+LBtI6vCmQ/Fw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
@ -917,8 +919,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-ia32-msvc/13.0.6-canary.4:
|
/@next/swc-win32-ia32-msvc/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-XWpmQngO9Z5kTnUum4dnXg1jOP2H36qOEofvNj87BtlUJFxAbMzrs9Zbnix+1CmGbBg88mNTuaH1LAYupc+utQ==}
|
resolution: {integrity: sha512-kG84cAm/FZsK3u2vgcUpQRT28NEA+vMTMrp4ufdHPu+c0o0aEcLqh3yQstWqw+hGpYQxiB0EF95K9bbRfHkgOQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
@ -926,8 +928,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-x64-msvc/13.0.6-canary.4:
|
/@next/swc-win32-x64-msvc/13.0.7-canary.1:
|
||||||
resolution: {integrity: sha512-E6FnI9m40oQAYgcwsY7i9HLHSCo0Lh6m+TzXUEdMkjl7WVpo96dtvEVt4/8wDnVPA+x/PkM6Qo8MKooZmCeeZw==}
|
resolution: {integrity: sha512-FrEMvjaPJ3g2BcQp0aovr4Jj5L/KnvWlnvw5fIPMMoDmUYuMkbR4ZbAvIrOaLGCRiO0862kcoCcdhZ75AwzU2g==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
@ -1709,14 +1711,14 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@wits/next-themes/0.2.14_wq5w3t2dm6pp5l2gadmp4osgce:
|
/@wits/next-themes/0.2.12_ihvxcpofhpc4k2aqfys2drrlkq:
|
||||||
resolution: {integrity: sha512-fHKb/tRcWbYNblGHZtfvAQztDhzUB9d7ZkYOny0BisSPh6EABcsqxKB48ABUQztcmKywlp2zEMkLcSRj/PQBSw==}
|
resolution: {integrity: sha512-P/qtLW68n4xBLT8UfLkCD/0jmF0yWxdf3xpGCDbfR6WuvK2brJDQ0DhPbhsucGkJ42ArA6ItKqcIo7/cnKzhGg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
next: '*'
|
next: '*'
|
||||||
react: '*'
|
react: '*'
|
||||||
react-dom: '*'
|
react-dom: '*'
|
||||||
dependencies:
|
dependencies:
|
||||||
next: 13.0.6-canary.4_biqbaboplfbrettd7655fr4n2y
|
next: 13.0.7-canary.1_biqbaboplfbrettd7655fr4n2y
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -3423,10 +3425,12 @@ packages:
|
||||||
minimist: 1.2.7
|
minimist: 1.2.7
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/goober/2.1.11:
|
/goober/2.1.11_csstype@3.1.1:
|
||||||
resolution: {integrity: sha512-5SS2lmxbhqH0u9ABEWq7WPU69a4i2pYcHeCxqaNq6Cw3mnrF0ghWNM4tEGid4dKy8XNIAUbuThuozDHHKJVh3A==}
|
resolution: {integrity: sha512-5SS2lmxbhqH0u9ABEWq7WPU69a4i2pYcHeCxqaNq6Cw3mnrF0ghWNM4tEGid4dKy8XNIAUbuThuozDHHKJVh3A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
csstype: ^3.0.10
|
csstype: ^3.0.10
|
||||||
|
dependencies:
|
||||||
|
csstype: 3.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/graceful-fs/4.2.10:
|
/graceful-fs/4.2.10:
|
||||||
|
@ -5198,8 +5202,8 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/next-auth/4.17.0_wq5w3t2dm6pp5l2gadmp4osgce:
|
/next-auth/4.18.0_ihvxcpofhpc4k2aqfys2drrlkq:
|
||||||
resolution: {integrity: sha512-aN2tdnjS0MDeUpB2tBDOaWnegkgeMWrsccujbXRGMJ607b+EwRcy63MFGSr0OAboDJEe0902piXQkt94GqF8Qw==}
|
resolution: {integrity: sha512-lqJtusYqUwDiwzO4+B+lx/vKCuf/akcdhxT5R47JmS5gvI9O6Y4CZYc8coysY7XaMGHCxfttvTSEw76RA8gNTg==}
|
||||||
engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0}
|
engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
next: ^12.2.5 || ^13
|
next: ^12.2.5 || ^13
|
||||||
|
@ -5214,7 +5218,7 @@ packages:
|
||||||
'@panva/hkdf': 1.0.2
|
'@panva/hkdf': 1.0.2
|
||||||
cookie: 0.5.0
|
cookie: 0.5.0
|
||||||
jose: 4.11.0
|
jose: 4.11.0
|
||||||
next: 13.0.6-canary.4_biqbaboplfbrettd7655fr4n2y
|
next: 13.0.7-canary.1_biqbaboplfbrettd7655fr4n2y
|
||||||
oauth: 0.9.15
|
oauth: 0.9.15
|
||||||
openid-client: 5.3.0
|
openid-client: 5.3.0
|
||||||
preact: 10.11.2
|
preact: 10.11.2
|
||||||
|
@ -5235,8 +5239,8 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/next/13.0.6-canary.4_biqbaboplfbrettd7655fr4n2y:
|
/next/13.0.7-canary.1_biqbaboplfbrettd7655fr4n2y:
|
||||||
resolution: {integrity: sha512-41koM6+K/ywM2N5beni1x/vP2huDVTBbHikP46eLAmJK7KU+CHwoyblWDjqeg0c6PAkoAHybk5LjYbfjiFiBCw==}
|
resolution: {integrity: sha512-xfLT5Ikty2zcFCYSsYaQta3Dik09BJmwwj5a3i/ceh+51rJ+I3lP9+BbB9dUCUmgftOgxyyFUkzIZJ/gi3fUiQ==}
|
||||||
engines: {node: '>=14.6.0'}
|
engines: {node: '>=14.6.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -5253,7 +5257,7 @@ packages:
|
||||||
sass:
|
sass:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 13.0.6-canary.4
|
'@next/env': 13.0.7-canary.1
|
||||||
'@swc/helpers': 0.4.14
|
'@swc/helpers': 0.4.14
|
||||||
caniuse-lite: 1.0.30001431
|
caniuse-lite: 1.0.30001431
|
||||||
postcss: 8.4.14
|
postcss: 8.4.14
|
||||||
|
@ -5261,19 +5265,19 @@ packages:
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
styled-jsx: 5.1.0_react@18.2.0
|
styled-jsx: 5.1.0_react@18.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-android-arm-eabi': 13.0.6-canary.4
|
'@next/swc-android-arm-eabi': 13.0.7-canary.1
|
||||||
'@next/swc-android-arm64': 13.0.6-canary.4
|
'@next/swc-android-arm64': 13.0.7-canary.1
|
||||||
'@next/swc-darwin-arm64': 13.0.6-canary.4
|
'@next/swc-darwin-arm64': 13.0.7-canary.1
|
||||||
'@next/swc-darwin-x64': 13.0.6-canary.4
|
'@next/swc-darwin-x64': 13.0.7-canary.1
|
||||||
'@next/swc-freebsd-x64': 13.0.6-canary.4
|
'@next/swc-freebsd-x64': 13.0.7-canary.1
|
||||||
'@next/swc-linux-arm-gnueabihf': 13.0.6-canary.4
|
'@next/swc-linux-arm-gnueabihf': 13.0.7-canary.1
|
||||||
'@next/swc-linux-arm64-gnu': 13.0.6-canary.4
|
'@next/swc-linux-arm64-gnu': 13.0.7-canary.1
|
||||||
'@next/swc-linux-arm64-musl': 13.0.6-canary.4
|
'@next/swc-linux-arm64-musl': 13.0.7-canary.1
|
||||||
'@next/swc-linux-x64-gnu': 13.0.6-canary.4
|
'@next/swc-linux-x64-gnu': 13.0.7-canary.1
|
||||||
'@next/swc-linux-x64-musl': 13.0.6-canary.4
|
'@next/swc-linux-x64-musl': 13.0.7-canary.1
|
||||||
'@next/swc-win32-arm64-msvc': 13.0.6-canary.4
|
'@next/swc-win32-arm64-msvc': 13.0.7-canary.1
|
||||||
'@next/swc-win32-ia32-msvc': 13.0.6-canary.4
|
'@next/swc-win32-ia32-msvc': 13.0.7-canary.1
|
||||||
'@next/swc-win32-x64-msvc': 13.0.6-canary.4
|
'@next/swc-win32-x64-msvc': 13.0.7-canary.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
|
@ -5926,14 +5930,14 @@ packages:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-hot-toast/2.4.0_biqbaboplfbrettd7655fr4n2y:
|
/react-hot-toast/2.4.0_owo25xnefcwdq3zjgtohz6dbju:
|
||||||
resolution: {integrity: sha512-qnnVbXropKuwUpriVVosgo8QrB+IaPJCpL8oBI6Ov84uvHZ5QQcTp2qg6ku2wNfgJl6rlQXJIQU5q+5lmPOutA==}
|
resolution: {integrity: sha512-qnnVbXropKuwUpriVVosgo8QrB+IaPJCpL8oBI6Ov84uvHZ5QQcTp2qg6ku2wNfgJl6rlQXJIQU5q+5lmPOutA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=16'
|
react: '>=16'
|
||||||
react-dom: '>=16'
|
react-dom: '>=16'
|
||||||
dependencies:
|
dependencies:
|
||||||
goober: 2.1.11
|
goober: 2.1.11_csstype@3.1.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -6022,10 +6026,6 @@ packages:
|
||||||
tslib: 2.4.1
|
tslib: 2.4.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-trigger-change/1.0.2:
|
|
||||||
resolution: {integrity: sha512-3x2i/CTQZZlzTuDIfOrJ12r1IqcNWLxHVHXKTtlb4L0slt6Zy3YF3smimx4kdX4X/ASSuQnI/n1q4cTqDwWkPA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react/18.2.0:
|
/react/18.2.0:
|
||||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -6732,16 +6732,15 @@ packages:
|
||||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/textarea-markdown-editor/0.1.13_biqbaboplfbrettd7655fr4n2y:
|
/textarea-markdown-editor/1.0.4_biqbaboplfbrettd7655fr4n2y:
|
||||||
resolution: {integrity: sha512-2r1gTPFA/wwAzt+Aa6LVZWjJNvL0aXfR6Z9T6eQBpJ1AK6gtPVCZgkO97KIrqpAmMcwgNCz0ToYj2AqPufdVeg==}
|
resolution: {integrity: sha512-4uA8EZ0FkIL0dq89+xiA0BEo832/rKdtoi2T4Wab0wLZfHys82JE1i5YJf8BKAr/IQELF2NxQ5LITYkb8BGIFA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.9.0 || ^17.0
|
react: ^16.8.0 || ^17.0.0 || || ^18.0.0
|
||||||
react-dom: ^16.9.0 || ^17.0
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
mousetrap: 1.6.5
|
mousetrap: 1.6.5
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
react-trigger-change: 1.0.2
|
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/tmpl/1.0.5:
|
/tmpl/1.0.5:
|
||||||
|
|
Loading…
Reference in a new issue