From a4590a2886e26dd67bb09b1c63e1eeffb6c41321 Mon Sep 17 00:00:00 2001 From: Sang Dang Date: Sat, 16 Sep 2023 00:47:45 +0300 Subject: [PATCH 1/4] made the post dashboard view and improve the login a bit --- app/[locale]/(auth)/login/LoginForm/index.tsx | 55 +++--- app/[locale]/(auth)/login/page.tsx | 3 +- .../(auth)/signup/SignUpForm/index.tsx | 50 +++--- app/[locale]/(auth)/signup/page.tsx | 9 +- .../(default)/dashboard/posts/[id]/page.tsx | 32 ++++ .../components/PostForm/DeleteSection.tsx | 166 ++++++++++++++++++ .../posts/components/PostForm/index.tsx | 157 +++++++++++++++++ .../posts/components/PostsTable/index.tsx | 34 ++++ .../dashboard/posts/components/index.ts | 4 + .../(default)/dashboard/posts/new/page.tsx | 23 +++ .../(default)/dashboard/posts/page.tsx | 27 ++- prisma/schema.prisma | 2 +- services/posts.ts | 59 +++++++ services/users.ts | 2 + 14 files changed, 566 insertions(+), 57 deletions(-) create mode 100644 app/[locale]/(default)/dashboard/posts/[id]/page.tsx create mode 100644 app/[locale]/(default)/dashboard/posts/components/PostForm/DeleteSection.tsx create mode 100644 app/[locale]/(default)/dashboard/posts/components/PostForm/index.tsx create mode 100644 app/[locale]/(default)/dashboard/posts/components/PostsTable/index.tsx create mode 100644 app/[locale]/(default)/dashboard/posts/components/index.ts create mode 100644 app/[locale]/(default)/dashboard/posts/new/page.tsx create mode 100644 services/posts.ts diff --git a/app/[locale]/(auth)/login/LoginForm/index.tsx b/app/[locale]/(auth)/login/LoginForm/index.tsx index 6cb7f0a..18775f9 100644 --- a/app/[locale]/(auth)/login/LoginForm/index.tsx +++ b/app/[locale]/(auth)/login/LoginForm/index.tsx @@ -1,9 +1,16 @@ 'use client'; -import { Input, Button, FormLabel, FormControl } from '@/components/chakra'; +import { + Input, + Button, + FormLabel, + FormControl, + Stack, + Flex, +} from '@/components/chakra'; import { useAuth, useEffect, useState, useRouter } from '@/hooks'; -import { signIn } from 'next-auth/react'; import { NextLink } from '@/components/client'; +import { signIn } from 'next-auth/react'; export default function LoginForm() { const router = useRouter(); @@ -29,7 +36,7 @@ export default function LoginForm() { }, [isAuthenticated, router]); return ( - <> + 0 && !validEmail} @@ -54,26 +61,28 @@ export default function LoginForm() { /> - + + - - + + + ); } diff --git a/app/[locale]/(auth)/login/page.tsx b/app/[locale]/(auth)/login/page.tsx index f497d84..1af3809 100644 --- a/app/[locale]/(auth)/login/page.tsx +++ b/app/[locale]/(auth)/login/page.tsx @@ -3,10 +3,11 @@ import LoginForm from './LoginForm'; export default function Login() { return ( - + Login + ); diff --git a/app/[locale]/(auth)/signup/SignUpForm/index.tsx b/app/[locale]/(auth)/signup/SignUpForm/index.tsx index 41bca21..67c5a6e 100644 --- a/app/[locale]/(auth)/signup/SignUpForm/index.tsx +++ b/app/[locale]/(auth)/signup/SignUpForm/index.tsx @@ -7,17 +7,15 @@ import { FormLabel, FormControl, FormErrorMessage, + Stack, + Flex, } from '@/components/chakra'; import { MIN_PASSWORD_LENGTH } from '@/utils/constants'; import { httpClient } from '@/utils/httpClient'; -import { useEffect, useRouter, useState } from '@/hooks'; -import type { Session } from 'next-auth'; +import { useRouter, useState } from '@/hooks'; import { NextLink } from '@/components/client'; -type PropTypes = { - session?: Session | null; -}; -export default function SignUpForm({ session }: PropTypes) { +export default function SignUpForm() { const router = useRouter(); const [isLoading, setLoading] = useState(false); const [agreed, setAgreed] = useState(false); @@ -33,12 +31,6 @@ export default function SignUpForm({ session }: PropTypes) { const validForm = validEmail && isPasswordStrong && isConfirmMatched; const colorScheme = validForm ? 'brand' : 'gray'; - useEffect(() => { - if (session?.user?.id) { - router.push('/protected'); - } - }, [session?.user?.id, router]); - const handleSignUp = async () => { setLoading(true); await httpClient.post('/api/users', credentials); @@ -47,7 +39,7 @@ export default function SignUpForm({ session }: PropTypes) { }; return ( - <> + Name - + + - - + + + ); } diff --git a/app/[locale]/(auth)/signup/page.tsx b/app/[locale]/(auth)/signup/page.tsx index d0ace62..1e7e126 100644 --- a/app/[locale]/(auth)/signup/page.tsx +++ b/app/[locale]/(auth)/signup/page.tsx @@ -1,16 +1,21 @@ import { Heading, Stack } from '@/components/chakra'; import SignUpForm from './SignUpForm'; import { getSession } from '@/utils/auth'; +import { redirect } from 'next/navigation'; export default async function SignUp() { const session = await getSession(); + if (session) { + redirect('/'); + } + return ( - + Sign Up - + ); } diff --git a/app/[locale]/(default)/dashboard/posts/[id]/page.tsx b/app/[locale]/(default)/dashboard/posts/[id]/page.tsx new file mode 100644 index 0000000..a2d6459 --- /dev/null +++ b/app/[locale]/(default)/dashboard/posts/[id]/page.tsx @@ -0,0 +1,32 @@ +import { Flex, Heading, Spinner, Stack } from '@/components/chakra'; +import { GoBackButton } from '@/components/client'; +import { prisma } from '@/utils/prisma'; +import { Suspense } from 'react'; + +import { PostForm } from '../components'; + +type EditPostProps = { + params: { id: string }; +}; + +export default async function EditPost({ params }: EditPostProps) { + const { id } = params; + + const data = await prisma.post.findUnique({ where: { id } }); + + return ( + + + + + + Edit Post + + + + }> + {data && } + + + ); +} diff --git a/app/[locale]/(default)/dashboard/posts/components/PostForm/DeleteSection.tsx b/app/[locale]/(default)/dashboard/posts/components/PostForm/DeleteSection.tsx new file mode 100644 index 0000000..9ecdb48 --- /dev/null +++ b/app/[locale]/(default)/dashboard/posts/components/PostForm/DeleteSection.tsx @@ -0,0 +1,166 @@ +import { + AlertDialog, + AlertDialogBody, + AlertDialogCloseButton, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + Button, + Card, + CardBody, + CardFooter, + CardHeader, + Divider, + Flex, + FormControl, + FormLabel, + Heading, + Input, + Skeleton, + Stack, + Text, +} from '@/components/chakra'; +import { + useColorModeValue, + useDisclosure, + useRef, + useRouter, + useState, + useToast, +} from '@/hooks'; +import { deletePost } from '@/services/posts'; +import type { Post } from '@/types'; + +export type PostDeleteSectionProps = { + post: Post; +}; + +export const DeleteSection = ({ post }: PostDeleteSectionProps) => { + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + const toast = useToast(); + const cancelRef = useRef(null); + + const { isOpen, onOpen, onClose } = useDisclosure(); + + const [confirmText, setConfirmText] = useState(''); + + const postId = post?.id ?? ''; + + const handleCancel = () => { + setConfirmText(''); + onClose(); + }; + + const handleDelete = async () => { + try { + if (!post) return; + + setIsLoading(true); + + await deletePost(postId); + + toast({ description: 'Delete successfully' }); + + setIsLoading(false); + + onClose(); + + router.refresh(); + router.push('/dashboard/posts'); + } catch (error: any) { + toast({ status: 'error', title: `Error: ${error.message}` }); + } + }; + + const backgroundColor = useColorModeValue('red.50', 'red.900'); + const footerBorder = useColorModeValue('red.100', 'red.600'); + + if (!postId) { + return ; + } + + return ( + <> + + + Delete Post + + + + + + The post will be permanently deleted. This action is irreversible + and can not be undone. + + + + + + + + + + + + + + + + + + Are you sure? + + + + + Confirm by typing:{' '} + + YES + + + setConfirmText(event.target.value)} + /> + + + + + + + + + + + + ); +}; diff --git a/app/[locale]/(default)/dashboard/posts/components/PostForm/index.tsx b/app/[locale]/(default)/dashboard/posts/components/PostForm/index.tsx new file mode 100644 index 0000000..70642e6 --- /dev/null +++ b/app/[locale]/(default)/dashboard/posts/components/PostForm/index.tsx @@ -0,0 +1,157 @@ +import { + Button, + Flex, + FormControl, + FormLabel, + Heading, + IconButton, + Input, + Select, + Stack, +} from '@/components/chakra'; +import { CustomEditable, FormWrapper, TextEditor } from '@/components/client'; +import { useEffect, useRouter, useState, useToast } from '@/hooks'; +import { RepeatIcon } from '@/icons'; +import { createPost, updatePost } from '@/services/posts'; +import { Post } from '@/types'; +import slugify from 'slugify'; +import { DeleteSection } from './DeleteSection'; + +export type PostFormProps = { + data?: Post; +}; + +export const PostForm = ({ data: propsData }: PostFormProps) => { + const toast = useToast(); + const router = useRouter(); + + const [isLoading, setIsLoading] = useState(false); + + const [data, setInputData] = useState({ + title: propsData?.title ?? '', + content: propsData?.content ?? '', + slug: propsData?.slug ?? '', + locale: propsData?.locale ?? 'vi', + }); + + const [isCustomEdited, setIsCustomEdited] = useState(false); + + useEffect(() => { + if (!isCustomEdited) { + setInputData((prev) => ({ + ...prev, + slug: slugify(prev.title), + })); + } + }, [data.title, isCustomEdited]); + + const handleSubmit = async (formData: FormData) => { + setIsLoading(true); + + if (propsData) { + const response = await updatePost(propsData.id, formData); + + if (response) { + toast({ description: 'Cập nhật thành công' }); + router.refresh(); + } + } else { + const response = await createPost(formData); + + if (response) { + toast({ description: 'Published successfully' }); + router.refresh(); + router.push(`/dashboard/posts/${response.id}`); + } + } + + setIsLoading(false); + }; + + return ( + + + + + Title + + setInputData({ ...data, title: event.target.value }) + } + /> + + + + Slug: + + + {data.slug && ( + { + setIsCustomEdited(true); + setInputData({ ...data, slug: newValue }); + }} + /> + )} + + {isCustomEdited && ( + } + onClick={() => { + setIsCustomEdited(false); + setInputData({ + ...data, + slug: slugify(data.title), + }); + }} + /> + )} + + + + + Content + + setInputData({ ...data, content: newValue }) + } + /> + + + + Locale + + + + + + + + {propsData && } + + + + ); +}; diff --git a/app/[locale]/(default)/dashboard/posts/components/PostsTable/index.tsx b/app/[locale]/(default)/dashboard/posts/components/PostsTable/index.tsx new file mode 100644 index 0000000..4cbc9b1 --- /dev/null +++ b/app/[locale]/(default)/dashboard/posts/components/PostsTable/index.tsx @@ -0,0 +1,34 @@ +'use client'; + +import { VirtualTable } from '@/components/client'; +import { Text } from '@/components/chakra'; +import { useRouter } from '@/hooks'; +import type { Post } from '@/types'; + +export type PostTableProps = { + data: Post[]; +}; + +export const PostTable = ({ data }: PostTableProps) => { + const router = useRouter(); + + return data.length > 0 ? ( + router.push(`/dashboard/posts/${item.id}`)} + /> + ) : ( + No posts + ); +}; diff --git a/app/[locale]/(default)/dashboard/posts/components/index.ts b/app/[locale]/(default)/dashboard/posts/components/index.ts new file mode 100644 index 0000000..366e38f --- /dev/null +++ b/app/[locale]/(default)/dashboard/posts/components/index.ts @@ -0,0 +1,4 @@ +'use client'; + +export * from './PostForm'; +export * from './PostsTable'; diff --git a/app/[locale]/(default)/dashboard/posts/new/page.tsx b/app/[locale]/(default)/dashboard/posts/new/page.tsx new file mode 100644 index 0000000..7159ef4 --- /dev/null +++ b/app/[locale]/(default)/dashboard/posts/new/page.tsx @@ -0,0 +1,23 @@ +import { Flex, Heading, Spinner, Stack } from '@/components/chakra'; +import { GoBackButton } from '@/components/client'; +import { Suspense } from 'react'; + +import { PostForm } from '../components'; + +export default function AddNewPost() { + return ( + + + + + + Add New Post + + + + }> + + + + ); +} diff --git a/app/[locale]/(default)/dashboard/posts/page.tsx b/app/[locale]/(default)/dashboard/posts/page.tsx index 2039e5a..f6ea60f 100644 --- a/app/[locale]/(default)/dashboard/posts/page.tsx +++ b/app/[locale]/(default)/dashboard/posts/page.tsx @@ -1,3 +1,26 @@ -export default function PostsDashboard() { - return 'post managment here'; +import { Flex, Heading, Spacer } from '@/components/chakra'; +import { AddNewButton } from '@/components/client'; +import { prisma } from '@/utils/prisma'; +import { PostTable } from './components'; + +export default async function PostsDashboard() { + const posts = await prisma.post.findMany({ + include: { author: true }, + }); + + return ( + + + + Posts + + + + + + + + + + ); } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0c578ee..76b59c5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -97,7 +97,7 @@ model Post { title String @db.VarChar(255) slug String @db.VarChar(255) content String - originalId String + originalId String? originalPost Post? @relation("OriginalTranslated", fields: [originalId], references: [id]) translatedPosts Post[] @relation("OriginalTranslated") locale String diff --git a/services/posts.ts b/services/posts.ts new file mode 100644 index 0000000..3905d3b --- /dev/null +++ b/services/posts.ts @@ -0,0 +1,59 @@ +'use server'; + +import { prisma } from '@/utils/prisma'; +import { getSession } from '@/utils/auth'; +import type { Post } from '@/types'; + +type CreatePostDto = Pick; + +export const createPost = async (formData: FormData) => { + const session = await getSession(); + + if (!session) { + throw new Error('Unauthorized request'); + } + + const entries = Object.fromEntries(formData.entries()) as CreatePostDto; + + const result = await prisma.post.create({ + data: { + ...entries, + authorId: session.user.id, + }, + }); + + return result; +}; + +type UpdatePostDto = Partial; + +export const updatePost = async (id: string, formData: FormData) => { + const session = await getSession(); + + if (!session) { + throw new Error('Unauthorized request'); + } + + const entries = Object.fromEntries(formData.entries()) as UpdatePostDto; + + const result = await prisma.post.update({ + where: { id, authorId: session.user.id }, + data: entries, + }); + + return result; +}; + +export const deletePost = async (id: string) => { + const session = await getSession(); + + if (!session) { + throw new Error('Unauthorized request'); + } + + const result = await prisma.post.delete({ + where: { id, authorId: session.user.id }, + }); + + return result; +}; diff --git a/services/users.ts b/services/users.ts index bb92a11..2902371 100644 --- a/services/users.ts +++ b/services/users.ts @@ -158,6 +158,8 @@ export const createUser = async ( }, }); + console.log('### result: ', { result }); + return NextResponse.json({ status: 200, result: exclude(result, 'password'), From b74b48cd8a7e93c3f38bbb2a8a79f9cedc9ccb50 Mon Sep 17 00:00:00 2001 From: Sang Dang Date: Sat, 16 Sep 2023 00:59:19 +0300 Subject: [PATCH 2/4] small improvement on navbar --- app/[locale]/(custom)/layout.tsx | 16 ++++++--- components/client/LocaleSwitcher/index.tsx | 2 +- components/client/Navbar/index.tsx | 39 +++++++++++++--------- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/app/[locale]/(custom)/layout.tsx b/app/[locale]/(custom)/layout.tsx index 0ab4519..0946d75 100644 --- a/app/[locale]/(custom)/layout.tsx +++ b/app/[locale]/(custom)/layout.tsx @@ -1,5 +1,5 @@ import { LocaleSwitcher, Navbar } from '@/components/client'; -import { Box, Flex } from '@/components/chakra'; +import { Box, Container, Flex } from '@/components/chakra'; import { ProfileMenu } from '@/components/server'; import type { ReactNode } from '@/types'; import type { Locale } from '@/configs/i18n.config'; @@ -14,15 +14,23 @@ export default function CustomLayout({ return ( - + - + {children} - + ); } diff --git a/components/client/LocaleSwitcher/index.tsx b/components/client/LocaleSwitcher/index.tsx index a50aa2a..3e22218 100644 --- a/components/client/LocaleSwitcher/index.tsx +++ b/components/client/LocaleSwitcher/index.tsx @@ -31,7 +31,7 @@ export function LocaleSwitcher({ locale }: LocaleSwitcherProps) { - setInputData({ ...data, title: event.target.value }) - } - /> - - - - Slug: - - - {data.slug && ( - { - setIsCustomEdited(true); - setInputData({ ...data, slug: newValue }); - }} - /> - )} - - {isCustomEdited && ( - } - onClick={() => { - setIsCustomEdited(false); - setInputData({ - ...data, - slug: slugify(data.title), - }); - }} - /> - )} - - - - - Content - - setInputData({ ...data, content: newValue }) - } - /> - - - - Locale - - - - - - - - {propsData && } - - + + + + Title + + setInputData({ ...data, title: event.target.value }) + } + /> + + + + Slug: + + + {data.slug && ( + { + setIsCustomEdited(true); + setInputData({ ...data, slug: newValue }); + }} + /> + )} + + {isCustomEdited && ( + } + onClick={() => { + setIsCustomEdited(false); + setInputData({ + ...data, + slug: slugify(data.title), + }); + }} + /> + )} + + + + + Content + + setInputData({ ...data, content: newValue }) + } + /> + + + + Locale + + + + {propsData && } + ); }; diff --git a/app/[locale]/(default)/dashboard/pages/new/page.tsx b/app/[locale]/(default)/dashboard/pages/new/page.tsx index 97d06ca..4bd2d1f 100644 --- a/app/[locale]/(default)/dashboard/pages/new/page.tsx +++ b/app/[locale]/(default)/dashboard/pages/new/page.tsx @@ -1,23 +1,5 @@ -import { Flex, Heading, Spinner, Stack } from '@/components/chakra'; -import { GoBackButton } from '@/components/client'; -import { Suspense } from 'react'; - import { PageForm } from '../components'; export default function AddNewPage() { - return ( - - - - - - Add New Page - - - - }> - - - - ); + return ; } diff --git a/app/[locale]/(default)/dashboard/posts/[id]/page.tsx b/app/[locale]/(default)/dashboard/posts/[id]/page.tsx index a2d6459..99d93e0 100644 --- a/app/[locale]/(default)/dashboard/posts/[id]/page.tsx +++ b/app/[locale]/(default)/dashboard/posts/[id]/page.tsx @@ -1,7 +1,4 @@ -import { Flex, Heading, Spinner, Stack } from '@/components/chakra'; -import { GoBackButton } from '@/components/client'; import { prisma } from '@/utils/prisma'; -import { Suspense } from 'react'; import { PostForm } from '../components'; @@ -14,19 +11,5 @@ export default async function EditPost({ params }: EditPostProps) { const data = await prisma.post.findUnique({ where: { id } }); - return ( - - - - - - Edit Post - - - - }> - {data && } - - - ); + return ; } diff --git a/app/[locale]/(default)/dashboard/posts/components/PostForm/index.tsx b/app/[locale]/(default)/dashboard/posts/components/PostForm/index.tsx index 70642e6..1e8903f 100644 --- a/app/[locale]/(default)/dashboard/posts/components/PostForm/index.tsx +++ b/app/[locale]/(default)/dashboard/posts/components/PostForm/index.tsx @@ -9,19 +9,30 @@ import { Select, Stack, } from '@/components/chakra'; -import { CustomEditable, FormWrapper, TextEditor } from '@/components/client'; +import { + CustomEditable, + FormWrapper, + GoBackButton, + TextEditor, +} from '@/components/client'; import { useEffect, useRouter, useState, useToast } from '@/hooks'; -import { RepeatIcon } from '@/icons'; +import { ArrowUpIcon, RepeatIcon } from '@/icons'; import { createPost, updatePost } from '@/services/posts'; import { Post } from '@/types'; import slugify from 'slugify'; import { DeleteSection } from './DeleteSection'; export type PostFormProps = { - data?: Post; + title?: string; + backPath?: string; + data: Post | null; }; -export const PostForm = ({ data: propsData }: PostFormProps) => { +export const PostForm = ({ + backPath, + title, + data: propsData, +}: PostFormProps) => { const toast = useToast(); const router = useRouter(); @@ -70,88 +81,99 @@ export const PostForm = ({ data: propsData }: PostFormProps) => { return ( - - - - Title - - setInputData({ ...data, title: event.target.value }) - } - /> - - - - Slug: - - - {data.slug && ( - { - setIsCustomEdited(true); - setInputData({ ...data, slug: newValue }); - }} - /> - )} - - {isCustomEdited && ( - } - onClick={() => { - setIsCustomEdited(false); - setInputData({ - ...data, - slug: slugify(data.title), - }); - }} - /> - )} - - - - - Content - - setInputData({ ...data, content: newValue }) - } - /> - - - - Locale - - - - - - - - {propsData && } - - + + + + Title + + setInputData({ ...data, title: event.target.value }) + } + /> + + + + Slug: + + + {data.slug && ( + { + setIsCustomEdited(true); + setInputData({ ...data, slug: newValue }); + }} + /> + )} + + {isCustomEdited && ( + } + onClick={() => { + setIsCustomEdited(false); + setInputData({ + ...data, + slug: slugify(data.title), + }); + }} + /> + )} + + + + + Content + + setInputData({ ...data, content: newValue }) + } + /> + + + + Locale + + + + {propsData && } + ); }; diff --git a/app/[locale]/(default)/dashboard/posts/new/page.tsx b/app/[locale]/(default)/dashboard/posts/new/page.tsx index 7159ef4..22b53ef 100644 --- a/app/[locale]/(default)/dashboard/posts/new/page.tsx +++ b/app/[locale]/(default)/dashboard/posts/new/page.tsx @@ -1,23 +1,5 @@ -import { Flex, Heading, Spinner, Stack } from '@/components/chakra'; -import { GoBackButton } from '@/components/client'; -import { Suspense } from 'react'; - import { PostForm } from '../components'; export default function AddNewPost() { - return ( - - - - - - Add New Post - - - - }> - - - - ); + return ; } diff --git a/components/client/AddNewButton/index.tsx b/components/client/AddNewButton/index.tsx index b366de9..6eb87d3 100644 --- a/components/client/AddNewButton/index.tsx +++ b/components/client/AddNewButton/index.tsx @@ -22,12 +22,7 @@ export const AddNewButton = ({ }; return ( - ); From 3ad80b7e0b44fe0eb4647aa0b5d80c276cda1254 Mon Sep 17 00:00:00 2001 From: Sang Dang Date: Sat, 16 Sep 2023 02:17:20 +0300 Subject: [PATCH 4/4] add data optional so we can use form for create new --- .../(default)/dashboard/pages/components/PageForm/index.tsx | 2 +- .../(default)/dashboard/posts/components/PostForm/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/[locale]/(default)/dashboard/pages/components/PageForm/index.tsx b/app/[locale]/(default)/dashboard/pages/components/PageForm/index.tsx index 75ec332..5b67098 100644 --- a/app/[locale]/(default)/dashboard/pages/components/PageForm/index.tsx +++ b/app/[locale]/(default)/dashboard/pages/components/PageForm/index.tsx @@ -25,7 +25,7 @@ import { DeleteSection } from './DeleteSection'; export type PageFormProps = { title?: string; backPath?: string; - data: Page | null; + data?: Page | null; }; export const PageForm = ({ diff --git a/app/[locale]/(default)/dashboard/posts/components/PostForm/index.tsx b/app/[locale]/(default)/dashboard/posts/components/PostForm/index.tsx index 1e8903f..31fdf4b 100644 --- a/app/[locale]/(default)/dashboard/posts/components/PostForm/index.tsx +++ b/app/[locale]/(default)/dashboard/posts/components/PostForm/index.tsx @@ -25,7 +25,7 @@ import { DeleteSection } from './DeleteSection'; export type PostFormProps = { title?: string; backPath?: string; - data: Post | null; + data?: Post | null; }; export const PostForm = ({