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