7b15cb2c5a
#99 #99 #99 #99 #99 #99 #99 #99 #99 #99 Co-authored-by: miteruzo <miteruzo@naver.com> Reviewed-on: #303
125 lines
3.1 KiB
TypeScript
125 lines
3.1 KiB
TypeScript
import { useState } from 'react'
|
||
import { Helmet } from 'react-helmet-async'
|
||
import { useLocation, useNavigate } from 'react-router-dom'
|
||
|
||
import Form from '@/components/common/Form'
|
||
import Label from '@/components/common/Label'
|
||
import PageTitle from '@/components/common/PageTitle'
|
||
import TagInput from '@/components/common/TagInput'
|
||
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 { apiPost } from '@/lib/api'
|
||
|
||
import type { FC } from 'react'
|
||
|
||
|
||
export default (() => {
|
||
const location = useLocation ()
|
||
const query = new URLSearchParams (location.search)
|
||
const tagQuery = query.get ('tag') ?? ''
|
||
|
||
const navigate = useNavigate ()
|
||
|
||
const [file, setFile] = useState<File | null> (null)
|
||
const [filePreview, setFilePreview] = useState ('')
|
||
const [sending, setSending] = useState (false)
|
||
const [tag, setTag] = useState (tagQuery)
|
||
const [url, setURL] = useState ('')
|
||
|
||
const handleSubmit = async () => {
|
||
const formData = new FormData
|
||
if (tag)
|
||
formData.append ('tag', tag)
|
||
if (file)
|
||
formData.append ('file', file)
|
||
if (url)
|
||
formData.append ('url', url)
|
||
|
||
try
|
||
{
|
||
setSending (true)
|
||
await apiPost ('/materials', formData)
|
||
toast ({ title: '送信成功!' })
|
||
navigate (`/materials?tag=${ encodeURIComponent (tag) }`)
|
||
}
|
||
catch
|
||
{
|
||
toast ({ title: '送信失敗……', description: '入力を見直してください.' })
|
||
}
|
||
finally
|
||
{
|
||
setSending (false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<MainArea>
|
||
<Helmet>
|
||
<title>{`素材追加 | ${ SITE_TITLE }`}</title>
|
||
</Helmet>
|
||
|
||
<Form>
|
||
<PageTitle>素材追加</PageTitle>
|
||
|
||
{/* タグ */}
|
||
<div>
|
||
<Label>タグ</Label>
|
||
<TagInput value={tag} setValue={setTag}/>
|
||
</div>
|
||
|
||
{/* ファイル */}
|
||
<div>
|
||
<Label>ファイル</Label>
|
||
<input
|
||
type="file"
|
||
accept="image/*,video/*,audio/*"
|
||
onChange={e => {
|
||
const f = e.target.files?.[0]
|
||
setFile (f ?? null)
|
||
setFilePreview (f ? URL.createObjectURL (f) : '')
|
||
}}/>
|
||
{(file && filePreview) && (
|
||
(/image\/.*/.test (file.type) && (
|
||
<img
|
||
src={filePreview}
|
||
alt="preview"
|
||
className="mt-2 max-h-48 rounded border"/>))
|
||
|| (/video\/.*/.test (file.type) && (
|
||
<video
|
||
src={filePreview}
|
||
controls
|
||
className="mt-2 max-h-48 rounded border"/>))
|
||
|| (/audio\/.*/.test (file.type) && (
|
||
<audio
|
||
src={filePreview}
|
||
controls
|
||
className="mt-2 max-h-48"/>))
|
||
|| (
|
||
<p className="text-red-600 dark:text-red-400">
|
||
その形式のファイルには対応していません.
|
||
</p>))}
|
||
</div>
|
||
|
||
{/* 参考 URL */}
|
||
<div>
|
||
<Label>参考 URL</Label>
|
||
<input
|
||
type="url"
|
||
value={url}
|
||
onChange={e => setURL (e.target.value)}
|
||
className="w-full border p-2 rounded"/>
|
||
</div>
|
||
|
||
{/* 送信 */}
|
||
<Button
|
||
onClick={handleSubmit}
|
||
className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400"
|
||
disabled={sending}>
|
||
追加
|
||
</Button>
|
||
</Form>
|
||
</MainArea>)
|
||
}) satisfies FC
|