Style improvements, re-enable themes, bump next
This commit is contained in:
parent
e41dc292b8
commit
b848aa9e40
36 changed files with 385 additions and 425 deletions
|
@ -55,7 +55,6 @@ const Auth = ({
|
|||
message: res.error
|
||||
})
|
||||
} else {
|
||||
console.log("res", res)
|
||||
startTransition(() => {
|
||||
router.push("/new")
|
||||
router.refresh()
|
||||
|
|
|
@ -2,7 +2,6 @@ import { memo, useEffect, useState } from "react"
|
|||
import styles from "./preview.module.css"
|
||||
import "@styles/markdown.css"
|
||||
import "@styles/syntax.css"
|
||||
import Skeleton from "@components/skeleton"
|
||||
import { Spinner } from "@components/spinner"
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -311,7 +311,7 @@ const Post = ({
|
|||
enableTabLoop={false}
|
||||
minDate={new Date()}
|
||||
/>
|
||||
<ButtonDropdown iconHeight={40}>
|
||||
<ButtonDropdown>
|
||||
<Button
|
||||
height={40}
|
||||
width={251}
|
||||
|
|
|
@ -13,19 +13,22 @@ export const PostButtons = ({
|
|||
files,
|
||||
loading,
|
||||
postId,
|
||||
parentId
|
||||
parentId,
|
||||
}: {
|
||||
title: string
|
||||
files?: Pick<PostWithFiles, "files">["files"]
|
||||
loading?: boolean
|
||||
postId?: string
|
||||
parentId?: string
|
||||
visibility?: string
|
||||
authorId?: string
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
const downloadClick = async () => {
|
||||
if (!files?.length) return
|
||||
const downloadZip = (await import("client-zip")).downloadZip
|
||||
const blob = await downloadZip(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
files.map((file: any) => {
|
||||
return {
|
||||
name: file.title,
|
||||
|
@ -52,11 +55,7 @@ export const PostButtons = ({
|
|||
return (
|
||||
<span className={styles.buttons}>
|
||||
<ButtonGroup verticalIfMobile>
|
||||
<Button
|
||||
iconLeft={<Edit />}
|
||||
onClick={editACopy}
|
||||
style={{ textTransform: "none" }}
|
||||
>
|
||||
<Button iconLeft={<Edit />} onClick={editACopy}>
|
||||
Edit a Copy
|
||||
</Button>
|
||||
{parentId && (
|
||||
|
@ -64,11 +63,7 @@ export const PostButtons = ({
|
|||
View Parent
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={downloadClick}
|
||||
iconLeft={<Archive />}
|
||||
style={{ textTransform: "none" }}
|
||||
>
|
||||
<Button onClick={downloadClick} iconLeft={<Archive />}>
|
||||
Download as ZIP Archive
|
||||
</Button>
|
||||
<FileDropdown loading={loading} files={files || []} />
|
||||
|
|
|
@ -34,7 +34,9 @@ export const PostTitle = ({
|
|||
{title}{" "}
|
||||
<span style={{ color: "var(--gray)" }}>
|
||||
by{" "}
|
||||
<Link href={`/author/${authorId}`}>{displayName || "anonymous"}</Link>
|
||||
<Link colored href={`/author/${authorId}`}>
|
||||
{displayName || "anonymous"}
|
||||
</Link>
|
||||
</span>
|
||||
</h1>
|
||||
{!loading && (
|
||||
|
|
|
@ -17,6 +17,13 @@
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.titleWithDropdown {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.title {
|
||||
flex-direction: column;
|
||||
|
|
|
@ -5,8 +5,7 @@ import DocumentComponent from "./view-document"
|
|||
import { useEffect, useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import PasswordModalPage from "./password-modal-wrapper"
|
||||
import { File, PostWithFilesAndAuthor } from "@lib/server/prisma"
|
||||
import { useSession } from "next-auth/react"
|
||||
import { PostWithFilesAndAuthor } from "@lib/server/prisma"
|
||||
|
||||
type Props = {
|
||||
post: string | PostWithFilesAndAuthor
|
||||
|
@ -14,66 +13,46 @@ type Props = {
|
|||
isAuthor?: boolean
|
||||
}
|
||||
|
||||
const PostFiles = ({
|
||||
post: _initialPost,
|
||||
isAuthor: isAuthorFromServer
|
||||
}: Props) => {
|
||||
const { data: session, status } = useSession()
|
||||
const isLoading = status === "loading"
|
||||
const PostFiles = ({ post: _initialPost }: Props) => {
|
||||
const initialPost =
|
||||
typeof _initialPost === "string"
|
||||
? (JSON.parse(_initialPost) as PostWithFilesAndAuthor)
|
||||
: _initialPost
|
||||
const [post, setPost] = useState<PostWithFilesAndAuthor>(initialPost)
|
||||
const isProtected = initialPost.visibility === "protected"
|
||||
|
||||
// We generate public and unlisted posts at build time, so we can't use
|
||||
// the session to determine if the user is the author on the server. We need to check
|
||||
// the post's authorId against the session's user id.
|
||||
const isAuthor = isAuthorFromServer
|
||||
? true
|
||||
: session?.user?.id === post?.authorId
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (post.expiresAt) {
|
||||
if (new Date(post.expiresAt) < new Date()) {
|
||||
if (!isAuthor) {
|
||||
router.push("/expired")
|
||||
}
|
||||
|
||||
const expirationDate = new Date(post.expiresAt ? post.expiresAt : "")
|
||||
if (!isAuthor && expirationDate < new Date()) {
|
||||
router.push("/expired")
|
||||
}
|
||||
|
||||
let interval: NodeJS.Timer | null = null
|
||||
if (post.expiresAt) {
|
||||
interval = setInterval(() => {
|
||||
const expirationDate = new Date(
|
||||
post.expiresAt ? post.expiresAt : ""
|
||||
)
|
||||
if (expirationDate < new Date()) {
|
||||
if (!isAuthor) {
|
||||
router.push("/expired")
|
||||
}
|
||||
clearInterval(interval!)
|
||||
}
|
||||
}, 4000)
|
||||
}
|
||||
return () => {
|
||||
if (interval) clearInterval(interval)
|
||||
}
|
||||
}
|
||||
if (post.expiresAt) {
|
||||
if (new Date(post.expiresAt) < new Date()) {
|
||||
router.push("/expired")
|
||||
}
|
||||
}, [isAuthor, post.expiresAt, router])
|
||||
|
||||
if (isLoading) {
|
||||
return <DocumentComponent skeleton={true} initialTab={"preview"} />
|
||||
}
|
||||
|
||||
if (isProtected) {
|
||||
return <PasswordModalPage setPost={setPost} postId={post.id} />
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timer | null = null
|
||||
if (post.expiresAt) {
|
||||
interval = setInterval(() => {
|
||||
const expirationDate = new Date(post.expiresAt ? post.expiresAt : "")
|
||||
if (expirationDate < new Date()) {
|
||||
router.push("/expired")
|
||||
if (interval) clearInterval(interval)
|
||||
}
|
||||
}, 4000)
|
||||
}
|
||||
return () => {
|
||||
if (interval) clearInterval(interval)
|
||||
}
|
||||
}, [post.expiresAt, router])
|
||||
|
||||
const isProtected = post.visibility === "protected"
|
||||
const hasFetched = post.files !== undefined
|
||||
if (isProtected && !hasFetched) {
|
||||
return (
|
||||
<PasswordModalPage
|
||||
authorId={post.authorId}
|
||||
setPost={setPost}
|
||||
postId={post.id}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -3,20 +3,27 @@
|
|||
import { Post, PostWithFilesAndAuthor } from "@lib/server/prisma"
|
||||
import PasswordModal from "@components/password-modal"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useState } from "react"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useToasts } from "@components/toasts"
|
||||
import { useSession } from "next-auth/react"
|
||||
|
||||
type Props = {
|
||||
setPost: (post: PostWithFilesAndAuthor) => void
|
||||
postId: Post["id"]
|
||||
authorId: Post["authorId"]
|
||||
}
|
||||
|
||||
const PasswordModalPage = ({ setPost, postId }: Props) => {
|
||||
const PasswordModalPage = ({ setPost, postId, authorId }: Props) => {
|
||||
const router = useRouter()
|
||||
const { setToast } = useToasts()
|
||||
const { data: session, status } = useSession()
|
||||
const isAuthor =
|
||||
status === "loading"
|
||||
? undefined
|
||||
: session?.user && session?.user?.id === authorId
|
||||
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(true)
|
||||
|
||||
const onSubmit = async (password: string) => {
|
||||
const onSubmit = useCallback(async (password: string) => {
|
||||
const res = await fetch(`/api/post/${postId}?password=${password}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
@ -44,13 +51,24 @@ const PasswordModalPage = ({ setPost, postId }: Props) => {
|
|||
setPost(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [postId, setPost, setToast])
|
||||
|
||||
const onClose = () => {
|
||||
setIsPasswordModalOpen(false)
|
||||
router.push("/")
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthor) {
|
||||
onSubmit("author")
|
||||
setToast({
|
||||
message:
|
||||
"You're the author of this post, so you automatically have access to it.",
|
||||
type: "default"
|
||||
})
|
||||
}
|
||||
}, [isAuthor, onSubmit, setToast])
|
||||
|
||||
return (
|
||||
<PasswordModal
|
||||
creating={false}
|
||||
|
|
|
@ -68,7 +68,7 @@ const Document = ({ skeleton, ...props }: Props) => {
|
|||
<Skeleton width={"100%"} height={36} />
|
||||
</div>
|
||||
<div className={styles.documentContainer}>
|
||||
<Skeleton width={145} height={36} borderRadius={"4px 4px 0 0"} />
|
||||
<Skeleton width={175} height={36} borderRadius={"4px 4px 0 0"} />
|
||||
<Skeleton
|
||||
width={"100%"}
|
||||
height={350}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
}
|
||||
|
||||
.controls {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
|
|
@ -1,111 +1,10 @@
|
|||
import { notFound, redirect } from "next/navigation"
|
||||
import { getPostById, Post, PostWithFilesAndAuthor } from "@lib/server/prisma"
|
||||
import { getCurrentUser } from "@lib/server/session"
|
||||
import { PostWithFilesAndAuthor } from "@lib/server/prisma"
|
||||
import ScrollToTop from "@components/scroll-to-top"
|
||||
import { title } from "process"
|
||||
import { PostButtons } from "./components/header/post-buttons"
|
||||
import styles from "./layout.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"
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
// return posts.map((post) => ({
|
||||
// id: post.id
|
||||
// }))
|
||||
// }
|
||||
|
||||
// export const dynamic = 'error';
|
||||
|
||||
const getPost = async (id: string) => {
|
||||
const [post, user] = await Promise.all([
|
||||
getPostById(id, {
|
||||
select: {
|
||||
visibility: true,
|
||||
authorId: true,
|
||||
title: true,
|
||||
description: true,
|
||||
id: true,
|
||||
createdAt: true,
|
||||
expiresAt: true,
|
||||
parentId: true,
|
||||
author: {
|
||||
select: {
|
||||
displayName: true,
|
||||
image: true
|
||||
}
|
||||
},
|
||||
files: {
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
updatedAt: true,
|
||||
title: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}).then((post) => {
|
||||
if (!post) {
|
||||
return notFound()
|
||||
}
|
||||
return post as PostWithFilesAndAuthor
|
||||
}),
|
||||
getCurrentUser()
|
||||
])
|
||||
|
||||
const isAuthorOrAdmin = user?.id === post?.authorId || user?.role === "admin"
|
||||
|
||||
if (post.visibility === "public" || post.visibility === "unlisted") {
|
||||
return { post, isAuthorOrAdmin }
|
||||
}
|
||||
|
||||
if (post.visibility === "private" && !isAuthorOrAdmin) {
|
||||
return redirect("/signin")
|
||||
}
|
||||
|
||||
if (post.visibility === "protected" && !isAuthorOrAdmin) {
|
||||
return {
|
||||
// TODO: remove this. It's temporary to appease typescript
|
||||
post: {
|
||||
visibility: "protected",
|
||||
id: post.id,
|
||||
files: [],
|
||||
parentId: "",
|
||||
title: "",
|
||||
createdAt: "",
|
||||
expiresAt: "",
|
||||
author: {
|
||||
displayName: ""
|
||||
},
|
||||
description: "",
|
||||
authorId: ""
|
||||
},
|
||||
isAuthor: isAuthorOrAdmin
|
||||
}
|
||||
}
|
||||
|
||||
// if expired
|
||||
if (post.expiresAt && !isAuthorOrAdmin) {
|
||||
const expirationDate = new Date(post.expiresAt)
|
||||
if (expirationDate < new Date()) {
|
||||
return redirect("/expired")
|
||||
}
|
||||
}
|
||||
|
||||
return { post, isAuthor: isAuthorOrAdmin }
|
||||
}
|
||||
import { getPost } from "./page"
|
||||
|
||||
export default async function PostLayout({
|
||||
children,
|
||||
|
@ -116,38 +15,41 @@ export default async function PostLayout({
|
|||
}
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const { post, isAuthor } = await getPost(params.id)
|
||||
const { post } = (await getPost(params.id)) as {
|
||||
post: PostWithFilesAndAuthor
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={styles.header}>
|
||||
<PostButtons
|
||||
parentId={post.parentId || undefined}
|
||||
postId={post.id}
|
||||
files={post.files}
|
||||
title={title}
|
||||
/>
|
||||
<PostTitle
|
||||
title={post.title}
|
||||
createdAt={post.createdAt.toString()}
|
||||
expiresAt={post.expiresAt?.toString()}
|
||||
// displayName is an optional param
|
||||
displayName={post.author?.displayName || undefined}
|
||||
visibility={post.visibility}
|
||||
authorId={post.authorId}
|
||||
/>
|
||||
{/* post.title is not set when the post is protected */}
|
||||
{post.title && (
|
||||
<PostButtons
|
||||
parentId={post.parentId || undefined}
|
||||
postId={post.id}
|
||||
files={post.files}
|
||||
title={title}
|
||||
authorId={post.authorId}
|
||||
visibility={post.visibility}
|
||||
/>
|
||||
)}
|
||||
{post.title && (
|
||||
<PostTitle
|
||||
title={post.title}
|
||||
createdAt={post.createdAt.toString()}
|
||||
expiresAt={post.expiresAt?.toString()}
|
||||
// displayName is an optional param
|
||||
displayName={post.author?.displayName || undefined}
|
||||
visibility={post.visibility}
|
||||
authorId={post.authorId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{post.description && (
|
||||
<div>
|
||||
<p>{post.description}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isAuthor && (
|
||||
<span className={styles.controls}>
|
||||
<VisibilityControl postId={post.id} visibility={post.visibility} />
|
||||
</span>
|
||||
)}
|
||||
<ScrollToTop />
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { PostButtons } from "./components/header/post-buttons"
|
||||
import { PostTitle } from "./components/header/title"
|
||||
import DocumentComponent from "./components/post-files/view-document"
|
||||
import styles from "./layout.module.css"
|
||||
|
||||
|
@ -7,8 +5,6 @@ export default function PostLoading() {
|
|||
return (
|
||||
<>
|
||||
<div className={styles.header}>
|
||||
<PostButtons loading title="" />
|
||||
<PostTitle title="" loading />
|
||||
<DocumentComponent skeleton initialTab="preview" />
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -1,86 +1,71 @@
|
|||
import VisibilityControl from "@components/badges/visibility-control"
|
||||
import { getPostById } from "@lib/server/prisma"
|
||||
import { getCurrentUser } from "@lib/server/session"
|
||||
import { notFound, redirect } from "next/navigation"
|
||||
import { cache } from "react"
|
||||
import PostFiles from "./components/post-files"
|
||||
|
||||
const getPost = async (id: string) => {
|
||||
const [post, user] = await Promise.all([
|
||||
getPostById(id, {
|
||||
select: {
|
||||
visibility: true,
|
||||
authorId: true,
|
||||
title: true,
|
||||
description: true,
|
||||
id: true,
|
||||
createdAt: true,
|
||||
expiresAt: true,
|
||||
parentId: true,
|
||||
author: {
|
||||
select: {
|
||||
displayName: true,
|
||||
image: true
|
||||
}
|
||||
},
|
||||
files: {
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
updatedAt: true,
|
||||
title: true,
|
||||
html: true
|
||||
}
|
||||
export const getPost = cache(async (id: string) => {
|
||||
const post = await getPostById(id, {
|
||||
select: {
|
||||
visibility: true,
|
||||
authorId: true,
|
||||
title: true,
|
||||
description: true,
|
||||
id: true,
|
||||
createdAt: true,
|
||||
expiresAt: true,
|
||||
parentId: true,
|
||||
author: {
|
||||
select: {
|
||||
displayName: true,
|
||||
image: true
|
||||
}
|
||||
},
|
||||
files: {
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
updatedAt: true,
|
||||
title: true,
|
||||
html: true
|
||||
}
|
||||
}
|
||||
}).then((post) => {
|
||||
if (!post) {
|
||||
return notFound()
|
||||
}
|
||||
return post
|
||||
}),
|
||||
getCurrentUser()
|
||||
])
|
||||
|
||||
const isAuthorOrAdmin = user?.id === post?.authorId || user?.role === "admin"
|
||||
|
||||
// if expired
|
||||
if (post.expiresAt && !isAuthorOrAdmin) {
|
||||
const expirationDate = new Date(post.expiresAt)
|
||||
if (expirationDate < new Date()) {
|
||||
return redirect("/expired")
|
||||
}
|
||||
})
|
||||
|
||||
if (!post) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
if (post.expiresAt && new Date(post.expiresAt) < new Date()) {
|
||||
return redirect("/expired")
|
||||
}
|
||||
|
||||
if (post.visibility === "public" || post.visibility === "unlisted") {
|
||||
return { post, isAuthorOrAdmin }
|
||||
return { post }
|
||||
}
|
||||
|
||||
if (post.visibility === "private" && !isAuthorOrAdmin) {
|
||||
return redirect("/signin")
|
||||
if (post.visibility === "private") {
|
||||
const user = await getCurrentUser()
|
||||
if (user?.id === post.authorId || user.role === "admin") {
|
||||
return { post }
|
||||
}
|
||||
return redirect("/new")
|
||||
}
|
||||
|
||||
if (post.visibility === "protected" && !isAuthorOrAdmin) {
|
||||
if (post.visibility === "protected") {
|
||||
return {
|
||||
// TODO: remove this. It's temporary to appease typescript
|
||||
post: {
|
||||
visibility: "protected",
|
||||
id: post.id,
|
||||
files: [],
|
||||
parentId: "",
|
||||
title: "",
|
||||
createdAt: "",
|
||||
expiresAt: "",
|
||||
author: {
|
||||
displayName: ""
|
||||
},
|
||||
description: "",
|
||||
authorId: ""
|
||||
},
|
||||
isAuthor: isAuthorOrAdmin
|
||||
authorId: post.authorId,
|
||||
id: post.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { post, isAuthor: isAuthorOrAdmin }
|
||||
}
|
||||
return { post }
|
||||
})
|
||||
|
||||
export default async function PostPage({
|
||||
params
|
||||
|
@ -89,7 +74,16 @@ export default async function PostPage({
|
|||
id: string
|
||||
}
|
||||
}) {
|
||||
const { post, isAuthor } = await getPost(params.id)
|
||||
const { post } = await getPost(params.id)
|
||||
const stringifiedPost = JSON.stringify(post)
|
||||
return <PostFiles post={stringifiedPost} isAuthor={isAuthor} />
|
||||
return (
|
||||
<>
|
||||
<PostFiles post={stringifiedPost} />
|
||||
<VisibilityControl
|
||||
authorId={post.authorId}
|
||||
postId={post.id}
|
||||
visibility={post.visibility}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -43,10 +43,10 @@ export default async function UserPage({
|
|||
return (
|
||||
<Image
|
||||
src={user.image}
|
||||
alt="User avatar"
|
||||
className="w-12 h-12 rounded-full"
|
||||
alt=""
|
||||
width={48}
|
||||
height={48}
|
||||
style={{ borderRadius: "50%", height: 32, width: 32 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -37,8 +37,11 @@ const ExpirationBadge = ({
|
|||
}, [expirationDate])
|
||||
|
||||
const isExpired = useMemo(() => {
|
||||
return timeUntilString === "in 0 seconds"
|
||||
}, [timeUntilString])
|
||||
if (!expirationDate) {
|
||||
return false
|
||||
}
|
||||
return expirationDate < new Date()
|
||||
}, [expirationDate])
|
||||
|
||||
if (!expirationDate) {
|
||||
return null
|
||||
|
@ -49,7 +52,9 @@ const ExpirationBadge = ({
|
|||
<Tooltip
|
||||
content={`${expirationDate.toLocaleDateString()} ${expirationDate.toLocaleTimeString()}`}
|
||||
>
|
||||
<>{isExpired ? "Expired" : `Expires ${timeUntilString}`}</>
|
||||
<span suppressHydrationWarning>
|
||||
{isExpired ? "Expired" : `Expires ${timeUntilString}`}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
)
|
||||
|
|
|
@ -6,13 +6,17 @@ import ButtonGroup from "@components/button-group"
|
|||
import Button from "@components/button"
|
||||
import { useToasts } from "@components/toasts"
|
||||
import { Spinner } from "@components/spinner"
|
||||
import { useSession } from "next-auth/react"
|
||||
|
||||
type Props = {
|
||||
authorId: string
|
||||
postId: string
|
||||
visibility: string
|
||||
}
|
||||
|
||||
const VisibilityControl = ({ postId, visibility: postVisibility }: Props) => {
|
||||
const VisibilityControl = ({ authorId, postId, visibility: postVisibility }: Props) => {
|
||||
const { data: session } = useSession()
|
||||
const isAuthor = session?.user && session?.user?.id === authorId
|
||||
const [visibility, setVisibility] = useState<string>(postVisibility)
|
||||
|
||||
const [isSubmitting, setSubmitting] = useState<string | null>()
|
||||
|
@ -66,9 +70,16 @@ const VisibilityControl = ({ postId, visibility: postVisibility }: Props) => {
|
|||
|
||||
const submitPassword = (password: string) => onSubmit("protected", password)
|
||||
|
||||
if (!isAuthor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonGroup verticalIfMobile>
|
||||
<ButtonGroup style={{
|
||||
maxWidth: 600,
|
||||
margin: "var(--gap) auto",
|
||||
}}>
|
||||
<Button
|
||||
disabled={visibility === "private"}
|
||||
onClick={() => onSubmit("private")}
|
||||
|
|
|
@ -5,10 +5,7 @@ import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
|
|||
import { ArrowDown } from "react-feather"
|
||||
type Props = {
|
||||
type?: "primary" | "secondary"
|
||||
loading?: boolean
|
||||
disabled?: boolean
|
||||
className?: string
|
||||
iconHeight?: number
|
||||
height?: number | string
|
||||
}
|
||||
|
||||
type Attrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
|
@ -16,14 +13,14 @@ type ButtonDropdownProps = Props & Attrs
|
|||
|
||||
const ButtonDropdown: React.FC<
|
||||
React.PropsWithChildren<ButtonDropdownProps>
|
||||
> = ({ type, className, disabled, loading, iconHeight = 24, ...props }) => {
|
||||
> = ({ type, ...props }) => {
|
||||
if (!Array.isArray(props.children)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<div className={styles.dropdown}>
|
||||
<div className={styles.dropdown} style={{ height: props.height }}>
|
||||
{props.children[0]}
|
||||
<DropdownMenu.Trigger
|
||||
style={{
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
|
||||
.card .content {
|
||||
padding: var(--gap-half);
|
||||
padding: var(--gap);
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { usePathname } from "next/navigation"
|
|||
import { signOut, useSession } from "next-auth/react"
|
||||
import Button from "@components/button"
|
||||
import clsx from "clsx"
|
||||
import { useTheme } from "@wits/next-themes"
|
||||
import { useTheme } from "next-themes"
|
||||
import {
|
||||
GitHub,
|
||||
Home,
|
||||
|
|
|
@ -18,8 +18,8 @@ const PasswordModal = ({
|
|||
onSubmit: onSubmitAfterVerify,
|
||||
creating
|
||||
}: Props) => {
|
||||
const [password, setPassword] = useState<string>()
|
||||
const [confirmPassword, setConfirmPassword] = useState<string>()
|
||||
const [password, setPassword] = useState<string>("")
|
||||
const [confirmPassword, setConfirmPassword] = useState<string>("")
|
||||
const [error, setError] = useState<string>()
|
||||
|
||||
const onSubmit = () => {
|
||||
|
@ -60,6 +60,12 @@ const PasswordModal = ({
|
|||
: "Enter the password to access the post"}
|
||||
</Dialog.Description>
|
||||
<fieldset className={styles.fieldset}>
|
||||
{!error && creating && (
|
||||
<Note type="warning">
|
||||
This doesn't protect your post from the server
|
||||
administrator.
|
||||
</Note>
|
||||
)}
|
||||
{error && <Note type="error">{error}</Note>}
|
||||
<Input
|
||||
width={"100%"}
|
||||
|
@ -79,15 +85,11 @@ const PasswordModal = ({
|
|||
onChange={(e) => setConfirmPassword(e.currentTarget.value)}
|
||||
/>
|
||||
)}
|
||||
{!error && creating && (
|
||||
<Note type="warning">
|
||||
This doesn't protect your post from the server
|
||||
administrator.
|
||||
</Note>
|
||||
)}
|
||||
</fieldset>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={onSubmit}>Submit</Button>
|
||||
<footer className={styles.footer}>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={onSubmit}>Submit</Button>
|
||||
</footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap);
|
||||
margin-bottom: var(--gap-half);
|
||||
margin-bottom: var(--gap);
|
||||
}
|
||||
|
||||
.content:focus {
|
||||
|
@ -48,6 +48,12 @@
|
|||
gap: var(--gap-half);
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--gap-half);
|
||||
}
|
||||
|
||||
@keyframes overlayShow {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
|
|
@ -16,13 +16,17 @@ type Props = {
|
|||
morePosts?: boolean
|
||||
userId?: string
|
||||
hideSearch?: boolean
|
||||
hideActions?: boolean
|
||||
isOwner?: boolean
|
||||
}
|
||||
|
||||
const PostList = ({
|
||||
morePosts,
|
||||
initialPosts: initialPostsMaybeJSON,
|
||||
userId,
|
||||
hideSearch
|
||||
hideSearch,
|
||||
hideActions,
|
||||
isOwner
|
||||
}: Props) => {
|
||||
const initialPosts =
|
||||
typeof initialPostsMaybeJSON === "string"
|
||||
|
@ -156,6 +160,8 @@ const PostList = ({
|
|||
deletePost={deletePost(post.id)}
|
||||
post={post}
|
||||
key={post.id}
|
||||
hideActions={hideActions}
|
||||
isOwner={isOwner}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
|
|
@ -3,6 +3,12 @@
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.titleText {
|
||||
display: flex;
|
||||
gap: var(--gap-half);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.badges {
|
||||
display: flex;
|
||||
gap: var(--gap-half);
|
||||
|
@ -33,6 +39,11 @@
|
|||
.title h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.titleText {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.files {
|
||||
|
|
|
@ -24,12 +24,14 @@ import { codeFileExtensions } from "@lib/constants"
|
|||
// TODO: isOwner should default to false so this can be used generically
|
||||
const ListItem = ({
|
||||
post,
|
||||
isOwner = true,
|
||||
deletePost
|
||||
isOwner,
|
||||
deletePost,
|
||||
hideActions
|
||||
}: {
|
||||
post: PostWithFiles
|
||||
isOwner?: boolean
|
||||
deletePost: () => void
|
||||
hideActions?: boolean
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
|
||||
|
@ -69,33 +71,45 @@ const ListItem = ({
|
|||
<Card style={{ overflowY: "scroll" }}>
|
||||
<>
|
||||
<div className={styles.title}>
|
||||
<h3 style={{ display: "inline-block", margin: 0 }}>
|
||||
<Link
|
||||
colored
|
||||
style={{ marginRight: "var(--gap)" }}
|
||||
href={`/post/${post.id}`}
|
||||
>
|
||||
{post.title}
|
||||
</Link>
|
||||
</h3>
|
||||
{isOwner && (
|
||||
<span className={styles.buttons}>
|
||||
{post.parentId && (
|
||||
<Tooltip content={"View parent"}>
|
||||
<Button
|
||||
iconRight={<ArrowUpCircle />}
|
||||
onClick={viewParentClick}
|
||||
height={38}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip content={"Make a copy"}>
|
||||
<span className={styles.titleText}>
|
||||
<h3 style={{ display: "inline-block", margin: 0 }}>
|
||||
<Link
|
||||
colored
|
||||
style={{ marginRight: "var(--gap)" }}
|
||||
href={`/post/${post.id}`}
|
||||
>
|
||||
{post.title}
|
||||
</Link>
|
||||
</h3>
|
||||
<div className={styles.badges}>
|
||||
<VisibilityBadge visibility={post.visibility} />
|
||||
<Badge type="secondary">
|
||||
{post.files?.length === 1
|
||||
? "1 file"
|
||||
: `${post.files?.length || 0} files`}
|
||||
</Badge>
|
||||
<CreatedAgoBadge createdAt={post.createdAt} />
|
||||
<ExpirationBadge postExpirationDate={post.expiresAt} />
|
||||
</div>
|
||||
</span>
|
||||
{!hideActions ? <span className={styles.buttons}>
|
||||
{post.parentId && (
|
||||
<Tooltip content={"View parent"}>
|
||||
<Button
|
||||
iconRight={<Edit />}
|
||||
onClick={editACopy}
|
||||
iconRight={<ArrowUpCircle />}
|
||||
onClick={viewParentClick}
|
||||
height={38}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip content={"Make a copy"}>
|
||||
<Button
|
||||
iconRight={<Edit />}
|
||||
onClick={editACopy}
|
||||
height={38}
|
||||
/>
|
||||
</Tooltip>
|
||||
{isOwner && (
|
||||
<Tooltip content={"Delete"}>
|
||||
<Button
|
||||
iconRight={<Trash />}
|
||||
|
@ -103,35 +117,28 @@ const ListItem = ({
|
|||
height={38}
|
||||
/>
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
)}
|
||||
</span> : null}
|
||||
</div>
|
||||
|
||||
{post.description && (
|
||||
<p className={styles.oneline}>{post.description}</p>
|
||||
)}
|
||||
|
||||
<div className={styles.badges}>
|
||||
<VisibilityBadge visibility={post.visibility} />
|
||||
<Badge type="secondary">
|
||||
{post.files?.length === 1
|
||||
? "1 file"
|
||||
: `${post.files?.length || 0} files`}
|
||||
</Badge>
|
||||
<CreatedAgoBadge createdAt={post.createdAt} />
|
||||
<ExpirationBadge postExpirationDate={post.expiresAt} />
|
||||
</div>
|
||||
</>
|
||||
<ul className={styles.files}>
|
||||
{post?.files?.map(
|
||||
(file: Pick<PostWithFiles, "files">["files"][0]) => {
|
||||
return (
|
||||
<li key={file.id}>
|
||||
<Link colored href={`/post/${post.id}#${file.title}`} style={{
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
}}>
|
||||
`` {getIconFromFilename(file.title)}
|
||||
<Link
|
||||
colored
|
||||
href={`/post/${post.id}#${file.title}`}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{getIconFromFilename(file.title)}
|
||||
{file.title || "Untitled file"}
|
||||
</Link>
|
||||
</li>
|
||||
|
|
|
@ -8,12 +8,6 @@
|
|||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.container ul li::before {
|
||||
content: "";
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.postHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
|
@ -25,6 +25,7 @@ export default async function Mine() {
|
|||
userId={userId}
|
||||
morePosts={hasMore}
|
||||
initialPosts={stringifiedPosts}
|
||||
isOwner={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ const getWelcomeData = async () => {
|
|||
}
|
||||
|
||||
export default async function Page() {
|
||||
const { content, rendered, title } = await getWelcomeData()
|
||||
const getPostsPromise = getAllPosts({
|
||||
select: {
|
||||
id: true,
|
||||
|
@ -21,18 +20,24 @@ export default async function Page() {
|
|||
select: {
|
||||
name: true
|
||||
}
|
||||
}
|
||||
},
|
||||
visibility: true,
|
||||
files: {
|
||||
select: {
|
||||
id: true,
|
||||
title: true
|
||||
}
|
||||
},
|
||||
authorId: true
|
||||
},
|
||||
where: {
|
||||
deletedAt: null,
|
||||
expiresAt: {
|
||||
gt: new Date()
|
||||
}
|
||||
visibility: "public"
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc"
|
||||
}
|
||||
})
|
||||
const { content, rendered, title } = await getWelcomeData()
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -79,9 +84,10 @@ async function PublicPostList({
|
|||
userId={undefined}
|
||||
morePosts={false}
|
||||
initialPosts={JSON.stringify(posts)}
|
||||
hideActions
|
||||
hideSearch
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const revalidate = 30
|
||||
export const revalidate = 60
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
|
||||
import * as RadixTooltip from "@radix-ui/react-tooltip"
|
||||
import { SessionProvider } from "next-auth/react"
|
||||
import { ThemeProvider } from "next-themes"
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<SessionProvider>
|
||||
<RadixTooltip.Provider delayDuration={200}>
|
||||
{children}
|
||||
<ThemeProvider enableSystem defaultTheme="dark">
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</RadixTooltip.Provider>
|
||||
</SessionProvider>
|
||||
)
|
||||
|
|
|
@ -10,9 +10,8 @@ import styles from "./profile.module.css"
|
|||
|
||||
const Profile = () => {
|
||||
const { data: session } = useSession()
|
||||
const [name, setName] = useState<string>(session?.user.displayName || "")
|
||||
const [name, setName] = useState<string>(session?.user.name || "")
|
||||
const [submitting, setSubmitting] = useState<boolean>(false)
|
||||
|
||||
const { setToast } = useToasts()
|
||||
|
||||
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
--darkest-gray: #efefef;
|
||||
--article-color: #eaeaea;
|
||||
--header-bg: rgba(19, 20, 21, 0.45);
|
||||
--gray-alpha: rgba(255, 255, 255, 0.5);
|
||||
--gray-alpha: rgba(19, 20, 21, 0.6);
|
||||
--selection: rgba(255, 255, 255, 0.99);
|
||||
--border: var(--light-gray);
|
||||
--warning: #ff6700;
|
||||
|
|
|
@ -6,7 +6,7 @@ const nextConfig = {
|
|||
experimental: {
|
||||
// esmExternals: true,
|
||||
appDir: true,
|
||||
serverComponentsExternalPackages: ["prisma", "@prisma/client"],
|
||||
serverComponentsExternalPackages: ["prisma", "@prisma/client"]
|
||||
},
|
||||
output: "standalone",
|
||||
rewrites() {
|
||||
|
@ -20,6 +20,9 @@ const nextConfig = {
|
|||
destination: "/"
|
||||
}
|
||||
]
|
||||
},
|
||||
images: {
|
||||
domains: ["avatars.githubusercontent.com"]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@next-auth/prisma-adapter": "^1.0.5",
|
||||
"@next/eslint-plugin-next": "13.0.7-canary.4",
|
||||
"@next/font": "13.0.8-canary.0",
|
||||
"@next/eslint-plugin-next": "13.1.1-canary.1",
|
||||
"@next/font": "13.1.1-canary.1",
|
||||
"@prisma/client": "^4.7.1",
|
||||
"@radix-ui/react-dialog": "^1.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.1",
|
||||
|
@ -28,8 +28,9 @@
|
|||
"client-zip": "2.2.1",
|
||||
"jest": "^29.3.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"next": "13.0.8-canary.0",
|
||||
"next": "13.1.1-canary.1",
|
||||
"next-auth": "^4.18.6",
|
||||
"next-themes": "^0.2.1",
|
||||
"prisma": "^4.7.1",
|
||||
"react": "18.2.0",
|
||||
"react-datepicker": "4.8.0",
|
||||
|
|
|
@ -61,14 +61,14 @@ async function handleGet(req: NextApiRequest, res: NextApiResponse<unknown>) {
|
|||
...post
|
||||
})
|
||||
} else {
|
||||
return {
|
||||
return res.json({
|
||||
isProtected: true,
|
||||
post: {
|
||||
id: post.id,
|
||||
visibility: post.visibility,
|
||||
title: post.title
|
||||
authorId: post.authorId,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
})
|
||||
}
|
||||
case "GET":
|
||||
return res.json(currUser)
|
||||
return res.json({
|
||||
...currUser,
|
||||
displayName: user.displayName
|
||||
})
|
||||
case "DELETE":
|
||||
if (currUser?.role !== "admin" && currUser?.id !== id) {
|
||||
return res.status(403).json({ message: "Unauthorized" })
|
||||
|
|
|
@ -3,8 +3,8 @@ lockfileVersion: 5.4
|
|||
specifiers:
|
||||
'@next-auth/prisma-adapter': ^1.0.5
|
||||
'@next/bundle-analyzer': 13.0.7-canary.4
|
||||
'@next/eslint-plugin-next': 13.0.7-canary.4
|
||||
'@next/font': 13.0.8-canary.0
|
||||
'@next/eslint-plugin-next': 13.1.1-canary.1
|
||||
'@next/font': 13.1.1-canary.1
|
||||
'@prisma/client': ^4.7.1
|
||||
'@radix-ui/react-dialog': ^1.0.2
|
||||
'@radix-ui/react-dropdown-menu': ^2.0.1
|
||||
|
@ -31,8 +31,9 @@ specifiers:
|
|||
eslint-config-next: 13.0.3
|
||||
jest: ^29.3.1
|
||||
lodash.debounce: ^4.0.8
|
||||
next: 13.0.8-canary.0
|
||||
next: 13.1.1-canary.1
|
||||
next-auth: ^4.18.6
|
||||
next-themes: ^0.2.1
|
||||
next-unused: 0.0.6
|
||||
prettier: 2.6.2
|
||||
prisma: ^4.7.1
|
||||
|
@ -52,8 +53,8 @@ specifiers:
|
|||
|
||||
dependencies:
|
||||
'@next-auth/prisma-adapter': 1.0.5_64qbzg5ec56bux2misz3l4u6g4
|
||||
'@next/eslint-plugin-next': 13.0.7-canary.4
|
||||
'@next/font': 13.0.8-canary.0
|
||||
'@next/eslint-plugin-next': 13.1.1-canary.1
|
||||
'@next/font': 13.1.1-canary.1
|
||||
'@prisma/client': 4.7.1_prisma@4.7.1
|
||||
'@radix-ui/react-dialog': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
|
||||
'@radix-ui/react-dropdown-menu': 2.0.1_jbvntnid6ohjelon6ccj5dhg2u
|
||||
|
@ -61,13 +62,14 @@ 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_rhfownvlqkszea7w3lnpwl7bzy
|
||||
'@wits/next-themes': 0.2.14_jcrpix7mbfpfu5movksylxa5c4
|
||||
client-only: 0.0.1
|
||||
client-zip: 2.2.1
|
||||
jest: 29.3.1_@types+node@17.0.23
|
||||
lodash.debounce: 4.0.8
|
||||
next: 13.0.8-canary.0_biqbaboplfbrettd7655fr4n2y
|
||||
next-auth: 4.18.6_rhfownvlqkszea7w3lnpwl7bzy
|
||||
next: 13.1.1-canary.1_biqbaboplfbrettd7655fr4n2y
|
||||
next-auth: 4.18.6_jcrpix7mbfpfu5movksylxa5c4
|
||||
next-themes: 0.2.1_jcrpix7mbfpfu5movksylxa5c4
|
||||
prisma: 4.7.1
|
||||
react: 18.2.0
|
||||
react-datepicker: 4.8.0_biqbaboplfbrettd7655fr4n2y
|
||||
|
@ -790,7 +792,7 @@ packages:
|
|||
next-auth: ^4
|
||||
dependencies:
|
||||
'@prisma/client': 4.7.1_prisma@4.7.1
|
||||
next-auth: 4.18.6_rhfownvlqkszea7w3lnpwl7bzy
|
||||
next-auth: 4.18.6_jcrpix7mbfpfu5movksylxa5c4
|
||||
dev: false
|
||||
|
||||
/@next/bundle-analyzer/13.0.7-canary.4:
|
||||
|
@ -802,8 +804,8 @@ packages:
|
|||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/@next/env/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-IiZM9mAUE9F3p9q/ydZBGlvmleOaMO6fBDBJzvQa4t3Ezg5e3NfGlTO01MTWvKPEKYPeAwFp+tcVh9ivA28+Dw==}
|
||||
/@next/env/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-h8DEj69dLJpFUuXOVPQeJ4/X1LbW5mtZSsaS5Xr/pt2VbrRN50eAV/6rMY+l6U6p/4AX1/F5aK4UBzLQJbwFzw==}
|
||||
dev: false
|
||||
|
||||
/@next/eslint-plugin-next/13.0.3:
|
||||
|
@ -812,18 +814,18 @@ packages:
|
|||
glob: 7.1.7
|
||||
dev: true
|
||||
|
||||
/@next/eslint-plugin-next/13.0.7-canary.4:
|
||||
resolution: {integrity: sha512-jNgarJTQSia+yTcQr6dF9wZhCfCFIXbc0WzSIAfyx4Z8FZjfmTmeVDGL1JHSYlcSmTnJ6o6Z36MsFh/mreuE4g==}
|
||||
/@next/eslint-plugin-next/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-gKabWQJ+Aps/u/lzVC3FoGwbG+r1t3cwoUXPbcpC3igrpBSbkhEoK9u3MYeMlkAUywJ7IvsENWVYqjzRuaso4Q==}
|
||||
dependencies:
|
||||
glob: 7.1.7
|
||||
dev: false
|
||||
|
||||
/@next/font/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-sdi4WoYMbJdiDev9UICg9dMhhA7I2xaJT07e8DZoSDWWTZaKk5zKNKuQFQoP47461R1WZAIQwIYTq+pVxQ87Gw==}
|
||||
/@next/font/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-cygeAS0h5OuWaorcQ6Ry8c/E0fwZEGQfZy7kUjXHVn6DP4sekB2RgR9aNWL3cUoJ35RwjwtsR7K6FufUyzfGag==}
|
||||
dev: false
|
||||
|
||||
/@next/swc-android-arm-eabi/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-U6nayRvWuASLLBwqG4nN9540ako+JEBblN8479BpGvW1F2FyQPUx/zq+WO0b47KPyJI2XNPBIenHGvtSY7yN/Q==}
|
||||
/@next/swc-android-arm-eabi/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-0McGEjTnNXdBTlghWxkuM07qpKMr44afLeGFpS/zwIlDV7lNOXFzCpyHdJoJsFL4kBJgfbyCi8aamnhwqlwZxA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
@ -831,8 +833,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-android-arm64/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-GtUW5CCIfN1FUln+pRm0rAWe8k957rcKhYDPGBrfr+jaKvUgjI4NgMcXRJ0R83j+vcM4+DIhIkIO+OYQ1vU4RA==}
|
||||
/@next/swc-android-arm64/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-XCmPqmhtsc52lv0Qs/maThRrQpHMRK1AqFhgFXfFG9wclbFBtQIUapD/qD7nOlXbch+7RDONbABPf/8pE2T0cQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
@ -840,8 +842,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-arm64/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-dqUn4ERXHT+g/L+paIi+IhNP3P7HiF95ZBIjQvn++n0IhdT8rRfaQK3ubps/NopL14jHA33J7HnK73vgUBIvwg==}
|
||||
/@next/swc-darwin-arm64/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-qz+et20cTetOppH6stlDW171tTo1vG4eHGmXY1Zwa3D/sZPk5IRsqsmpdzxuBsVxdk5x7zaliYZowOlQM2awnw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
@ -849,8 +851,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-x64/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-jGaI2idOd2Z0Dvlnz0WYHC+hbqQPIhaso/upJQebknWeu1VsSrwH5oDbCgMBaXLkHO7rMOITWC5FjxdXjSGK6g==}
|
||||
/@next/swc-darwin-x64/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-rPGOUsxturFtSkqtbSis1kBuu0mNzCPibWEMihsM32EzdXeDXJMgl5EP3+RiwGfrawON5lcTEz0r52Zll+0kmw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
@ -858,8 +860,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-freebsd-x64/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-ieM8XwqX9m/frFGpSwrXubzZYPT+ZzOEJsDgCNo3CD0DGHu8hZz1XLRym0Nl2mZAnBlxgENN+RlGwutWKBQMHg==}
|
||||
/@next/swc-freebsd-x64/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-tEnpdXSEzltEEbeh32w4eQN1znR35xjX0pMC7leud8XhJvppWwdEqfdOp3OuviPmb8p6LzFqYyknNe710cFy+Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
@ -867,8 +869,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm-gnueabihf/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-/9CnPhcqu/kudpk07zCkApFRUwF0wbwdFm5CqtguZ6yubqhoAV1Wjmrs1gnt+MUBHsVnKRdoGkz6KupQEZqz7g==}
|
||||
/@next/swc-linux-arm-gnueabihf/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-EJdCFjRHVoyDC8Q0N8ULCJ7+5hl4NhaELlu+04cCcgQ3qFZkFZIfTLrXnCT1aa2Y8yyR5FvyBeHgvusL5abqpQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
@ -876,8 +878,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-gnu/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-KUQs6KdX3lMxJu4ym/jNzotQvbkpXD/ne8KgjUuzTdgw3LYSfEMsTzORj71IR48H5yMDSLGPvCJA+B8FuVzS8Q==}
|
||||
/@next/swc-linux-arm64-gnu/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-BRN7Beg1OASa2F7FGYAdYL3O+bA2wFX6ow9QnHD312+JHCf/IKun3FSxSXBaSnc8ZJCnexmSWIz+hewKN1jGQQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
@ -885,8 +887,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-musl/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-bisV2RUrsQMJodK2xGszfqK9G/BuDlqVLeDZVrOENWaZnOVDtrP+WlqrN0vS1r8xn/OepJWKkMnibO4aLCruvw==}
|
||||
/@next/swc-linux-arm64-musl/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-WE1muJmocpSHUHBH02iMOy9RR4Hz7XFM6tjAevY40svXNmGNszhYzsm0MQ+/VnlqP9f9l1/dEiPN6tSbMAlH9A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
@ -894,8 +896,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-gnu/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-X8pcTN7nPZ7gDXb04oGWOS/qPvPaPK5x753AmevQgVa7FwqXQ6IkJeD3sr8ergmu6Fcfr6c4LcnBEQzpIxOxYA==}
|
||||
/@next/swc-linux-x64-gnu/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-aeBiutM8gFndpUkDA6t8DKzD9TcYg48+b7QxuL2XyRJo+47muhNbXaB6y/MwarxwjnsAry0hMs/ycP3lOL7vnw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
@ -903,8 +905,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-musl/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-Kg+tsnDmQ21qYfLik3YH+ZOYMmoNyhYqLMZE6qSASA5uN448J1cJUHIdpJxUpidZHtWBV+kTVR2Hw7+We+BiKQ==}
|
||||
/@next/swc-linux-x64-musl/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-JyJzejDuu68bZj1jrdbgJEIyj0xQy8N0R363T6Rx5/F5Htk2vVzXaP+MkANcWuZjvmH/BHjQc515liiTwQ328Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
@ -912,8 +914,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-arm64-msvc/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-tde5+ZQFT0+Pr/BKINQ32+8C/AEaZLzU69AvpD7dvbUEJ5fReIiSBPIL1ov3pZYR+EPwl7wFPoj7NLxTU1E8WA==}
|
||||
/@next/swc-win32-arm64-msvc/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-y/VxMhjXrTt4fGzrJwdfa6MM2ZauZ0dX20aRGDX/6VeaxO5toBsmXF7cwoDC97C65l93FY/X9vyc75WSLrXFrA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
@ -921,8 +923,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-ia32-msvc/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-CKs0Os7cDKa9GZANf4HbOgkQodjQ2GtJZBBwdZ7OaFMWmWet/0JCkakaF/+EUl0vx0QP83qpIK8LHEkYXxJItg==}
|
||||
/@next/swc-win32-ia32-msvc/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-Nk1DdvC+Ocdqnj4Ra+qWJK/PQ68hrWmSg3FXL4I3pooX2IZcUSF8nPFNS0r8V47inTAXbwatcFEKSBRjFBS2ww==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
@ -930,8 +932,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-x64-msvc/13.0.8-canary.0:
|
||||
resolution: {integrity: sha512-DTICRWenuqExpO3WmFzkhvYwKgLuPweb3eWiYeybSwHB6ji/cC5ZQjh3AvGbff548Ye8Z1bf4SUAIjdcg0Y/fA==}
|
||||
/@next/swc-win32-x64-msvc/13.1.1-canary.1:
|
||||
resolution: {integrity: sha512-/7q6tjUebSaUYTGZRpp4qAmrcL6+tiKfHN5YgW6zpX5MWLEk1DkdnuBjO/jSvCJd0510byBkN6drlzmfTMjzzg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
@ -1802,14 +1804,14 @@ packages:
|
|||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@wits/next-themes/0.2.14_rhfownvlqkszea7w3lnpwl7bzy:
|
||||
/@wits/next-themes/0.2.14_jcrpix7mbfpfu5movksylxa5c4:
|
||||
resolution: {integrity: sha512-fHKb/tRcWbYNblGHZtfvAQztDhzUB9d7ZkYOny0BisSPh6EABcsqxKB48ABUQztcmKywlp2zEMkLcSRj/PQBSw==}
|
||||
peerDependencies:
|
||||
next: '*'
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
dependencies:
|
||||
next: 13.0.8-canary.0_biqbaboplfbrettd7655fr4n2y
|
||||
next: 13.1.1-canary.1_biqbaboplfbrettd7655fr4n2y
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
@ -5215,7 +5217,7 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/next-auth/4.18.6_rhfownvlqkszea7w3lnpwl7bzy:
|
||||
/next-auth/4.18.6_jcrpix7mbfpfu5movksylxa5c4:
|
||||
resolution: {integrity: sha512-0TQwbq5X9Jyd1wUVYUoyvHJh4JWXeW9UOcMEl245Er/Y5vsSbyGJHt8M7xjRMzk9mORVMYehoMdERgyiq/jCgA==}
|
||||
engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0}
|
||||
peerDependencies:
|
||||
|
@ -5231,7 +5233,7 @@ packages:
|
|||
'@panva/hkdf': 1.0.2
|
||||
cookie: 0.5.0
|
||||
jose: 4.11.0
|
||||
next: 13.0.8-canary.0_biqbaboplfbrettd7655fr4n2y
|
||||
next: 13.1.1-canary.1_biqbaboplfbrettd7655fr4n2y
|
||||
oauth: 0.9.15
|
||||
openid-client: 5.3.0
|
||||
preact: 10.11.2
|
||||
|
@ -5241,6 +5243,18 @@ packages:
|
|||
uuid: 8.3.2
|
||||
dev: false
|
||||
|
||||
/next-themes/0.2.1_jcrpix7mbfpfu5movksylxa5c4:
|
||||
resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
|
||||
peerDependencies:
|
||||
next: '*'
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
dependencies:
|
||||
next: 13.1.1-canary.1_biqbaboplfbrettd7655fr4n2y
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/next-unused/0.0.6:
|
||||
resolution: {integrity: sha512-dHFNNBanFq4wvYrULtsjfWyZ6BzOnr5VYI9EYMGAZYF2vkAhFpj2JOuT5Wu2o3LbFSG92PmAZnSUF/LstF82pA==}
|
||||
hasBin: true
|
||||
|
@ -5252,8 +5266,8 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/next/13.0.8-canary.0_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-+LP4KZGBp+97TRgYExChOvoONZY1qfJmtB6IjG2HXDshgYpQmsAPEMy9r0rWbvhOveChCJ6sv+yEFAOCNc4yKQ==}
|
||||
/next/13.1.1-canary.1_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-20EeQyfGs9dGUAPrXAod5jay1plcM0itItL/7z9BMczYM55/it8TxS1OPTmseyM9Y8uuybTRoCHeKh6TCI09tg==}
|
||||
engines: {node: '>=14.6.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
|
@ -5270,27 +5284,27 @@ packages:
|
|||
sass:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@next/env': 13.0.8-canary.0
|
||||
'@next/env': 13.1.1-canary.1
|
||||
'@swc/helpers': 0.4.14
|
||||
caniuse-lite: 1.0.30001431
|
||||
postcss: 8.4.14
|
||||
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.1_react@18.2.0
|
||||
optionalDependencies:
|
||||
'@next/swc-android-arm-eabi': 13.0.8-canary.0
|
||||
'@next/swc-android-arm64': 13.0.8-canary.0
|
||||
'@next/swc-darwin-arm64': 13.0.8-canary.0
|
||||
'@next/swc-darwin-x64': 13.0.8-canary.0
|
||||
'@next/swc-freebsd-x64': 13.0.8-canary.0
|
||||
'@next/swc-linux-arm-gnueabihf': 13.0.8-canary.0
|
||||
'@next/swc-linux-arm64-gnu': 13.0.8-canary.0
|
||||
'@next/swc-linux-arm64-musl': 13.0.8-canary.0
|
||||
'@next/swc-linux-x64-gnu': 13.0.8-canary.0
|
||||
'@next/swc-linux-x64-musl': 13.0.8-canary.0
|
||||
'@next/swc-win32-arm64-msvc': 13.0.8-canary.0
|
||||
'@next/swc-win32-ia32-msvc': 13.0.8-canary.0
|
||||
'@next/swc-win32-x64-msvc': 13.0.8-canary.0
|
||||
'@next/swc-android-arm-eabi': 13.1.1-canary.1
|
||||
'@next/swc-android-arm64': 13.1.1-canary.1
|
||||
'@next/swc-darwin-arm64': 13.1.1-canary.1
|
||||
'@next/swc-darwin-x64': 13.1.1-canary.1
|
||||
'@next/swc-freebsd-x64': 13.1.1-canary.1
|
||||
'@next/swc-linux-arm-gnueabihf': 13.1.1-canary.1
|
||||
'@next/swc-linux-arm64-gnu': 13.1.1-canary.1
|
||||
'@next/swc-linux-arm64-musl': 13.1.1-canary.1
|
||||
'@next/swc-linux-x64-gnu': 13.1.1-canary.1
|
||||
'@next/swc-linux-x64-musl': 13.1.1-canary.1
|
||||
'@next/swc-win32-arm64-msvc': 13.1.1-canary.1
|
||||
'@next/swc-win32-ia32-msvc': 13.1.1-canary.1
|
||||
'@next/swc-win32-x64-msvc': 13.1.1-canary.1
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
@ -6589,8 +6603,8 @@ packages:
|
|||
inline-style-parser: 0.1.1
|
||||
dev: false
|
||||
|
||||
/styled-jsx/5.1.0_react@18.2.0:
|
||||
resolution: {integrity: sha512-/iHaRJt9U7T+5tp6TRelLnqBqiaIT0HsO0+vgyj8hK2KUk7aejFqRrumqPUlAqDwAj8IbS/1hk3IhBAAK/FCUQ==}
|
||||
/styled-jsx/5.1.1_react@18.2.0:
|
||||
resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': '*'
|
||||
|
|
1
src/types/next-auth.d.ts
vendored
1
src/types/next-auth.d.ts
vendored
|
@ -24,6 +24,5 @@ declare module "next-auth" {
|
|||
email?: string | null
|
||||
role?: string | null
|
||||
id: UserId
|
||||
displayName?: string | null
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue