Page/layout optimizations, bump next, styling fixes
This commit is contained in:
parent
f034f29a1d
commit
ff310a67b9
28 changed files with 431 additions and 247 deletions
|
@ -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,
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-top: var(--gap-double);
|
||||
gap: var(--gap);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}</>
|
||||
}
|
||||
|
|
|
@ -4,3 +4,5 @@ import "./react-datepicker.css"
|
|||
const New = () => <NewPost />
|
||||
|
||||
export default New
|
||||
|
||||
export const dynamic = 'force-static'
|
||||
|
|
|
@ -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(() => {
|
||||
|
@ -59,6 +70,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
|
|
@ -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}>
|
|
@ -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
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
.root {
|
||||
padding-bottom: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap);
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
159
src/app/(posts)/post/[id]/layout.tsx
Normal file
159
src/app/(posts)/post/[id]/layout.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,54 +1,58 @@
|
|||
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.visibility === "public" || post.visibility === "unlisted") {
|
||||
return { post }
|
||||
}
|
||||
|
||||
const user = await getCurrentUser()
|
||||
const isAuthorOrAdmin = user?.id === post?.authorId || user?.role === "admin"
|
||||
|
||||
// if expired
|
||||
if (post.expiresAt && !isAuthorOrAdmin) {
|
||||
const expirationDate = new Date(post.expiresAt)
|
||||
if (expirationDate < new Date()) {
|
||||
return redirect("/expired")
|
||||
}
|
||||
}
|
||||
|
||||
if (post.visibility === "public" || post.visibility === "unlisted") {
|
||||
return { post, isAuthorOrAdmin }
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -5,7 +5,7 @@ export default function Card({
|
|||
className,
|
||||
...props
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
} & React.ComponentProps<"div">) {
|
||||
return (
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
"use client"
|
||||
import Card from "@components/card"
|
||||
import styles from "./settings-group.module.css"
|
||||
|
||||
type Props = {
|
||||
type Props =
|
||||
| {
|
||||
skeleton: true
|
||||
title?: string
|
||||
children?: React.ReactNode
|
||||
}
|
||||
| {
|
||||
skeleton?: false
|
||||
title: string
|
||||
children: React.ReactNode | React.ReactNode[]
|
||||
}
|
||||
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>
|
||||
|
|
|
@ -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 }} />
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
)
|
||||
|
|
21
src/app/settings/layout.tsx
Normal file
21
src/app/settings/layout.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
5
src/app/settings/loading.tsx
Normal file
5
src/app/settings/loading.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import SettingsGroup from "@components/settings-group"
|
||||
|
||||
export default function SettingsLoading() {
|
||||
return <SettingsGroup skeleton />
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,7 +5,8 @@ const nextConfig = {
|
|||
reactStrictMode: true,
|
||||
experimental: {
|
||||
// esmExternals: true,
|
||||
appDir: true
|
||||
appDir: true,
|
||||
serverComponentsExternalPackages: ['prisma'],
|
||||
},
|
||||
output: "standalone",
|
||||
async rewrites() {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue