168 lines
5.7 KiB
TypeScript
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
|