ぼざクリタグ広場 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.
 
 
 
 
 
 

125 lines
3.1 KiB

  1. import { useState } from 'react'
  2. import { Helmet } from 'react-helmet-async'
  3. import { useLocation, useNavigate } from 'react-router-dom'
  4. import Form from '@/components/common/Form'
  5. import Label from '@/components/common/Label'
  6. import PageTitle from '@/components/common/PageTitle'
  7. import TagInput from '@/components/common/TagInput'
  8. import MainArea from '@/components/layout/MainArea'
  9. import { Button } from '@/components/ui/button'
  10. import { toast } from '@/components/ui/use-toast'
  11. import { SITE_TITLE } from '@/config'
  12. import { apiPost } from '@/lib/api'
  13. import type { FC } from 'react'
  14. export default (() => {
  15. const location = useLocation ()
  16. const query = new URLSearchParams (location.search)
  17. const tagQuery = query.get ('tag') ?? ''
  18. const navigate = useNavigate ()
  19. const [file, setFile] = useState<File | null> (null)
  20. const [filePreview, setFilePreview] = useState ('')
  21. const [sending, setSending] = useState (false)
  22. const [tag, setTag] = useState (tagQuery)
  23. const [url, setURL] = useState ('')
  24. const handleSubmit = async () => {
  25. const formData = new FormData
  26. if (tag)
  27. formData.append ('tag', tag)
  28. if (file)
  29. formData.append ('file', file)
  30. if (url)
  31. formData.append ('url', url)
  32. try
  33. {
  34. setSending (true)
  35. await apiPost ('/materials', formData)
  36. toast ({ title: '送信成功!' })
  37. navigate (`/materials?tag=${ encodeURIComponent (tag) }`)
  38. }
  39. catch
  40. {
  41. toast ({ title: '送信失敗……', description: '入力を見直してください.' })
  42. }
  43. finally
  44. {
  45. setSending (false)
  46. }
  47. }
  48. return (
  49. <MainArea>
  50. <Helmet>
  51. <title>{`素材追加 | ${ SITE_TITLE }`}</title>
  52. </Helmet>
  53. <Form>
  54. <PageTitle>素材追加</PageTitle>
  55. {/* タグ */}
  56. <div>
  57. <Label>タグ</Label>
  58. <TagInput value={tag} setValue={setTag}/>
  59. </div>
  60. {/* ファイル */}
  61. <div>
  62. <Label>ファイル</Label>
  63. <input
  64. type="file"
  65. accept="image/*,video/*,audio/*"
  66. onChange={e => {
  67. const f = e.target.files?.[0]
  68. setFile (f ?? null)
  69. setFilePreview (f ? URL.createObjectURL (f) : '')
  70. }}/>
  71. {(file && filePreview) && (
  72. (/image\/.*/.test (file.type) && (
  73. <img
  74. src={filePreview}
  75. alt="preview"
  76. className="mt-2 max-h-48 rounded border"/>))
  77. || (/video\/.*/.test (file.type) && (
  78. <video
  79. src={filePreview}
  80. controls
  81. className="mt-2 max-h-48 rounded border"/>))
  82. || (/audio\/.*/.test (file.type) && (
  83. <audio
  84. src={filePreview}
  85. controls
  86. className="mt-2 max-h-48"/>))
  87. || (
  88. <p className="text-red-600 dark:text-red-400">
  89. その形式のファイルには対応していません.
  90. </p>))}
  91. </div>
  92. {/* 参考 URL */}
  93. <div>
  94. <Label>参考 URL</Label>
  95. <input
  96. type="url"
  97. value={url}
  98. onChange={e => setURL (e.target.value)}
  99. className="w-full border p-2 rounded"/>
  100. </div>
  101. {/* 送信 */}
  102. <Button
  103. onClick={handleSubmit}
  104. className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400"
  105. disabled={sending}>
  106. 追加
  107. </Button>
  108. </Form>
  109. </MainArea>)
  110. }) satisfies FC