Page/layout optimizations, bump next, styling fixes

This commit is contained in:
Max Leiter 2022-12-17 16:22:29 -08:00
parent f034f29a1d
commit ff310a67b9
28 changed files with 431 additions and 247 deletions

View file

@ -17,6 +17,7 @@ import Button from "@components/button"
import Input from "@components/input" import Input from "@components/input"
import ButtonDropdown from "@components/button-dropdown" import ButtonDropdown from "@components/button-dropdown"
import { useToasts } from "@components/toasts" import { useToasts } from "@components/toasts"
import { useSession } from "next-auth/react"
const emptyDoc = { const emptyDoc = {
title: "", title: "",
@ -37,6 +38,8 @@ const Post = ({
initialPost?: string initialPost?: string
newPostParent?: string newPostParent?: string
}) => { }) => {
const session = useSession()
const parsedPost = JSON.parse(stringifiedInitialPost || "{}") const parsedPost = JSON.parse(stringifiedInitialPost || "{}")
const initialPost = parsedPost?.id ? parsedPost : null const initialPost = parsedPost?.id ? parsedPost : null
const { setToast } = useToasts() const { setToast } = useToasts()
@ -140,6 +143,7 @@ const Post = ({
type: "error" type: "error"
}) })
hasErrored = true hasErrored = true
break
} }
} }
@ -182,6 +186,11 @@ const Post = ({
[] []
) )
if (session.status === "unauthenticated") {
router.push("/login")
return null
}
const updateDocTitle = (i: number) => (title: string) => { const updateDocTitle = (i: number) => (title: string) => {
setDocs((docs) => setDocs((docs) =>
docs.map((doc, index) => (i === index ? { ...doc, title } : doc)) docs.map((doc, index) => (i === index ? { ...doc, title } : doc))
@ -265,7 +274,6 @@ const Post = ({
/> />
<div className={styles.buttons}> <div className={styles.buttons}>
<Button <Button
className={styles.button}
onClick={() => { onClick={() => {
setDocs([ setDocs([
...docs, ...docs,

View file

@ -10,7 +10,6 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
margin-top: var(--gap-double);
gap: var(--gap); gap: var(--gap);
} }

View file

@ -22,8 +22,16 @@ const NewFromExisting = async ({
} }
const post = await getPostById(id, { const post = await getPostById(id, {
withFiles: true, include: {
withAuthor: false files: true,
author: false
},
select: {
authorId: true,
title: true,
description: true,
id: true,
}
}) })
const serialized = JSON.stringify(post) const serialized = JSON.stringify(post)

View file

@ -2,10 +2,5 @@ import { getCurrentUser } from "@lib/server/session"
import { redirect } from "next/navigation" import { redirect } from "next/navigation"
export default function NewLayout({ children }: { children: React.ReactNode }) { export default function NewLayout({ children }: { children: React.ReactNode }) {
const user = getCurrentUser()
if (!user) {
return redirect("/new")
}
return <>{children}</> return <>{children}</>
} }

View file

@ -4,3 +4,5 @@ import "./react-datepicker.css"
const New = () => <NewPost /> const New = () => <NewPost />
export default New export default New
export const dynamic = 'force-static'

View file

@ -14,16 +14,27 @@ type Props = {
isAuthor?: boolean isAuthor?: boolean
} }
const PostPage = ({ post: initialPost, isProtected, isAuthor: isAuthorFromServer }: Props) => { const PostFiles = ({
const { data: session } = useSession() post: _initialPost,
const [post, setPost] = useState<PostWithFilesAndAuthor>( isAuthor: isAuthorFromServer
typeof initialPost === "string" ? JSON.parse(initialPost) : initialPost }: Props) => {
) console.log("PostFiles")
const { data: session, status } = useSession()
const isLoading = status === "loading"
const initialPost =
typeof _initialPost === "string"
? (JSON.parse(_initialPost) as PostWithFilesAndAuthor)
: _initialPost
const [post, setPost] = useState<PostWithFilesAndAuthor>(initialPost)
const isProtected = initialPost.visibility === "protected"
console.log(_initialPost, post)
// We generate public and unlisted posts at build time, so we can't use // 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 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. // the post's authorId against the session's user id.
const isAuthor = isAuthorFromServer ? true : session?.user?.id === post?.authorId; const isAuthor = isAuthorFromServer
? true
: session?.user?.id === post?.authorId
const router = useRouter() const router = useRouter()
useEffect(() => { useEffect(() => {
@ -58,6 +69,15 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor: isAuthorFromServer
} }
} }
}, [isAuthor, post.expiresAt, router]) }, [isAuthor, post.expiresAt, router])
if (isLoading) {
return (
<DocumentComponent
skeleton={true}
initialTab={"preview"}
/>
)
}
if (isProtected) { if (isProtected) {
return <PasswordModalPage setPost={setPost} postId={post.id} /> return <PasswordModalPage setPost={setPost} postId={post.id} />
@ -67,6 +87,7 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor: isAuthorFromServer
<> <>
{post.files?.map(({ id, content, title, html }: File) => ( {post.files?.map(({ id, content, title, html }: File) => (
<DocumentComponent <DocumentComponent
skeleton={false}
key={id} key={id}
title={title} title={title}
initialTab={"preview"} initialTab={"preview"}
@ -79,4 +100,4 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor: isAuthorFromServer
) )
} }
export default PostPage export default PostFiles

View file

@ -1,3 +1,5 @@
"use client"
import { memo } from "react" import { memo } from "react"
import styles from "./document.module.css" import styles from "./document.module.css"
import Skeleton from "@components/skeleton" import Skeleton from "@components/skeleton"
@ -10,16 +12,24 @@ import DocumentTabs from "app/(posts)/components/tabs"
import Input from "@components/input" import Input from "@components/input"
import { Download, ExternalLink } from "react-feather" import { Download, ExternalLink } from "react-feather"
// import Link from "next/link" type SharedProps = {
type Props = { title?: string
title: string initialTab: "edit" | "preview"
initialTab?: "edit" | "preview" id?: string
skeleton?: boolean content?: string
id: string preview?: string
content: string
preview: string
} }
type Props = (
| {
skeleton?: true
}
| {
skeleton?: false
}
) &
SharedProps
const DownloadButtons = ({ rawLink }: { rawLink?: string }) => { const DownloadButtons = ({ rawLink }: { rawLink?: string }) => {
return ( return (
<div className={styles.actionWrapper}> <div className={styles.actionWrapper}>
@ -51,32 +61,29 @@ const DownloadButtons = ({ rawLink }: { rawLink?: string }) => {
) )
} }
const Document = ({ const Document = ({ skeleton, ...props }: Props) => {
content,
preview,
title,
initialTab = "edit",
skeleton,
id
}: Props) => {
if (skeleton) { if (skeleton) {
return ( return (
<> <>
<div className={styles.card}> <div className={styles.card}>
<div className={styles.fileNameContainer}> <div className={styles.fileNameContainer}>
<Skeleton width={275} height={36} /> <Skeleton width={'100%'} height={36} />
</div> </div>
<div className={styles.descriptionContainer}> <div className={styles.descriptionContainer}>
<div style={{ flexDirection: "row", display: "flex" }}> <Skeleton width={145} height={36} borderRadius={"4px 4px 0 0"} />
<Skeleton width={125} height={36} /> <Skeleton
</div> width={"100%"}
<Skeleton width={"100%"} height={350} /> height={350}
borderRadius={"0 0 4px 4px"}
/>
</div> </div>
</div> </div>
</> </>
) )
} }
const { title, initialTab, id, content, preview } = props
return ( return (
<> <>
<div className={styles.card}> <div className={styles.card}>

View file

@ -8,7 +8,12 @@ export default async function Head({
id: string id: string
} }
}) { }) {
const post = await getPostById(params.id) const post = await getPostById(params.id, {
select: {
title: true,
description: true
}
})
if (!post) { if (!post) {
return null return null

View file

@ -1,8 +1,5 @@
.root { .root {
padding-bottom: 200px;
display: flex;
flex-direction: column;
gap: var(--gap);
} }
@media screen and (max-width: 900px) { @media screen and (max-width: 900px) {

View file

@ -0,0 +1,159 @@
import { notFound, redirect } from "next/navigation"
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 "./layout.module.css"
import { PostTitle } from "./components/header/title"
import VisibilityControl from "@components/badges/visibility-control"
export type PostProps = {
post: Post
isProtected?: boolean
}
// export async function generateStaticParams() {
// const posts = await getAllPosts({
// where: {
// visibility: {
// equals: "public"
// }
// }
// })
// return posts.map((post) => ({
// id: post.id
// }))
// }
// export const dynamic = 'error';
const getPost = async (id: string) => {
const [post, user] = await Promise.all([
getPostById(id, {
select: {
visibility: true,
authorId: true,
title: true,
description: true,
id: true,
createdAt: true,
expiresAt: true,
parentId: true,
author: {
select: {
displayName: true,
image: true
}
},
files: {
select: {
id: true,
content: true,
updatedAt: true,
title: true
}
}
}
}).then((post) => {
if (!post) {
return notFound()
}
return post as PostWithFilesAndAuthor
}),
getCurrentUser()
])
const isAuthorOrAdmin = user?.id === post?.authorId || user?.role === "admin"
if (post.visibility === "public" || post.visibility === "unlisted") {
return { post, isAuthorOrAdmin }
}
if (post.visibility === "private" && !isAuthorOrAdmin) {
return redirect("/signin")
}
if (post.visibility === "protected" && !isAuthorOrAdmin) {
return {
// TODO: remove this. It's temporary to appease typescript
post: {
visibility: "protected",
id: post.id,
files: [],
parentId: "",
title: "",
createdAt: "",
expiresAt: "",
author: {
displayName: ""
},
description: "",
authorId: ""
},
isAuthor: isAuthorOrAdmin
}
}
// if expired
if (post.expiresAt && !isAuthorOrAdmin) {
const expirationDate = new Date(post.expiresAt)
if (expirationDate < new Date()) {
return redirect("/expired")
}
}
return { post, isAuthor: isAuthorOrAdmin }
}
export default async function PostLayout({
children,
params
}: {
params: {
id: string
}
children: React.ReactNode
}) {
const { post, isAuthor } = await getPost(params.id)
return (
<div className={styles.root}>
<div className={styles.header}>
<PostButtons
parentId={post.parentId || undefined}
postId={post.id}
files={post.files}
title={title}
/>
<PostTitle
title={post.title}
createdAt={post.createdAt.toString()}
expiresAt={post.expiresAt?.toString()}
// displayName is an optional param
displayName={post.author?.displayName || undefined}
visibility={post.visibility}
authorId={post.authorId}
/>
</div>
{post.description && (
<div>
<p>{post.description}</p>
</div>
)}
{isAuthor && (
<span className={styles.controls}>
<VisibilityControl postId={post.id} visibility={post.visibility} />
</span>
)}
<ScrollToTop />
{children}
</div>
)
}

View file

@ -1,6 +1,7 @@
import { PostButtons } from "./components/header/post-buttons" import { PostButtons } from "./components/header/post-buttons"
import { PostTitle } from "./components/header/title" import { PostTitle } from "./components/header/title"
import styles from "./styles.module.css" import DocumentComponent from "./components/post-files/view-document"
import styles from "./layout.module.css"
export default function PostLoading() { export default function PostLoading() {
return ( return (
@ -8,6 +9,7 @@ export default function PostLoading() {
<div className={styles.header}> <div className={styles.header}>
<PostButtons loading title="" /> <PostButtons loading title="" />
<PostTitle title="" loading /> <PostTitle title="" loading />
<DocumentComponent skeleton initialTab="preview" />
</div> </div>
</> </>
) )

View file

@ -1,55 +1,59 @@
import PostPage from "./components/post-page" import { getPostById } from "@lib/server/prisma"
import { notFound, redirect } from "next/navigation"
import { getAllPosts, getPostById, Post, PostWithFilesAndAuthor } from "@lib/server/prisma"
import { getCurrentUser } from "@lib/server/session" import { getCurrentUser } from "@lib/server/session"
import ScrollToTop from "@components/scroll-to-top" import { notFound, redirect } from "next/navigation"
import { title } from "process" import PostFiles from "./components/post-files"
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"
}
}
})
return posts.map((post) => ({
id: post.id
}))
}
export const dynamic = 'error';
const fetchOptions = {
withFiles: true,
withAuthor: true
}
const getPost = async (id: string) => { const getPost = async (id: string) => {
const post = (await getPostById(id, fetchOptions)) as PostWithFilesAndAuthor const [post, user] = await Promise.all([
getPostById(id, {
select: {
visibility: true,
authorId: true,
title: true,
description: true,
id: true,
createdAt: true,
expiresAt: true,
parentId: true,
author: {
select: {
displayName: true,
image: true
}
},
files: {
select: {
id: true,
content: true,
updatedAt: true,
title: true,
html: true,
}
}
}
}).then((post) => {
if (!post) {
return notFound()
}
return post
}),
getCurrentUser()
])
if (!post) { const isAuthorOrAdmin = user?.id === post?.authorId || user?.role === "admin"
return notFound()
// if expired
if (post.expiresAt && !isAuthorOrAdmin) {
const expirationDate = new Date(post.expiresAt)
if (expirationDate < new Date()) {
return redirect("/expired")
}
} }
if (post.visibility === "public" || post.visibility === "unlisted") { if (post.visibility === "public" || post.visibility === "unlisted") {
return { post } return { post, isAuthorOrAdmin }
} }
const user = await getCurrentUser()
const isAuthorOrAdmin = user?.id === post?.authorId || user?.role === "admin"
if (post.visibility === "private" && !isAuthorOrAdmin) { if (post.visibility === "private" && !isAuthorOrAdmin) {
return redirect("/signin") return redirect("/signin")
} }
@ -71,70 +75,21 @@ const getPost = async (id: string) => {
description: "", description: "",
authorId: "" authorId: ""
}, },
isProtected: true,
isAuthor: isAuthorOrAdmin 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 } return { post, isAuthor: isAuthorOrAdmin }
} }
const PostView = async ({ export default async function PostPage({
params params
}: { }: {
params: { params: {
id: string id: string
} }
}) => { }) {
const { post, isProtected, isAuthor } = await getPost(params.id) const { post, isAuthor } = await getPost(params.id)
// TODO: serialize dates in prisma middleware instead of passing as JSON
const stringifiedPost = JSON.stringify(post) const stringifiedPost = JSON.stringify(post)
return <PostFiles post={stringifiedPost} isAuthor={isAuthor} />
return (
<div className={styles.root}>
<div className={styles.header}>
<PostButtons
parentId={post.parentId || undefined}
postId={post.id}
files={post.files}
title={title}
/>
<PostTitle
title={post.title}
createdAt={post.createdAt.toString()}
expiresAt={post.expiresAt?.toString()}
// displayName is an optional param
displayName={post.author?.displayName || undefined}
visibility={post.visibility}
authorId={post.authorId}
/>
</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 />
</div>
)
} }
export default PostView

View file

@ -41,7 +41,11 @@ const Button = forwardRef<HTMLButtonElement, Props>(
return ( return (
<button <button
ref={ref} ref={ref}
className={`${styles.button} ${styles[type]} ${className || ""}`} className={clsx(
styles.button,
type === "primary" || (type === "secondary" && styles[type]),
className
)}
disabled={disabled || loading} disabled={disabled || loading}
onClick={onClick} onClick={onClick}
style={{ height, width, margin, padding }} style={{ height, width, margin, padding }}

View file

@ -5,7 +5,7 @@ export default function Card({
className, className,
...props ...props
}: { }: {
children: React.ReactNode children?: React.ReactNode
className?: string className?: string
} & React.ComponentProps<"div">) { } & React.ComponentProps<"div">) {
return ( return (

View file

@ -2,7 +2,6 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
width: 100%; width: 100%;
height: 24px;
justify-content: flex-end; justify-content: flex-end;
} }

View file

@ -1,13 +1,29 @@
"use client"
import Card from "@components/card" import Card from "@components/card"
import styles from "./settings-group.module.css" import styles from "./settings-group.module.css"
type Props = { type Props =
title: string | {
children: React.ReactNode | React.ReactNode[] skeleton: true
} title?: string
children?: React.ReactNode
}
| {
skeleton?: false
title: string
children: React.ReactNode
}
const SettingsGroup = ({ title, children, skeleton }: Props) => {
if (skeleton) {
return (
<Card
style={{
height: 365
}}
/>
)
}
const SettingsGroup = ({ title, children }: Props) => {
return ( return (
<Card> <Card>
<h4>{title}</h4> <h4>{title}</h4>

View file

@ -2,10 +2,12 @@ import styles from "./skeleton.module.css"
export default function Skeleton({ export default function Skeleton({
width = 100, width = 100,
height = 24 height = 24,
borderRadius = 4,
}: { }: {
width?: number | string width?: number | string
height?: number | string height?: number | string,
borderRadius?: number | string
}) { }) {
return <div className={styles.skeleton} style={{ width, height }} /> return <div className={styles.skeleton} style={{ width, height, borderRadius }} />
} }

View file

@ -11,7 +11,7 @@ import styles from "./profile.module.css"
const Profile = ({ user }: { user: User }) => { const Profile = ({ user }: { user: User }) => {
// TODO: make this displayName, requires fetching user from DB as session doesnt have it // TODO: make this displayName, requires fetching user from DB as session doesnt have it
const [name, setName] = useState<string>(user.name || "") const [name, setName] = useState<string>(user.name || "")
const [bio, setBio] = useState<string>() const [submitting, setSubmitting] = useState<boolean>(false)
const { setToast } = useToasts() const { setToast } = useToasts()
@ -19,23 +19,19 @@ const Profile = ({ user }: { user: User }) => {
setName(e.target.value) setName(e.target.value)
} }
const handleBioChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setBio(e.target.value)
}
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => { const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault() e.preventDefault()
if (!name && !bio) { if (!name) {
setToast({ setToast({
message: "Please fill out at least one field", message: "Please fill out at least one field",
type: "error" type: "error"
}) })
return return
} }
setSubmitting(true)
const data = { const data = {
displayName: name, displayName: name,
bio
} }
const res = await fetch(`/api/user/${user.id}`, { const res = await fetch(`/api/user/${user.id}`, {
@ -46,6 +42,8 @@ const Profile = ({ user }: { user: User }) => {
body: JSON.stringify(data) body: JSON.stringify(data)
}) })
setSubmitting(false)
if (res.status === 200) { if (res.status === 200) {
setToast({ setToast({
message: "Profile updated", message: "Profile updated",
@ -90,7 +88,7 @@ const Profile = ({ user }: { user: User }) => {
aria-label="Email" aria-label="Email"
/> />
</div> </div>
<Button type="submit">Submit</Button> <Button type="submit" loading={submitting}>Submit</Button>
</form> </form>
</> </>
) )

View file

@ -0,0 +1,21 @@
export default function SettingsLayout({
children
}: {
children: React.ReactNode
}) {
return (
<>
<h1>Settings</h1>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "var(--gap)",
marginBottom: "var(--gap)"
}}
>
{children}
</div>
</>
)
}

View file

@ -0,0 +1,5 @@
import SettingsGroup from "@components/settings-group"
export default function SettingsLoading() {
return <SettingsGroup skeleton />
}

View file

@ -12,21 +12,8 @@ export default async function SettingsPage() {
} }
return ( return (
<div <SettingsGroup title="Profile">
style={{ <Profile user={user} />
display: "flex", </SettingsGroup>
flexDirection: "column",
gap: "var(--gap)",
marginBottom: "var(--gap)"
}}
>
<h1>Settings</h1>
<SettingsGroup title="Profile">
<Profile user={user} />
</SettingsGroup>
{/* <SettingsGroup title="Password">
<Password />
</SettingsGroup> */}
</div>
) )
} }

View file

@ -138,10 +138,11 @@ export const createUser = async (
} }
} }
type GetPostByIdOptions = { // all of prisma.post.findUnique
withFiles: boolean type GetPostByIdOptions = Pick<
withAuthor: boolean Prisma.PostFindUniqueArgs,
} "include" | "rejectOnNotFound" | "select"
>
export const getPostById = async ( export const getPostById = async (
postId: Post["id"], postId: Post["id"],
@ -151,17 +152,7 @@ export const getPostById = async (
where: { where: {
id: postId id: postId
}, },
include: { ...options
files: options?.withFiles,
author: options?.withAuthor
? {
select: {
id: true,
displayName: true
}
}
: false
}
}) })
return post return post

View file

@ -5,7 +5,8 @@ const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
experimental: { experimental: {
// esmExternals: true, // esmExternals: true,
appDir: true appDir: true,
serverComponentsExternalPackages: ['prisma'],
}, },
output: "standalone", output: "standalone",
async rewrites() { async rewrites() {

View file

@ -28,7 +28,7 @@
"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.7-canary.4", "next": "13.0.8-canary.0",
"next-auth": "^4.18.4", "next-auth": "^4.18.4",
"prisma": "^4.7.1", "prisma": "^4.7.1",
"react": "18.2.0", "react": "18.2.0",

View file

@ -23,8 +23,10 @@ async function handleGet(req: NextApiRequest, res: NextApiResponse<any>) {
} }
const post = await getPostById(id, { const post = await getPostById(id, {
withFiles: Boolean(files), include: {
withAuthor: true files: true,
author: true
}
}) })
if (!post) { if (!post) {

View file

@ -28,7 +28,7 @@ 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.7-canary.4 next: 13.0.8-canary.0
next-auth: ^4.18.4 next-auth: ^4.18.4
next-unused: 0.0.6 next-unused: 0.0.6
prettier: 2.6.2 prettier: 2.6.2
@ -56,14 +56,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_cn2wvw6rkm76gy2h2qfs2itv2u '@wits/next-themes': 0.2.14_rhfownvlqkszea7w3lnpwl7bzy
bcrypt: 5.1.0 bcrypt: 5.1.0
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.7-canary.4_biqbaboplfbrettd7655fr4n2y next: 13.0.8-canary.0_biqbaboplfbrettd7655fr4n2y
next-auth: 4.18.4_cn2wvw6rkm76gy2h2qfs2itv2u next-auth: 4.18.4_rhfownvlqkszea7w3lnpwl7bzy
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
@ -800,7 +800,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.4_cn2wvw6rkm76gy2h2qfs2itv2u next-auth: 4.18.4_rhfownvlqkszea7w3lnpwl7bzy
dev: false dev: false
/@next/bundle-analyzer/13.0.7-canary.4: /@next/bundle-analyzer/13.0.7-canary.4:
@ -812,8 +812,8 @@ packages:
- utf-8-validate - utf-8-validate
dev: true dev: true
/@next/env/13.0.7-canary.4: /@next/env/13.0.8-canary.0:
resolution: {integrity: sha512-PEFzHZkan5SJtxAL+0TmL4Vx3BJp4tDbCnba/2H3CeW0hVEck0e+UY7UGSXwyBaojD6DwUtgHHN3tu2yd2x51w==} resolution: {integrity: sha512-IiZM9mAUE9F3p9q/ydZBGlvmleOaMO6fBDBJzvQa4t3Ezg5e3NfGlTO01MTWvKPEKYPeAwFp+tcVh9ivA28+Dw==}
dev: false dev: false
/@next/eslint-plugin-next/13.0.3: /@next/eslint-plugin-next/13.0.3:
@ -828,8 +828,8 @@ packages:
glob: 7.1.7 glob: 7.1.7
dev: false dev: false
/@next/swc-android-arm-eabi/13.0.7-canary.4: /@next/swc-android-arm-eabi/13.0.8-canary.0:
resolution: {integrity: sha512-mXpIUvBXaxYD/tI6FUuRKF0JPs03dBCQAtmZjG7hRISpnFWij1wgm+NewmXEZ7EmWIIstc+vTgP0Akzqfz6abg==} resolution: {integrity: sha512-U6nayRvWuASLLBwqG4nN9540ako+JEBblN8479BpGvW1F2FyQPUx/zq+WO0b47KPyJI2XNPBIenHGvtSY7yN/Q==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm] cpu: [arm]
os: [android] os: [android]
@ -837,8 +837,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-android-arm64/13.0.7-canary.4: /@next/swc-android-arm64/13.0.8-canary.0:
resolution: {integrity: sha512-sht0AvPUp4667+QwWLj4zAcTEFoxmAH5sYRddSIrBpMa2fE0FT6P4ZInJ5eSlxrS+Ag9ehRq09kb+y9j8Ci/ZQ==} resolution: {integrity: sha512-GtUW5CCIfN1FUln+pRm0rAWe8k957rcKhYDPGBrfr+jaKvUgjI4NgMcXRJ0R83j+vcM4+DIhIkIO+OYQ1vU4RA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
@ -846,8 +846,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-darwin-arm64/13.0.7-canary.4: /@next/swc-darwin-arm64/13.0.8-canary.0:
resolution: {integrity: sha512-8WhDzOW2byCRde6bgMigqzVE0Uihhg9hicm2IzR81quoLuPU9UBbk7OCFSUg3OqQjjmzo2ZChYq85bouRMjpJg==} resolution: {integrity: sha512-dqUn4ERXHT+g/L+paIi+IhNP3P7HiF95ZBIjQvn++n0IhdT8rRfaQK3ubps/NopL14jHA33J7HnK73vgUBIvwg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
@ -855,8 +855,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-darwin-x64/13.0.7-canary.4: /@next/swc-darwin-x64/13.0.8-canary.0:
resolution: {integrity: sha512-MWItc0vAwDFpW6kR+aPWhTj2Q0VaqKrWOemv28wv3Wv3kwCxheSzWImOkcGPn5eTnCRfn0Cn2b/+VHQ1tH79tg==} resolution: {integrity: sha512-jGaI2idOd2Z0Dvlnz0WYHC+hbqQPIhaso/upJQebknWeu1VsSrwH5oDbCgMBaXLkHO7rMOITWC5FjxdXjSGK6g==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
@ -864,8 +864,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-freebsd-x64/13.0.7-canary.4: /@next/swc-freebsd-x64/13.0.8-canary.0:
resolution: {integrity: sha512-mFoYF/rEi6Vd1RF6L3rNdYzwXATwqXUmbhY9Brav4JrJ9TQmf8GiZz0itn65J1QDuRw3rD+czKJ/HxwSvCNUMA==} resolution: {integrity: sha512-ieM8XwqX9m/frFGpSwrXubzZYPT+ZzOEJsDgCNo3CD0DGHu8hZz1XLRym0Nl2mZAnBlxgENN+RlGwutWKBQMHg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
@ -873,8 +873,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm-gnueabihf/13.0.7-canary.4: /@next/swc-linux-arm-gnueabihf/13.0.8-canary.0:
resolution: {integrity: sha512-+uxKr1/BXvZjTeIFnue85pdDxnneD9lCvvfkIjcY2EIToTMOhTtqlyosXblENTL7uM+q26lhKOepRDttG9L2cQ==} resolution: {integrity: sha512-/9CnPhcqu/kudpk07zCkApFRUwF0wbwdFm5CqtguZ6yubqhoAV1Wjmrs1gnt+MUBHsVnKRdoGkz6KupQEZqz7g==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
@ -882,8 +882,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-gnu/13.0.7-canary.4: /@next/swc-linux-arm64-gnu/13.0.8-canary.0:
resolution: {integrity: sha512-WVqK6/jXVjWePvIaOOgNHO6P8LUntvLuay6clhdBzAzArEStG1RRoupAnuqz9VXyFtHguRthejQhFAqYLq9jqw==} resolution: {integrity: sha512-KUQs6KdX3lMxJu4ym/jNzotQvbkpXD/ne8KgjUuzTdgw3LYSfEMsTzORj71IR48H5yMDSLGPvCJA+B8FuVzS8Q==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -891,8 +891,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-musl/13.0.7-canary.4: /@next/swc-linux-arm64-musl/13.0.8-canary.0:
resolution: {integrity: sha512-LEu82umP+iLyP710qYA37UT6FFhcKZ7z9GmitFZxE9Jj6wenOFZHkkFDm624pJyAhfLcbLdT5MYqIgXIuwloDg==} resolution: {integrity: sha512-bisV2RUrsQMJodK2xGszfqK9G/BuDlqVLeDZVrOENWaZnOVDtrP+WlqrN0vS1r8xn/OepJWKkMnibO4aLCruvw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -900,8 +900,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-gnu/13.0.7-canary.4: /@next/swc-linux-x64-gnu/13.0.8-canary.0:
resolution: {integrity: sha512-ycoLEMKaNM/T/iplgVsjYFpYE7tTh/UNaBxuFnsxBLuQJJET26mCWNPjM7CpiDWZVhdFwD5ad9PI3+HeOnSgSg==} resolution: {integrity: sha512-X8pcTN7nPZ7gDXb04oGWOS/qPvPaPK5x753AmevQgVa7FwqXQ6IkJeD3sr8ergmu6Fcfr6c4LcnBEQzpIxOxYA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -909,8 +909,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-musl/13.0.7-canary.4: /@next/swc-linux-x64-musl/13.0.8-canary.0:
resolution: {integrity: sha512-xR22ldRvmg0l5bJbuK11iNFXAKS8+cgbkQZeHizYM5ngWOIEz8WQWn01+9sBeydtQZbvySpWHfm/Ev3XFEJHOw==} resolution: {integrity: sha512-Kg+tsnDmQ21qYfLik3YH+ZOYMmoNyhYqLMZE6qSASA5uN448J1cJUHIdpJxUpidZHtWBV+kTVR2Hw7+We+BiKQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -918,8 +918,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-arm64-msvc/13.0.7-canary.4: /@next/swc-win32-arm64-msvc/13.0.8-canary.0:
resolution: {integrity: sha512-psFkg4qGIx85tqDu9f7vl1jgLumzGsCdVL+OvLWY9opASDNaSYMp0xcwKT1BvGYpY8sGs81Q21yVrGl4sa8vvA==} resolution: {integrity: sha512-tde5+ZQFT0+Pr/BKINQ32+8C/AEaZLzU69AvpD7dvbUEJ5fReIiSBPIL1ov3pZYR+EPwl7wFPoj7NLxTU1E8WA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
@ -927,8 +927,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-ia32-msvc/13.0.7-canary.4: /@next/swc-win32-ia32-msvc/13.0.8-canary.0:
resolution: {integrity: sha512-4b0snVdqN8wDQMucJDbM759sC1txN/PwtaTn/rgcBaybXBFlYBc01mj3ePWcGcUj0378+FSKZWfo1+ldYBurFw==} resolution: {integrity: sha512-CKs0Os7cDKa9GZANf4HbOgkQodjQ2GtJZBBwdZ7OaFMWmWet/0JCkakaF/+EUl0vx0QP83qpIK8LHEkYXxJItg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
@ -936,8 +936,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-x64-msvc/13.0.7-canary.4: /@next/swc-win32-x64-msvc/13.0.8-canary.0:
resolution: {integrity: sha512-RbxWU5fswTBBuI2o7hHJ2KgSUAS73pw0RNMI387eGfh7Nmo5QQg9DCUJFXQlxCWUmvC+mvYPwMFWsolYdEd++A==} resolution: {integrity: sha512-DTICRWenuqExpO3WmFzkhvYwKgLuPweb3eWiYeybSwHB6ji/cC5ZQjh3AvGbff548Ye8Z1bf4SUAIjdcg0Y/fA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@ -1729,14 +1729,14 @@ packages:
- supports-color - supports-color
dev: false dev: false
/@wits/next-themes/0.2.14_cn2wvw6rkm76gy2h2qfs2itv2u: /@wits/next-themes/0.2.14_rhfownvlqkszea7w3lnpwl7bzy:
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.7-canary.4_biqbaboplfbrettd7655fr4n2y next: 13.0.8-canary.0_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
@ -5229,7 +5229,7 @@ packages:
dev: true dev: true
optional: true optional: true
/next-auth/4.18.4_cn2wvw6rkm76gy2h2qfs2itv2u: /next-auth/4.18.4_rhfownvlqkszea7w3lnpwl7bzy:
resolution: {integrity: sha512-tvXOabxv5U/y6ib56XPkOnc/48tYc+xT6GNOLREIme8WVGYHDTc3CGEfe2+0bVCWAm0ax/GYXH0By5NFoaJDww==} resolution: {integrity: sha512-tvXOabxv5U/y6ib56XPkOnc/48tYc+xT6GNOLREIme8WVGYHDTc3CGEfe2+0bVCWAm0ax/GYXH0By5NFoaJDww==}
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:
@ -5245,7 +5245,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.7-canary.4_biqbaboplfbrettd7655fr4n2y next: 13.0.8-canary.0_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
@ -5266,8 +5266,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/next/13.0.7-canary.4_biqbaboplfbrettd7655fr4n2y: /next/13.0.8-canary.0_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-cx0ST4A/ZYB5eqygzx59cW4/xhVBanPhDr7JytkzZJ/HUCX67VR/ho+rcekB/C/ZNsefYukhMrep6vwfidV95A==} resolution: {integrity: sha512-+LP4KZGBp+97TRgYExChOvoONZY1qfJmtB6IjG2HXDshgYpQmsAPEMy9r0rWbvhOveChCJ6sv+yEFAOCNc4yKQ==}
engines: {node: '>=14.6.0'} engines: {node: '>=14.6.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -5284,7 +5284,7 @@ packages:
sass: sass:
optional: true optional: true
dependencies: dependencies:
'@next/env': 13.0.7-canary.4 '@next/env': 13.0.8-canary.0
'@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
@ -5292,19 +5292,19 @@ packages:
react-dom: 18.2.0_react@18.2.0 react-dom: 18.2.0_react@18.2.0
styled-jsx: 5.1.0_react@18.2.0 styled-jsx: 5.1.0_react@18.2.0
optionalDependencies: optionalDependencies:
'@next/swc-android-arm-eabi': 13.0.7-canary.4 '@next/swc-android-arm-eabi': 13.0.8-canary.0
'@next/swc-android-arm64': 13.0.7-canary.4 '@next/swc-android-arm64': 13.0.8-canary.0
'@next/swc-darwin-arm64': 13.0.7-canary.4 '@next/swc-darwin-arm64': 13.0.8-canary.0
'@next/swc-darwin-x64': 13.0.7-canary.4 '@next/swc-darwin-x64': 13.0.8-canary.0
'@next/swc-freebsd-x64': 13.0.7-canary.4 '@next/swc-freebsd-x64': 13.0.8-canary.0
'@next/swc-linux-arm-gnueabihf': 13.0.7-canary.4 '@next/swc-linux-arm-gnueabihf': 13.0.8-canary.0
'@next/swc-linux-arm64-gnu': 13.0.7-canary.4 '@next/swc-linux-arm64-gnu': 13.0.8-canary.0
'@next/swc-linux-arm64-musl': 13.0.7-canary.4 '@next/swc-linux-arm64-musl': 13.0.8-canary.0
'@next/swc-linux-x64-gnu': 13.0.7-canary.4 '@next/swc-linux-x64-gnu': 13.0.8-canary.0
'@next/swc-linux-x64-musl': 13.0.7-canary.4 '@next/swc-linux-x64-musl': 13.0.8-canary.0
'@next/swc-win32-arm64-msvc': 13.0.7-canary.4 '@next/swc-win32-arm64-msvc': 13.0.8-canary.0
'@next/swc-win32-ia32-msvc': 13.0.7-canary.4 '@next/swc-win32-ia32-msvc': 13.0.8-canary.0
'@next/swc-win32-x64-msvc': 13.0.7-canary.4 '@next/swc-win32-x64-msvc': 13.0.8-canary.0
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros