이미지 인풋박스 설정

This commit is contained in:
2026-03-25 22:08:55 +09:00
parent fd8ea31fd8
commit bc8dc79295
2 changed files with 79 additions and 57 deletions

View File

@@ -1,59 +1,88 @@
.wrapper { .wrapper {
@apply w-full max-w-md mx-auto p-4; display: flex;
} align-items: center;
justify-content: center;
.dropzone { flex-direction: column;
@apply border-2 rounded-lg p-4 flex flex-col items-center justify-center transition-colors duration-150; min-height: 100vh;
} width: 100%;
padding: 1rem;
.normal {
@apply border-dashed border-gray-300 bg-white;
}
.dragging {
@apply border-indigo-400 bg-indigo-50;
} }
.fileInput { .fileInput {
@apply hidden; display: none;
}
.previewBox {
@apply w-32 h-32 rounded-md overflow-hidden bg-gray-100 flex items-center justify-center mb-3;
}
.previewImg {
@apply object-cover w-full h-full cursor-pointer;
}
.placeholder {
@apply text-gray-400 text-center text-sm px-2;
} }
.row { .row {
@apply w-full flex gap-2 items-center; width: 100%;
max-width: 520px;
display: flex;
gap: 0.5rem;
align-items: center;
} }
.textInput { .textInput {
@apply flex-1 px-3 py-2 border rounded h-10 cursor-pointer bg-white text-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500; flex: 1;
height: 2.75rem;
padding: 0.65rem 0.75rem;
border: 1px solid #cbd5e1;
border-radius: 0.55rem;
background-color: white;
color: #334155;
font-size: 0.95rem;
outline: none;
}
.textInput:focus {
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15);
border-color: #2563eb;
} }
.btn { .btn {
@apply px-4 py-2 rounded text-white; height: 2.75rem;
padding: 0.6rem 1rem;
border-radius: 0.55rem;
background-color: #6366F1;
color: #ffffff;
font-weight: 700;
cursor: pointer;
}
.btn:hover {
background-color: #4F46E5;
} }
.btnLoading { .btnLoading {
@apply bg-indigo-300; background-color: #A5B4FC;
cursor: wait;
} }
.btnPrimary { .messageArea {
@apply bg-indigo-600 hover:bg-indigo-700; width: 100%;
max-width: 520px;
margin-top: 0.75rem;
text-align: left;
} }
.errorMsg { .errorMsg {
@apply mt-3 text-sm text-red-600; margin: 0.25rem 0 0;
color: #dc2626;
} }
.successMsg { .successMsg {
@apply mt-3 text-sm text-green-600; margin: 0.25rem 0 0;
color: #16a34a;
}
.previewContainer {
margin-bottom: 1.5rem;
display: flex;
justify-content: center;
}
.previewImage {
width: 200px;
height: 120px;
border-radius: 0.55rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
object-fit: cover;
} }

View File

@@ -9,12 +9,14 @@ const ImgInputForm = () => {
const fileRef = useRef<HTMLInputElement | null>(null) const fileRef = useRef<HTMLInputElement | null>(null)
const [fileName, setFileName] = useState('') const [fileName, setFileName] = useState('')
const [preview, setPreview] = useState<string | null>(null) const [preview, setPreview] = useState<string | null>(null)
const [showPreview, setShowPreview] = useState(false)
const [isDragging, setIsDragging] = useState(false) const [isDragging, setIsDragging] = useState(false)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState<string | null>(null) const [success, setSuccess] = useState<string | null>(null)
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:9001' //process.env.NEXT_PUBLIC_API_URL ||
const API_BASE = 'http://localhost:9001'
const handleFile = useCallback((file: File) => { const handleFile = useCallback((file: File) => {
setError(null) setError(null)
@@ -30,6 +32,7 @@ const ImgInputForm = () => {
setFileName(file.name) setFileName(file.name)
setPreview(URL.createObjectURL(file)) setPreview(URL.createObjectURL(file))
setShowPreview(false)
// keep the selected file in the hidden input // keep the selected file in the hidden input
if (fileRef.current) { if (fileRef.current) {
const dataTransfer = new DataTransfer() const dataTransfer = new DataTransfer()
@@ -75,6 +78,7 @@ const ImgInputForm = () => {
return return
} }
setLoading(true) setLoading(true)
setShowPreview(false)
try { try {
const fd = new FormData() const fd = new FormData()
fd.append('image', file) fd.append('image', file)
@@ -85,6 +89,7 @@ const ImgInputForm = () => {
}) })
if (!res.ok) throw new Error('업로드 실패') if (!res.ok) throw new Error('업로드 실패')
setSuccess('업로드 성공') setSuccess('업로드 성공')
setShowPreview(true)
} catch (err) { } catch (err) {
setError((err as Error).message || '업로드 중 오류가 발생했습니다') setError((err as Error).message || '업로드 중 오류가 발생했습니다')
} finally { } finally {
@@ -93,13 +98,8 @@ const ImgInputForm = () => {
} }
return ( return (
<div className={styles.wrapper}> <>
<div <div className={styles.wrapper}>
className={`${styles.dropzone} ${isDragging ? styles.dragging : styles.normal}`}
onDrop={onDrop}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
>
<input <input
ref={fileRef} ref={fileRef}
type="file" type="file"
@@ -108,18 +108,11 @@ const ImgInputForm = () => {
onChange={onChange} onChange={onChange}
/> />
<div className={styles.previewBox}> {preview && (
{preview ? ( <div className={styles.previewContainer}>
// preview kept modest in size <img src={preview} alt="selected preview" className={styles.previewImage} />
// eslint-disable-next-line @next/next/no-img-element </div>
<img src={preview} alt="preview" className={styles.previewImg} onClick={onClickPick} /> )}
) : (
<div className={styles.placeholder}>
<br />
</div>
)}
</div>
<div className={styles.row}> <div className={styles.row}>
<input <input
@@ -132,17 +125,17 @@ const ImgInputForm = () => {
/> />
<button <button
onClick={upload} onClick={upload}
disabled={loading} disabled={loading || !fileName}
className={`${styles.btn} ${loading ? styles.btnLoading : styles.btnPrimary}`} className={`${styles.btn} ${loading ? styles.btnLoading : styles.btnPrimary}`}
> >
{loading ? '업로드 중...' : '업로드'} {loading ? '업로드 중...' : '업로드'}
</button> </button>
</div> </div>
{error && <p className={styles.errorMsg}>{error}</p>} {error && <p className={`${styles.errorMsg}`}>{error}</p>}
{success && <p className={styles.successMsg}>{success}</p>} {success && <p className={styles.successMsg}>{success}</p>}
</div> </div>
</div> </>
) )
} }