|
- import { useEffect, useState, useRef } from 'react'
- import { Helmet } from 'react-helmet-async'
- import { useNavigate } from 'react-router-dom'
-
- import PostFormTagsArea from '@/components/PostFormTagsArea'
- import PostOriginalCreatedTimeField from '@/components/PostOriginalCreatedTimeField'
- import Form from '@/components/common/Form'
- import Label from '@/components/common/Label'
- import PageTitle from '@/components/common/PageTitle'
- import MainArea from '@/components/layout/MainArea'
- import { Button } from '@/components/ui/button'
- import { toast } from '@/components/ui/use-toast'
- import { SITE_TITLE } from '@/config'
- import { apiGet, apiPost } from '@/lib/api'
- import Forbidden from '@/pages/Forbidden'
-
- import type { FC } from 'react'
-
- import type { User } from '@/types'
-
- type Props = { user: User | null }
-
-
- export default (({ user }: Props) => {
- if (!(['admin', 'member'].some (r => user?.role === r)))
- return <Forbidden/>
-
- const navigate = useNavigate ()
-
- const [originalCreatedBefore, setOriginalCreatedBefore] = useState<string | null> (null)
- const [originalCreatedFrom, setOriginalCreatedFrom] = useState<string | null> (null)
- const [tags, setTags] = useState ('')
- const [thumbnailAutoFlg, setThumbnailAutoFlg] = useState (true)
- const [thumbnailFile, setThumbnailFile] = useState<File | null> (null)
- const [thumbnailLoading, setThumbnailLoading] = useState (false)
- const [thumbnailPreview, setThumbnailPreview] = useState<string> ('')
- const [title, setTitle] = useState ('')
- const [titleAutoFlg, setTitleAutoFlg] = useState (true)
- const [titleLoading, setTitleLoading] = useState (false)
- const [url, setURL] = useState ('')
-
- const previousURLRef = useRef ('')
-
- const handleSubmit = async () => {
- const formData = new FormData
- formData.append ('title', title)
- formData.append ('url', url)
- formData.append ('tags', tags)
- if (thumbnailFile)
- formData.append ('thumbnail', thumbnailFile)
- if (originalCreatedFrom)
- formData.append ('original_created_from', originalCreatedFrom)
- if (originalCreatedBefore)
- formData.append ('original_created_before', originalCreatedBefore)
-
- try
- {
- await apiPost ('/posts', formData, { headers: { 'Content-Type': 'multipart/form-data' } })
- toast ({ title: '投稿成功!' })
- navigate ('/posts')
- }
- catch
- {
- toast ({ title: '投稿失敗', description: '入力を確認してください。' })
- }
- }
-
- useEffect (() => {
- if (titleAutoFlg && url)
- fetchTitle ()
- }, [titleAutoFlg])
-
- useEffect (() => {
- if (thumbnailAutoFlg && url)
- fetchThumbnail ()
- }, [thumbnailAutoFlg])
-
- const handleURLBlur = () => {
- if (!(url) || url === previousURLRef.current)
- return
-
- if (titleAutoFlg)
- fetchTitle ()
- if (thumbnailAutoFlg)
- fetchThumbnail ()
- previousURLRef.current = url
- }
-
- const fetchTitle = async () => {
- setTitle ('')
- setTitleLoading (true)
- const data = await apiGet<{ title: string }> ('/preview/title', { params: { url } })
- setTitle (data.title || '')
- setTitleLoading (false)
- }
-
- const fetchThumbnail = async () => {
- setThumbnailPreview ('')
- setThumbnailFile (null)
- setThumbnailLoading (true)
- if (thumbnailPreview)
- URL.revokeObjectURL (thumbnailPreview)
- const data = await apiGet<Blob> ('/preview/thumbnail',
- { params: { url }, responseType: 'blob' })
- const imageURL = URL.createObjectURL (data)
- setThumbnailPreview (imageURL)
- setThumbnailFile (new File ([data],
- 'thumbnail.png',
- { type: data.type || 'image/png' }))
- setThumbnailLoading (false)
- }
-
- return (
- <MainArea>
- <Helmet>
- <title>{`広場に投稿を追加 | ${ SITE_TITLE }`}</title>
- </Helmet>
- <Form>
- <PageTitle>広場に投稿を追加する</PageTitle>
-
- {/* URL */}
- <div>
- <Label>URL</Label>
- <input type="url"
- placeholder="例:https://www.nicovideo.jp/watch/..."
- value={url}
- onChange={e => setURL (e.target.value)}
- className="w-full border p-2 rounded"
- onBlur={handleURLBlur}/>
- </div>
-
- {/* タイトル */}
- <div>
- <Label checkBox={{
- label: '自動',
- checked: titleAutoFlg,
- onChange: ev => setTitleAutoFlg (ev.target.checked)}}>
- タイトル
- </Label>
- <input type="text"
- className="w-full border rounded p-2"
- value={title}
- placeholder={titleLoading ? 'Loading...' : ''}
- onChange={ev => setTitle (ev.target.value)}
- disabled={titleAutoFlg}/>
- </div>
-
- {/* サムネール */}
- <div>
- <Label checkBox={{
- label: '自動',
- checked: thumbnailAutoFlg,
- onChange: ev => setThumbnailAutoFlg (ev.target.checked)}}>
- サムネール
- </Label>
- {thumbnailAutoFlg
- ? (thumbnailLoading
- ? <p className="text-gray-500 text-sm">Loading...</p>
- : !(thumbnailPreview) && (
- <p className="text-gray-500 text-sm">
- URL から自動取得されます。
- </p>))
- : (
- <input type="file"
- accept="image/*"
- onChange={e => {
- const file = e.target.files?.[0]
- if (file)
- {
- setThumbnailFile (file)
- setThumbnailPreview (URL.createObjectURL (file))
- }
- }}/>)}
- {thumbnailPreview && (
- <img src={thumbnailPreview}
- alt="preview"
- className="mt-2 max-h-48 rounded border"/>)}
- </div>
-
- {/* タグ */}
- <PostFormTagsArea tags={tags} setTags={setTags}/>
-
- {/* オリジナルの作成日時 */}
- <PostOriginalCreatedTimeField
- originalCreatedFrom={originalCreatedFrom}
- setOriginalCreatedFrom={setOriginalCreatedFrom}
- originalCreatedBefore={originalCreatedBefore}
- setOriginalCreatedBefore={setOriginalCreatedBefore}/>
-
- {/* 送信 */}
- <Button onClick={handleSubmit}
- className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400"
- disabled={titleLoading || thumbnailLoading}>
- 追加
- </Button>
- </Form>
- </MainArea>)
- }) satisfies FC<Props>
|