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