diff --git a/package.json b/package.json index 118e69c..908e6eb 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@supabase/supabase-js": "^2.45.4", + "axios": "^1.7.7", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.35.2", diff --git a/src/app/api/Blog/route.ts b/src/app/api/Blog/route.ts new file mode 100644 index 0000000..65f55f5 --- /dev/null +++ b/src/app/api/Blog/route.ts @@ -0,0 +1,30 @@ +import {NextResponse} from 'next/server' + +import {createBrowserClient} from '@/supabase/client' + +export async function GET() { + const supabase = createBrowserClient() + + try { + const [ + {data: postsData, error: postsError}, + {data: commentsData, error: commentsError}, + ] = await Promise.all([ + supabase.from('posts').select('*'), + supabase.from('comments').select('*'), + ]) + + if (postsError || commentsError) { + return NextResponse.json( + {error: postsError?.message || commentsError?.message}, + {status: 500}, + ) + } + return NextResponse.json({posts: postsData, comments: commentsData}) + } catch (error) { + return NextResponse.json( + {error: 'Error fetching blog detail and comments'}, + {status: 500}, + ) + } +} diff --git a/src/app/api/BlogDetail/route.ts b/src/app/api/BlogDetail/route.ts index 961c8a5..d4f1a8e 100644 --- a/src/app/api/BlogDetail/route.ts +++ b/src/app/api/BlogDetail/route.ts @@ -4,9 +4,7 @@ import {createBrowserClient} from '@/supabase/client' export async function GET(req: NextRequest) { const supabase = createBrowserClient() - const {searchParams} = new URL(req.url) - const blogId = searchParams.get('id') if (!blogId) { @@ -14,20 +12,25 @@ export async function GET(req: NextRequest) { } try { - const {data, error} = await supabase - .from('posts') - .select('*') - .eq('id', blogId) - .single() - - if (error) { - return NextResponse.json({error: error.message}, {status: 500}) + const [ + {data: postData, error: postError}, + {data: commentsData, error: commentsError}, + ] = await Promise.all([ + supabase.from('posts').select('*').eq('id', blogId).single(), + supabase.from('comments').select('*').eq('post_id', blogId), // comments 테이블에서 post_id가 blogId인 것들을 가져옴 + ]) + + if (postError || commentsError) { + return NextResponse.json( + {error: postError?.message || commentsError?.message}, + {status: 500}, + ) } - return NextResponse.json(data) + return NextResponse.json({post: postData, comments: commentsData}) } catch (error) { return NextResponse.json( - {error: 'Error fetching blog detail'}, + {error: 'Error fetching blog detail and comments'}, {status: 500}, ) } diff --git a/src/app/api/createComment.ts b/src/app/api/createComment.ts new file mode 100644 index 0000000..833fdbc --- /dev/null +++ b/src/app/api/createComment.ts @@ -0,0 +1,42 @@ +import {createBrowserClient} from '@/supabase/client' + +export async function createComment({ + username, + userId, + blogId, + content, + role, +}: { + username: string + userId: string + blogId: string + content: string + role: string +}) { + const supabase = createBrowserClient() + + try { + const {data, error} = await supabase + .from('comments') + .insert([ + { + username: username, + user_id: userId, + post_id: blogId, + content, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + user_role: role, + }, + ]) + .select('*') + + if (error) { + return alert('권한이 없습니다.') + } + return data[0] + } catch (error) { + // eslint-disable-next-line no-console + console.log(error) + } +} diff --git a/src/app/api/deleteComment.ts b/src/app/api/deleteComment.ts new file mode 100644 index 0000000..b3482ff --- /dev/null +++ b/src/app/api/deleteComment.ts @@ -0,0 +1,31 @@ +import { createBrowserClient } from "@/supabase/client" + +export async function deleteComment({ + userId, + commentId, + role, +}: { + userId: string + commentId: string + role?: string +}) { + const supabase = createBrowserClient() + + try { + const query = role === 'admin' + ? supabase.from('comments').delete().eq('id', commentId) + : supabase.from('comments').delete().eq('user_id', userId).eq('id', commentId) + + const { data, error } = await query + + if (error) { + alert('삭제 권한이 없습니다.') + return null + } + + return data + } catch (error) { + console.error(error) + return null + } +} diff --git a/src/app/api/getBlog.ts b/src/app/api/getBlog.ts deleted file mode 100644 index 514f640..0000000 --- a/src/app/api/getBlog.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {createBrowserClient} from '@/supabase/client' - -export async function getBlogs() { - const supabase = createBrowserClient() - - try { - const {data, error} = await supabase.from('posts').select('*') - - if (error) { - return alert(error) - } - return data - } catch (error) { - // eslint-disable-next-line no-console - console.log(error) - } -} diff --git a/src/components/CommentInput/index.tsx b/src/components/CommentInput/index.tsx index dc33aae..4784abc 100644 --- a/src/components/CommentInput/index.tsx +++ b/src/components/CommentInput/index.tsx @@ -1,11 +1,24 @@ -import {useState} from 'react' +import {useCallback, useState} from 'react' import Icon from '@/components/Icon' import Input from '@/components/Input' -const CommentInput = () => { +interface Props { + onClick: (content: string) => Promise +} + +const CommentInput = ({onClick}: Props) => { const [commentValue, setCommentValue] = useState('') + const handleSubmit = useCallback( + async (event: React.FormEvent) => { + event.preventDefault() + + await onClick(commentValue) + }, + [commentValue, onClick], + ) + return (
@@ -14,11 +27,13 @@ const CommentInput = () => { iconName="search" />
- setCommentValue(e.target.value)} - placeholder="댓글을 입력해주세요." - /> +
+ setCommentValue(e.target.value)} + placeholder="댓글을 입력해주세요." + /> +
) } diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx index e57347f..a07dde9 100644 --- a/src/components/Icon/index.tsx +++ b/src/components/Icon/index.tsx @@ -29,6 +29,8 @@ const ICONS = { 'M14.707 5.293a1 1 0 0 0-1.414 1.414L17.586 11H4a1 1 0 1 0 0 2h13.586l-4.293 4.293a1 1 0 0 0 1.414 1.414l6-6a1 1 0 0 0 0-1.414l-6-6z', create: 'M 12 2 C 6.4889941 2 2 6.4889982 2 12 C 2 17.511002 6.4889941 22 12 22 C 17.511006 22 22 17.511002 22 12 C 22 6.4889982 17.511006 2 12 2 z M 12 4 C 16.430126 4 20 7.5698765 20 12 C 20 16.430123 16.430126 20 12 20 C 7.5698737 20 4 16.430123 4 12 C 4 7.5698765 7.5698737 4 12 4 z M 11.984375 6.9863281 A 1.0001 1.0001 0 0 0 11 8 L 11 11 L 8 11 A 1.0001 1.0001 0 1 0 8 13 L 11 13 L 11 16 A 1.0001 1.0001 0 1 0 13 16 L 13 13 L 16 13 A 1.0001 1.0001 0 1 0 16 11 L 13 11 L 13 8 A 1.0001 1.0001 0 0 0 11.984375 6.9863281 z', + delete: + 'M15 2.1a.9.9 0 0 1 .122 1.792L15 3.9H9a.9.9 0 0 1-.122-1.792L9 2.1h6zm6 3a.9.9 0 0 1 .122 1.792L21 6.9h-1.159l-.691 10.361-.045.567c-.095 1.043-.221 1.573-.523 2.102a3.9 3.9 0 0 1-1.688 1.579l-.224.101c-.332.137-.661.21-1.184.248l-.38.021-.451.013-1.147.008-3.616-.001-.51-.006-.434-.011-.37-.018-.316-.027c-.341-.038-.595-.097-.84-.189l-.21-.087-.107-.05a3.9 3.9 0 0 1-1.688-1.579l-.166-.325-.088-.223c-.054-.154-.098-.32-.135-.518l-.053-.321-.046-.38-.063-.708-.062-.878L4.158 6.9H3a.9.9 0 0 1-.122-1.792L3 5.1h18zm-2.963 1.8H5.962l.691 10.315.066.741.034.277.036.226.04.185.045.152.051.129.059.115a2.1 2.1 0 0 0 .909.85l.126.054.142.044.168.035.206.027.255.02.314.014.606.012.784.003 3.776-.003.583-.011.307-.013.252-.018.206-.024.089-.014.155-.034.132-.042.118-.051a2.1 2.1 0 0 0 .909-.85c.074-.13.131-.285.179-.511l.045-.252.04-.31.037-.378.082-1.129.637-9.559zM10 9.6a.9.9 0 0 1 .892.778l.008.122v5a.9.9 0 0 1-1.792.122L9.1 15.5v-5a.9.9 0 0 1 .9-.9zm4 0a.9.9 0 0 1 .892.778l.008.122v5a.9.9 0 0 1-1.792.122L13.1 15.5v-5a.9.9 0 0 1 .9-.9z', } export type IconName = keyof typeof ICONS diff --git a/src/components/IconButton/index.tsx b/src/components/IconButton/index.tsx new file mode 100644 index 0000000..080c4d2 --- /dev/null +++ b/src/components/IconButton/index.tsx @@ -0,0 +1,25 @@ +import Icon, {type IconName} from '@/components/Icon' + +interface Props { + type?: 'submit' | 'reset' | 'button' + buttonClassName?: string + iconClassName?: string + iconName: IconName + onClick?: () => void +} + +const IconButton = ({ + iconName, + buttonClassName, + iconClassName, + onClick, + type = 'button', +}: Props) => { + return ( + + ) +} + +export default IconButton diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index 2a9867e..d543361 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -1,7 +1,13 @@ -import {useEffect, useState} from 'react' +import axios from 'axios' +import {useParams, usePathname} from 'next/navigation' +import {useCallback, useEffect, useState} from 'react' +import {createComment} from '@/app/api/createComment' +import {deleteComment} from '@/app/api/deleteComment' import LeftSideBar from '@/components/LeftSideBar' import RightCommentBar from '@/components/RightCommentBar' +import {useUser} from '@/store/user' +import {type Database} from '@/supabase/database.types' interface Props { children: React.ReactNode @@ -9,8 +15,21 @@ interface Props { } const Layout = ({children, isMainView = false}: Props) => { + const params = useParams() + const pathname = usePathname() + + const user = useUser(state => state.user) + const [isSideBarVisible, setIsSideBarVisible] = useState(false) const [isRightSideBarVisible, setIsRightSideBarVisible] = useState(true) + const [commentsData, setCommentsData] = useState< + Database['public']['Tables']['comments']['Row'][] | [] + >([]) + const [blogCommentData, setBlogCommentData] = useState< + Database['public']['Tables']['comments']['Row'][] | [] + >([]) + + const isBlogDetailPage = pathname.includes('Blog') && params.id !== undefined const handleResize = () => { if (window.innerWidth <= 1179) { @@ -26,19 +45,99 @@ const Layout = ({children, isMainView = false}: Props) => { } } + const fetchBlogCommentData = useCallback(async () => { + const res = await axios(`/api/BlogDetail?id=${params.id}`) + const data = await res.data + + setBlogCommentData(data.comments) + }, [params.id]) + + const fetchCommentsData = useCallback(async () => { + const res = await axios(`/api/Blog`) + const data = await res.data + + setCommentsData(data.comments) + }, []) + + const handleCreateComment = useCallback( + async ({ + username, + userId, + content, + }: { + username: string + userId: string + content: string + }) => { + const newComment = await createComment({ + username, + userId, + blogId: `${params.id}`, + content, + role: user?.role ?? '', + }) + + if (!newComment) { + return + } + + if (isBlogDetailPage) { + setBlogCommentData(prevData => [...prevData, newComment]) + } else { + setCommentsData(prevData => [...prevData, newComment]) + } + }, + [isBlogDetailPage, params.id, user?.role], + ) + + const handleDeleteComment = useCallback( + async ({ + userId, + commentId, + role, + }: { + userId: string + commentId: string + role?: string + }) => { + await deleteComment({userId, commentId, role}) + + if (isBlogDetailPage) { + setBlogCommentData(prevData => + prevData.filter(comment => comment.id !== commentId), + ) + } else { + setCommentsData(prevData => + prevData.filter(comment => comment.id !== commentId), + ) + } + }, + [isBlogDetailPage], + ) + useEffect(() => { handleResize() window.addEventListener('resize', handleResize) + return () => window.removeEventListener('resize', handleResize) }, []) + useEffect(() => { + if (isBlogDetailPage) { + void fetchBlogCommentData() + + return + } + void fetchCommentsData() + }, [fetchBlogCommentData, fetchCommentsData, isBlogDetailPage]) + return (
- +
@@ -50,7 +149,12 @@ const Layout = ({children, isMainView = false}: Props) => { {isMainView || (isRightSideBarVisible && ( ))}
diff --git a/src/components/LeftSideBar/index.tsx b/src/components/LeftSideBar/index.tsx index 139f3fb..0f2a316 100644 --- a/src/components/LeftSideBar/index.tsx +++ b/src/components/LeftSideBar/index.tsx @@ -5,7 +5,7 @@ import Navigation, { } from '@/components/LeftSideBar/Navigation' import MainTitle from '@/components/MainTitle' import Profile from '@/components/Profile' -import {useUser} from '@/store/user' +import {type User} from '@/store/user' const NAVIGATION: NavigationType[] = [ { @@ -35,12 +35,11 @@ const NAVIGATION: NavigationType[] = [ ] interface Props { + user: User | null isLeftSideBarVisible?: boolean } -const LeftSideBar = ({isLeftSideBarVisible}: Props) => { - const user = useUser(state => state.user) - +const LeftSideBar = ({user, isLeftSideBarVisible}: Props) => { const isLogin = user !== null return ( diff --git a/src/components/RightCommentBar/CommentBox.tsx b/src/components/RightCommentBar/CommentBox.tsx index 5cec6a2..daf6dc1 100644 --- a/src/components/RightCommentBar/CommentBox.tsx +++ b/src/components/RightCommentBar/CommentBox.tsx @@ -1,45 +1,83 @@ import Link from 'next/link' +import {useCallback, useState} from 'react' -import Image from '@/components/Image' - -type CommentType = { - username: string - content: string - time: string - url: string - image: string -} +import Button from '@/components/Button' +import IconButton from '@/components/IconButton' +import TooltipModal from '@/components/TooltipModal' +import {useUser} from '@/store/user' +import {type Database} from '@/supabase/database.types' interface Props { - item: CommentType + isDisabled?: boolean + isTooltipVisible?: boolean + item: Database['public']['Tables']['comments']['Row'] + onClickPositiveButton?: () => void + onClickNegativeButton?: () => void | Promise } -const CommentBox = ({item}: Props) => { +const CommentBox = ({ + item, + isDisabled = false, + onClickPositiveButton, + onClickNegativeButton, +}: Props) => { + const user = useUser(state => state.user) + + const [isTooltipVisible, setIsTooltipVisible] = useState(false) + + const isDeleteButtonVisible = + user?.id === item.user_id || user?.role === 'admin' + + const handleTooltipVisible = useCallback(() => { + setIsTooltipVisible(!isTooltipVisible) + }, [isTooltipVisible]) + + const handlePositiveButton = useCallback(() => { + onClickPositiveButton?.() + setIsTooltipVisible(false) + }, [onClickPositiveButton]) + + const handleNegativeButton = useCallback(() => { + onClickNegativeButton?.() + setIsTooltipVisible(false) + }, [onClickNegativeButton]) + return ( -
-
-
- check -
-
- {item.username} -
-
- -
-
{item.content}
-
-
{item.time}
+ <> +
+ isDisabled && event.preventDefault()}> +
+
+ {item.username} +
+
{item.content}
+
+
{item.created_at}
+
-
- -
+ + {isDeleteButtonVisible && ( + <> + + +
+ ) } diff --git a/src/components/RightCommentBar/index.tsx b/src/components/RightCommentBar/index.tsx index 894c212..4085177 100644 --- a/src/components/RightCommentBar/index.tsx +++ b/src/components/RightCommentBar/index.tsx @@ -1,81 +1,106 @@ +import {useCallback} from 'react' import {twMerge} from 'tailwind-merge' import CommentInput from '@/components/CommentInput' import LoginForm from '@/components/LoginForm' import CommentBox from '@/components/RightCommentBar/CommentBox' - -const MOCK_DATA = [ - { - username: 'khs', - content: - 'content content content content content content content content content content', - time: new Date().toDateString(), - url: '/Blog', - image: '/images/smileHeart.png', - }, - { - username: 'kwon', - content: 'kwon kwon kwon kwon kwon kwon kwon kwon kwon', - time: new Date().toDateString(), - url: '/Blog', - image: '/images/smileHeart.png', - }, - { - username: 'han', - content: 'han han han han han', - time: new Date().toDateString(), - url: '/Blog', - image: '/images/smileHeart.png', - }, - { - username: 'sung', - content: 'sung sung sung sung sung sung sung', - time: new Date().toDateString(), - url: '/Blog', - image: '/images/smileHeart.png', - }, - { - username: 'ivory-code', - content: - 'ivory-code ivory-code ivory-code ivory-code ivory-code ivory-code ivory-code', - time: new Date().toDateString(), - url: '/Blog', - image: '/images/smileHeart.png', - }, - { - username: 'khs-log', - content: 'khs-log khs-log khs-log khs-log khs-log', - time: new Date().toDateString(), - url: '/Blog', - image: '/images/smileHeart.png', - }, -] +import {type User} from '@/store/user' +import {type Database} from '@/supabase/database.types' interface Props { + data: Database['public']['Tables']['comments']['Row'][] | [] + user: User | null className?: string + createComment?: ({ + username, + userId, + content, + }: { + username: string + userId: string + content: string + }) => Promise + deleteComment: ({ + userId, + commentId, + role, + }: { + userId: string + commentId: string + role?: string + }) => Promise + isBlogDetailPage?: boolean } -const RightCommentBar = ({className}: Props) => { +const RightCommentBar = ({ + data, + user, + className, + createComment, + deleteComment, + isBlogDetailPage = true, +}: Props) => { + const handleInput = useCallback( + async (content: string) => { + if (createComment) { + await createComment({ + username: user?.nickname ?? '', + userId: user?.id ?? '', + content, + }) + } + }, + [createComment, user?.id, user?.nickname], + ) + + const renderCommentBox = (data: { + content: string + created_at: string + id: string + post_id: string | null + updated_at: string | null + user_id: string | null + username: string + user_role: string + }) => { + const handleDelete = () => { + deleteComment?.({ + userId: user?.id ?? '', + commentId: data.id ?? '', + role: user?.role ?? '', + }) + } + + return ( + + ) + } + return (
-
Comment Length
+
댓글 수
- {MOCK_DATA.length} + {data?.length}
-
- {MOCK_DATA.map((data, index) => { - return - })} -
-
- +
+ {data?.map(data => renderCommentBox(data))}
+ {isBlogDetailPage && ( +
+ +
+ )}
) } diff --git a/src/components/SessionProvider/index.tsx b/src/components/SessionProvider/index.tsx index 82cdd8b..87424d5 100644 --- a/src/components/SessionProvider/index.tsx +++ b/src/components/SessionProvider/index.tsx @@ -32,7 +32,8 @@ export default function SessionProvider({children}: Props) { role: data?.role ?? '', email: data?.email ?? '', created_at: data?.created_at ?? new Date().toISOString(), - nickname: userSession.session.user.user_metadata.user_name ?? '', + // nickname: userSession.session.user.user_metadata.user_name ?? '', + nickname: data?.username ?? '', }) } } diff --git a/src/components/TooltipModal/index.tsx b/src/components/TooltipModal/index.tsx new file mode 100644 index 0000000..75c58d4 --- /dev/null +++ b/src/components/TooltipModal/index.tsx @@ -0,0 +1,33 @@ +import Typography from '@/components/Typography' + +interface Props { + isModalVisible: boolean + title: string + description?: string + children?: React.ReactNode +} + +const TooltipModal = ({ + isModalVisible, + title, + description = '', + children, +}: Props) => { + if (isModalVisible) { + return ( +
+ +
+ {children ? ( + children + ) : ( + + )} +
+
+ ) + } + return null +} + +export default TooltipModal diff --git a/src/supabase/database.types.ts b/src/supabase/database.types.ts index 175c22e..8bf7fd4 100644 --- a/src/supabase/database.types.ts +++ b/src/supabase/database.types.ts @@ -3,7 +3,7 @@ export type Json = | number | boolean | null - | {[key: string]: Json | undefined} + | { [key: string]: Json | undefined } | Json[] export type Database = { @@ -17,6 +17,8 @@ export type Database = { post_id: string | null updated_at: string | null user_id: string | null + user_role: string + username: string } Insert: { content: string @@ -25,6 +27,8 @@ export type Database = { post_id?: string | null updated_at?: string | null user_id?: string | null + user_role: string + username?: string } Update: { content?: string @@ -33,54 +37,76 @@ export type Database = { post_id?: string | null updated_at?: string | null user_id?: string | null + user_role?: string + username?: string } Relationships: [ { - foreignKeyName: 'Comments_post_id_fkey' - columns: ['post_id'] + foreignKeyName: "Comments_post_id_fkey" + columns: ["post_id"] isOneToOne: false - referencedRelation: 'posts' - referencedColumns: ['id'] + referencedRelation: "posts" + referencedColumns: ["id"] }, { - foreignKeyName: 'Comments_user_id_fkey' - columns: ['user_id'] + foreignKeyName: "Comments_user_id_fkey" + columns: ["user_id"] isOneToOne: false - referencedRelation: 'users' - referencedColumns: ['id'] + referencedRelation: "users" + referencedColumns: ["id"] + }, + { + foreignKeyName: "comments_user_role_fkey" + columns: ["user_role"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["role"] }, ] } favorites: { Row: { created_at: string + id: string post_id: string | null + post_title: string user_id: string | null } Insert: { created_at?: string + id?: string post_id?: string | null + post_title: string user_id?: string | null } Update: { created_at?: string + id?: string post_id?: string | null + post_title?: string user_id?: string | null } Relationships: [ { - foreignKeyName: 'Favorites_post_id_fkey' - columns: ['post_id'] + foreignKeyName: "Favorites_post_id_fkey" + columns: ["post_id"] + isOneToOne: false + referencedRelation: "posts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "favorites_post_title_fkey" + columns: ["post_title"] isOneToOne: false - referencedRelation: 'posts' - referencedColumns: ['id'] + referencedRelation: "posts" + referencedColumns: ["title"] }, { - foreignKeyName: 'Favorites_user_id_fkey' - columns: ['user_id'] + foreignKeyName: "Favorites_user_id_fkey" + columns: ["user_id"] isOneToOne: false - referencedRelation: 'users' - referencedColumns: ['id'] + referencedRelation: "users" + referencedColumns: ["id"] }, ] } @@ -117,11 +143,11 @@ export type Database = { } Relationships: [ { - foreignKeyName: 'Posts_author_id_fkey' - columns: ['author_id'] + foreignKeyName: "Posts_author_id_fkey" + columns: ["author_id"] isOneToOne: false - referencedRelation: 'users' - referencedColumns: ['id'] + referencedRelation: "users" + referencedColumns: ["id"] }, ] } @@ -131,18 +157,21 @@ export type Database = { email: string id: string role: string + username: string } Insert: { created_at?: string email: string id?: string role: string + username?: string } Update: { created_at?: string email?: string id?: string role?: string + username?: string } Relationships: [] } @@ -162,27 +191,27 @@ export type Database = { } } -type PublicSchema = Database[Extract] +type PublicSchema = Database[Extract] export type Tables< PublicTableNameOrOptions extends - | keyof (PublicSchema['Tables'] & PublicSchema['Views']) - | {schema: keyof Database}, - TableName extends PublicTableNameOrOptions extends {schema: keyof Database} - ? keyof (Database[PublicTableNameOrOptions['schema']]['Tables'] & - Database[PublicTableNameOrOptions['schema']]['Views']) + | keyof (PublicSchema["Tables"] & PublicSchema["Views"]) + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] & + Database[PublicTableNameOrOptions["schema"]]["Views"]) : never = never, -> = PublicTableNameOrOptions extends {schema: keyof Database} - ? (Database[PublicTableNameOrOptions['schema']]['Tables'] & - Database[PublicTableNameOrOptions['schema']]['Views'])[TableName] extends { +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? (Database[PublicTableNameOrOptions["schema"]]["Tables"] & + Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends { Row: infer R } ? R : never - : PublicTableNameOrOptions extends keyof (PublicSchema['Tables'] & - PublicSchema['Views']) - ? (PublicSchema['Tables'] & - PublicSchema['Views'])[PublicTableNameOrOptions] extends { + : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] & + PublicSchema["Views"]) + ? (PublicSchema["Tables"] & + PublicSchema["Views"])[PublicTableNameOrOptions] extends { Row: infer R } ? R @@ -191,19 +220,19 @@ export type Tables< export type TablesInsert< PublicTableNameOrOptions extends - | keyof PublicSchema['Tables'] - | {schema: keyof Database}, - TableName extends PublicTableNameOrOptions extends {schema: keyof Database} - ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + | keyof PublicSchema["Tables"] + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] : never = never, -> = PublicTableNameOrOptions extends {schema: keyof Database} - ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { Insert: infer I } ? I : never - : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] - ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { + : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] + ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { Insert: infer I } ? I @@ -212,19 +241,19 @@ export type TablesInsert< export type TablesUpdate< PublicTableNameOrOptions extends - | keyof PublicSchema['Tables'] - | {schema: keyof Database}, - TableName extends PublicTableNameOrOptions extends {schema: keyof Database} - ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + | keyof PublicSchema["Tables"] + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] : never = never, -> = PublicTableNameOrOptions extends {schema: keyof Database} - ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { Update: infer U } ? U : never - : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] - ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { + : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] + ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { Update: infer U } ? U @@ -233,13 +262,13 @@ export type TablesUpdate< export type Enums< PublicEnumNameOrOptions extends - | keyof PublicSchema['Enums'] - | {schema: keyof Database}, - EnumName extends PublicEnumNameOrOptions extends {schema: keyof Database} - ? keyof Database[PublicEnumNameOrOptions['schema']]['Enums'] + | keyof PublicSchema["Enums"] + | { schema: keyof Database }, + EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"] : never = never, -> = PublicEnumNameOrOptions extends {schema: keyof Database} - ? Database[PublicEnumNameOrOptions['schema']]['Enums'][EnumName] - : PublicEnumNameOrOptions extends keyof PublicSchema['Enums'] - ? PublicSchema['Enums'][PublicEnumNameOrOptions] +> = PublicEnumNameOrOptions extends { schema: keyof Database } + ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName] + : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"] + ? PublicSchema["Enums"][PublicEnumNameOrOptions] : never diff --git a/src/templates/BlogDetailPage/index.tsx b/src/templates/BlogDetailPage/index.tsx index 40a7ac6..4ce1e7c 100644 --- a/src/templates/BlogDetailPage/index.tsx +++ b/src/templates/BlogDetailPage/index.tsx @@ -1,5 +1,6 @@ 'use client' +import axios from 'axios' import {useParams} from 'next/navigation' import React, {Suspense, useCallback, useEffect, useState} from 'react' import {remark} from 'remark' @@ -13,9 +14,11 @@ import {type Database} from '@/supabase/database.types' const BlogDetailPage = () => { const params = useParams() + const [blogDetailData, setBlogDetailData] = useState< Database['public']['Tables']['posts']['Row'] | null >(null) + const [htmlContent, setHtmlContent] = useState('') const convertMarkdownToHtml = useCallback(async (markdownBody: string) => { @@ -24,10 +27,10 @@ const BlogDetailPage = () => { }, []) const fetchBlogDetailData = useCallback(async () => { - const res = await fetch(`/api/BlogDetail?id=${params.id}`) - const data = await res.json() + const res = await axios(`/api/BlogDetail?id=${params.id}`) + const data = await res.data - setBlogDetailData(data) + setBlogDetailData(data.post) }, [params.id]) useEffect(() => { diff --git a/src/templates/BlogPage/index.tsx b/src/templates/BlogPage/index.tsx index e1527b0..70312cc 100644 --- a/src/templates/BlogPage/index.tsx +++ b/src/templates/BlogPage/index.tsx @@ -1,8 +1,8 @@ 'use client' -import React, {useEffect, useState} from 'react' +import axios from 'axios' +import React, {useCallback, useEffect, useState} from 'react' -import {getBlogs} from '@/app/api/getBlog' import BlogCard from '@/components/BlogCard' import Layout from '@/components/Layout' import {type Database} from '@/supabase/database.types' @@ -12,14 +12,17 @@ const BlogPage = () => { Database['public']['Tables']['posts']['Row'][] | null >() - useEffect(() => { - getBlogs().then(res => { - if (res) { - setBlogsData(res) - } - }) + const fetchBlogsData = useCallback(async () => { + const res = await axios(`/api/Blog`) + const data = await res.data + + setBlogsData(data.posts) }, []) + useEffect(() => { + void fetchBlogsData() + }, [fetchBlogsData]) + return ( <> diff --git a/yarn.lock b/yarn.lock index 0e491f2..1bd43dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -629,6 +629,11 @@ ast-types-flow@^0.0.8: resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" @@ -641,6 +646,15 @@ axe-core@^4.10.0: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== +axios@^1.7.7: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" @@ -776,6 +790,13 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + comma-separated-tokens@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" @@ -910,6 +931,11 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + dequal@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" @@ -1420,6 +1446,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -1435,6 +1466,15 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2334,6 +2374,18 @@ micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.3" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + minimatch@9.0.3: version "9.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" @@ -2688,6 +2740,11 @@ property-information@^6.0.0: resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"