Files
ocr-nextjs/src/app/components/ImgInputForm.tsx
2026-02-27 18:13:28 +09:00

168 lines
5.7 KiB
TypeScript

"use client"
import { useCallback, useRef, useState, useEffect } from 'react'
import styles from './ImgInputForm.module.css'
const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB
const ImgInputForm = () => {
const fileRef = useRef<HTMLInputElement | null>(null)
const [fileName, setFileName] = useState('')
const [preview, setPreview] = useState<string | null>(null)
const [isDragging, setIsDragging] = useState(false)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState<string | null>(null)
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:9001'
useEffect(() => {
const checkAuth = async () => {
try {
const res = await fetch(`${API_BASE}/auth/check-token`, {
method: 'GET',
credentials: 'include',
})
if (!res.ok) throw new Error('인증 실패')
console.log('사용자 인증 성공')
} catch (err) {
console.error('사용자 인증 실패:', (err as Error).message)
setError('로그인이 필요합니다.')
}
}
checkAuth()
}, [])
const handleFile = useCallback((file: File) => {
setError(null)
setSuccess(null)
if (!file.type.startsWith('image/')) {
setError('이미지 파일만 업로드할 수 있습니다.')
return
}
if (file.size > MAX_FILE_SIZE) {
setError('파일 크기는 5MB 이하만 업로드 가능합니다.')
return
}
setFileName(file.name)
setPreview(URL.createObjectURL(file))
// keep the selected file in the hidden input
if (fileRef.current) {
const dataTransfer = new DataTransfer()
dataTransfer.items.add(file)
fileRef.current.files = dataTransfer.files
}
}, [])
const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
const file = e.target.files?.[0]
if (file) handleFile(file)
}
const onClickPick = () => fileRef.current?.click()
const onDrop: React.DragEventHandler<HTMLDivElement> = (e) => {
e.preventDefault()
setIsDragging(false)
const file = e.dataTransfer.files?.[0]
if (file) handleFile(file)
}
const onDragOver: React.DragEventHandler<HTMLDivElement> = (e) => {
e.preventDefault()
setIsDragging(true)
}
const onDragLeave: React.DragEventHandler<HTMLDivElement> = () => {
setIsDragging(false)
}
const upload = async () => {
setError(null)
setSuccess(null)
const files = fileRef.current?.files
if (!files || files.length === 0) {
setError('업로드할 파일을 선택해주세요.')
return
}
const file = files[0]
if (!file.type.startsWith('image/')) {
setError('이미지 파일만 업로드할 수 있습니다.')
return
}
setLoading(true)
try {
const fd = new FormData()
fd.append('image', file)
const res = await fetch(`${API_BASE}/img/get-img`, {
method: 'POST',
body: fd,
credentials: 'include'
})
if (!res.ok) throw new Error('업로드 실패')
setSuccess('업로드 성공')
} catch (err) {
setError((err as Error).message || '업로드 중 오류가 발생했습니다')
} finally {
setLoading(false)
}
}
return (
<div className={styles.wrapper}>
<div
className={`${styles.dropzone} ${isDragging ? styles.dragging : styles.normal}`}
onDrop={onDrop}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
>
<input
ref={fileRef}
type="file"
accept="image/*"
className={styles.fileInput}
onChange={onChange}
/>
<div className={styles.previewBox}>
{preview ? (
// preview kept modest in size
// eslint-disable-next-line @next/next/no-img-element
<img src={preview} alt="preview" className={styles.previewImg} onClick={onClickPick} />
) : (
<div className={styles.placeholder}>
<br />
</div>
)}
</div>
<div className={styles.row}>
<input
type="text"
readOnly
value={fileName}
onClick={onClickPick}
placeholder="선택된 파일 없음"
className={styles.textInput}
/>
<button
onClick={upload}
disabled={loading}
className={`${styles.btn} ${loading ? styles.btnLoading : styles.btnPrimary}`}
>
{loading ? '업로드 중...' : '업로드'}
</button>
</div>
{error && <p className={styles.errorMsg}>{error}</p>}
{success && <p className={styles.successMsg}>{success}</p>}
</div>
</div>
)
}
export default ImgInputForm