import { useCallback, useEffect, useMemo, useRef, useState } 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 FieldError from '@/components/common/FieldError' import Form from '@/components/common/Form' import FormField from '@/components/common/FormField' 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 { canEditContent } from '@/lib/users' import { inputClass } from '@/lib/utils' import { useValidationErrors } from '@/lib/useValidationErrors' import Forbidden from '@/pages/Forbidden' import type { FC } from 'react' import type { User } from '@/types' type Props = { user: User | null } type PostFormField = 'url' | 'title' | 'tags' | 'parentPostIds' | 'videoMs' | 'originalCreatedAt' | 'thumbnail' const PostNewPage: FC = ({ user }) => { const editable = canEditContent (user) const navigate = useNavigate () const { baseErrors, fieldErrors, clearValidationErrors, applyValidationError } = useValidationErrors () const [originalCreatedBefore, setOriginalCreatedBefore] = useState (null) const [originalCreatedFrom, setOriginalCreatedFrom] = useState (null) const [parentPostIds, setParentPostIds] = useState ('') const [tags, setTags] = useState ('') const [duration, setDuration] = useState ('') const [thumbnailFile, setThumbnailFile] = useState (null) const [thumbnailLoading, setThumbnailLoading] = useState (false) const [thumbnailPreview, setThumbnailPreview] = useState ('') const [title, setTitle] = useState ('') const [titleLoading, setTitleLoading] = useState (false) const [url, setURL] = useState ('') const thumbnailPreviewRef = useRef ('') const videoFlg = useMemo (() => tags.split (/\s+/).some ( tag => tag.replace (/\[.*\]$/, '') === '動画'), [tags]) const handleSubmit = async () => { clearValidationErrors () const formData = new FormData formData.append ('title', title) formData.append ('url', url) formData.append ('tags', tags) formData.append ('parent_post_ids', parentPostIds) if (videoFlg && duration !== '') formData.append ('duration', duration) 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 (e) { applyValidationError (e) toast ({ title: '投稿失敗', description: '入力を確認してください.' }) } } const fetchTitle = useCallback (async () => { setTitleLoading (true) try { const data = await apiGet<{ title: string }> ('/preview/title', { params: { url } }) setTitle (data.title || '') } finally { setTitleLoading (false) } }, [url]) const fetchThumbnail = useCallback (async () => { setThumbnailPreview ('') setThumbnailFile (null) setThumbnailLoading (true) if (thumbnailPreviewRef.current) URL.revokeObjectURL (thumbnailPreviewRef.current) try { const data = await apiGet ('/preview/thumbnail', { params: { url }, responseType: 'blob' }) const imageURL = URL.createObjectURL (data) setThumbnailPreview (imageURL) setThumbnailFile (new File ([data], 'thumbnail.png', { type: data.type || 'image/png' })) } finally { setThumbnailLoading (false) } }, [url]) useEffect (() => { thumbnailPreviewRef.current = thumbnailPreview }, [thumbnailPreview]) if (!(editable)) return return ( {`広場に投稿を追加 | ${ SITE_TITLE }`}
広場に投稿を追加する {/* URL */} {({ describedBy, invalid }) => ( setURL (e.target.value)} aria-describedby={describedBy} aria-invalid={invalid} className={inputClass (invalid)}/>)} {/* タイトル */} {({ describedBy, invalid }) => (
setTitle (ev.target.value)} disabled={titleLoading}/>
必要なタイミングで URL から取得できます.
)}
{/* サムネール */} {({ describedBy, invalid }) => ( <>
必要なタイミングで URL から取得できます.
{thumbnailLoading && (

Loading...

)} { const file = e.target.files?.[0] if (file) { setThumbnailFile (file) setThumbnailPreview (URL.createObjectURL (file)) } }}/> {thumbnailPreview && ( preview)} )}
{/* 親投稿 */} {({ describedBy, invalid }) => ( setParentPostIds (e.target.value)} aria-describedby={describedBy} aria-invalid={invalid} className={inputClass (invalid)}/>)} {/* タグ */} {/* オリジナルの作成日時 */} {/* 動画時間 */} {(videoFlg && {({ invalid }) => ( setDuration (e.target.value)} aria-invalid={invalid} className={inputClass (invalid)}/>)} )} {/* 送信 */}
) } export default PostNewPage