ぼざクリタグ広場 https://hub.nizika.monster
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

128 lines
3.4 KiB

  1. import axios from 'axios'
  2. import toCamel from 'camelcase-keys'
  3. import { useEffect, useState } from 'react'
  4. import { Helmet } from 'react-helmet-async'
  5. import { useParams } from 'react-router-dom'
  6. import PostList from '@/components/PostList'
  7. import TagDetailSidebar from '@/components/TagDetailSidebar'
  8. import PostEditForm from '@/components/PostEditForm'
  9. import PostEmbed from '@/components/PostEmbed'
  10. import TabGroup, { Tab } from '@/components/common/TabGroup'
  11. import MainArea from '@/components/layout/MainArea'
  12. import { Button } from '@/components/ui/button'
  13. import { toast } from '@/components/ui/use-toast'
  14. import { API_BASE_URL, SITE_TITLE } from '@/config'
  15. import { cn } from '@/lib/utils'
  16. import NotFound from '@/pages/NotFound'
  17. import ServiceUnavailable from '@/pages/ServiceUnavailable'
  18. import type { FC } from 'react'
  19. import type { Post, User } from '@/types'
  20. type Props = { user: User | null }
  21. export default (({ user }: Props) => {
  22. const { id } = useParams ()
  23. const [post, setPost] = useState<Post | null> (null)
  24. const [status, setStatus] = useState (200)
  25. const changeViewedFlg = async () => {
  26. const url = `${ API_BASE_URL }/posts/${ id }/viewed`
  27. const opt = { headers: { 'X-Transfer-Code': localStorage.getItem ('user_code') || '' } }
  28. try
  29. {
  30. if (post!.viewed)
  31. await axios.delete (url, opt)
  32. else
  33. await axios.post (url, { }, opt)
  34. // 通信に成功したら “閲覧済” をトグル
  35. setPost (post => ({ ...post!, viewed: !(post!.viewed) }))
  36. }
  37. catch
  38. {
  39. toast ({ title: '失敗……', description: '通信に失敗しました……' })
  40. }
  41. }
  42. useEffect (() => {
  43. setPost (null)
  44. if (!(id))
  45. return
  46. const fetchPost = async () => {
  47. try
  48. {
  49. const res = await axios.get (`${ API_BASE_URL }/posts/${ id }`, { headers: {
  50. 'X-Transfer-Code': localStorage.getItem ('user_code') ?? '' } })
  51. setPost (toCamel (res.data as any, { deep: true }) as Post)
  52. }
  53. catch (err)
  54. {
  55. if (axios.isAxiosError (err))
  56. setStatus (err.status ?? 200)
  57. }
  58. }
  59. fetchPost ()
  60. }, [id])
  61. switch (status)
  62. {
  63. case 404:
  64. return <NotFound/>
  65. case 503:
  66. return <ServiceUnavailable/>
  67. }
  68. const viewedClass = (post?.viewed
  69. ? 'bg-blue-600 hover:bg-blue-700'
  70. : 'bg-gray-500 hover:bg-gray-600')
  71. return (
  72. <div className="md:flex md:flex-1">
  73. <Helmet>
  74. {(post?.thumbnail || post?.thumbnailBase) && (
  75. <meta name="thumbnail" content={post.thumbnail || post.thumbnailBase}/>)}
  76. {post && <title>{`${ post.title || post.url } | ${ SITE_TITLE }`}</title>}
  77. </Helmet>
  78. <div className="hidden md:block">
  79. <TagDetailSidebar post={post}/>
  80. </div>
  81. <MainArea>
  82. {post
  83. ? (
  84. <>
  85. <PostEmbed post={post}/>
  86. <Button onClick={changeViewedFlg}
  87. className={cn ('text-white', viewedClass)}>
  88. {post.viewed ? '閲覧済' : '未閲覧'}
  89. </Button>
  90. <TabGroup>
  91. <Tab name="関聯">
  92. {post.related.length > 0
  93. ? <PostList posts={post.related}/>
  94. : 'まだないよ(笑)'}
  95. </Tab>
  96. {['admin', 'member'].some (r => user?.role === r) && (
  97. <Tab name="編輯">
  98. <PostEditForm post={post}
  99. onSave={newPost => {
  100. setPost (newPost)
  101. toast ({ description: '更新しました.' })
  102. }}/>
  103. </Tab>)}
  104. </TabGroup>
  105. </>)
  106. : 'Loading...'}
  107. </MainArea>
  108. <div className="md:hidden">
  109. <TagDetailSidebar post={post}/>
  110. </div>
  111. </div>)
  112. }) satisfies FC<Props>