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

View file

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

View file

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

View file

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

View file

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

View file

@ -14,16 +14,27 @@ type Props = {
isAuthor?: boolean
}
const PostPage = ({ post: initialPost, isProtected, isAuthor: isAuthorFromServer }: Props) => {
const { data: session } = useSession()
const [post, setPost] = useState<PostWithFilesAndAuthor>(
typeof initialPost === "string" ? JSON.parse(initialPost) : initialPost
)
const PostFiles = ({
post: _initialPost,
isAuthor: isAuthorFromServer
}: 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
// 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 isAuthor = isAuthorFromServer
? true
: session?.user?.id === post?.authorId
const router = useRouter()
useEffect(() => {
@ -58,6 +69,15 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor: isAuthorFromServer
}
}
}, [isAuthor, post.expiresAt, router])
if (isLoading) {
return (
<DocumentComponent
skeleton={true}
initialTab={"preview"}
/>
)
}
if (isProtected) {
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) => (
<DocumentComponent
skeleton={false}
key={id}
title={title}
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 styles from "./document.module.css"
import Skeleton from "@components/skeleton"
@ -10,16 +12,24 @@ import DocumentTabs from "app/(posts)/components/tabs"
import Input from "@components/input"
import { Download, ExternalLink } from "react-feather"
// import Link from "next/link"
type Props = {
title: string
initialTab?: "edit" | "preview"
skeleton?: boolean
id: string
content: string
preview: string
type SharedProps = {
title?: string
initialTab: "edit" | "preview"
id?: string
content?: string
preview?: string
}
type Props = (
| {
skeleton?: true
}
| {
skeleton?: false
}
) &
SharedProps
const DownloadButtons = ({ rawLink }: { rawLink?: string }) => {
return (
<div className={styles.actionWrapper}>
@ -51,32 +61,29 @@ const DownloadButtons = ({ rawLink }: { rawLink?: string }) => {
)
}
const Document = ({
content,
preview,
title,
initialTab = "edit",
skeleton,
id
}: Props) => {
const Document = ({ skeleton, ...props }: Props) => {
if (skeleton) {
return (
<>
<div className={styles.card}>
<div className={styles.fileNameContainer}>
<Skeleton width={275} height={36} />
<Skeleton width={'100%'} height={36} />
</div>
<div className={styles.descriptionContainer}>
<div style={{ flexDirection: "row", display: "flex" }}>
<Skeleton width={125} height={36} />
</div>
<Skeleton width={"100%"} height={350} />
<Skeleton width={145} height={36} borderRadius={"4px 4px 0 0"} />
<Skeleton
width={"100%"}
height={350}
borderRadius={"0 0 4px 4px"}
/>
</div>
</div>
</>
)
}
const { title, initialTab, id, content, preview } = props
return (
<>
<div className={styles.card}>

View file

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

View file

@ -1,8 +1,5 @@
.root {
padding-bottom: 200px;
display: flex;
flex-direction: column;
gap: var(--gap);
}
@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 { 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() {
return (
@ -8,6 +9,7 @@ export default function PostLoading() {
<div className={styles.header}>
<PostButtons loading title="" />
<PostTitle title="" loading />
<DocumentComponent skeleton initialTab="preview" />
</div>
</>
)

View file

@ -1,55 +1,59 @@
import PostPage from "./components/post-page"
import { notFound, redirect } from "next/navigation"
import { getAllPosts, getPostById, Post, PostWithFilesAndAuthor } from "@lib/server/prisma"
import { getPostById } 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"
}
}
})
return posts.map((post) => ({
id: post.id
}))
}
export const dynamic = 'error';
const fetchOptions = {
withFiles: true,
withAuthor: true
}
import { notFound, redirect } from "next/navigation"
import PostFiles from "./components/post-files"
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) {
return notFound()
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.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) {
return redirect("/signin")
}
@ -71,70 +75,21 @@ const getPost = async (id: string) => {
description: "",
authorId: ""
},
isProtected: true,
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 }
}
const PostView = async ({
export default async function PostPage({
params
}: {
params: {
id: string
}
}) => {
const { post, isProtected, isAuthor } = await getPost(params.id)
// TODO: serialize dates in prisma middleware instead of passing as JSON
}) {
const { post, isAuthor } = await getPost(params.id)
const stringifiedPost = JSON.stringify(post)
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>
)
return <PostFiles post={stringifiedPost} isAuthor={isAuthor} />
}
export default PostView

View file

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

View file

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

View file

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

View file

@ -1,13 +1,29 @@
"use client"
import Card from "@components/card"
import styles from "./settings-group.module.css"
type Props = {
title: string
children: React.ReactNode | React.ReactNode[]
}
type Props =
| {
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 (
<Card>
<h4>{title}</h4>

View file

@ -2,10 +2,12 @@ import styles from "./skeleton.module.css"
export default function Skeleton({
width = 100,
height = 24
height = 24,
borderRadius = 4,
}: {
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 }) => {
// TODO: make this displayName, requires fetching user from DB as session doesnt have it
const [name, setName] = useState<string>(user.name || "")
const [bio, setBio] = useState<string>()
const [submitting, setSubmitting] = useState<boolean>(false)
const { setToast } = useToasts()
@ -19,23 +19,19 @@ const Profile = ({ user }: { user: User }) => {
setName(e.target.value)
}
const handleBioChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setBio(e.target.value)
}
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (!name && !bio) {
if (!name) {
setToast({
message: "Please fill out at least one field",
type: "error"
})
return
}
setSubmitting(true)
const data = {
displayName: name,
bio
}
const res = await fetch(`/api/user/${user.id}`, {
@ -46,6 +42,8 @@ const Profile = ({ user }: { user: User }) => {
body: JSON.stringify(data)
})
setSubmitting(false)
if (res.status === 200) {
setToast({
message: "Profile updated",
@ -90,7 +88,7 @@ const Profile = ({ user }: { user: User }) => {
aria-label="Email"
/>
</div>
<Button type="submit">Submit</Button>
<Button type="submit" loading={submitting}>Submit</Button>
</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 (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "var(--gap)",
marginBottom: "var(--gap)"
}}
>
<h1>Settings</h1>
<SettingsGroup title="Profile">
<Profile user={user} />
</SettingsGroup>
{/* <SettingsGroup title="Password">
<Password />
</SettingsGroup> */}
</div>
<SettingsGroup title="Profile">
<Profile user={user} />
</SettingsGroup>
)
}

View file

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

View file

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

View file

@ -28,7 +28,7 @@
"client-zip": "2.2.1",
"jest": "^29.3.1",
"lodash.debounce": "^4.0.8",
"next": "13.0.7-canary.4",
"next": "13.0.8-canary.0",
"next-auth": "^4.18.4",
"prisma": "^4.7.1",
"react": "18.2.0",

View file

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

View file

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