프론트 수정
This commit is contained in:
@@ -1,73 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { callServer } from "@/lib/Auth"
|
||||
import { isLogin } from "@/utils/Login"
|
||||
|
||||
const ImgInputForm_client = () => {
|
||||
|
||||
//페이지 오픈 시 로그인 여부 확인
|
||||
useEffect(() => {
|
||||
isLogin
|
||||
})
|
||||
|
||||
const fileRef = useRef<HTMLInputElement|null>(null)
|
||||
const [fileName, setFileName] = useState('')
|
||||
const [preview, setPreview] = useState<String|null>()
|
||||
|
||||
const imgInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const selected = e.target.files?.[0];
|
||||
if (selected != null) {
|
||||
setFileName(selected?.name)
|
||||
setPreview(URL.createObjectURL(selected))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const inputFileClick = () => {
|
||||
const file = fileRef.current;
|
||||
if (file) file.click()
|
||||
}
|
||||
|
||||
const confirm = async () => {
|
||||
const files = fileRef.current?.files
|
||||
if (files) {
|
||||
const file = files[0]
|
||||
const fomrData = new FormData()
|
||||
fomrData.append("image", file)
|
||||
console.log("here")
|
||||
console.log(file.size)
|
||||
await fetch("http://localhost:9001/img/get-img", {
|
||||
method: "POST",
|
||||
body: fomrData
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-3 max-w-xs max-h-40">
|
||||
{ preview &&
|
||||
<img src={preview} sizes=""/>
|
||||
}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<input type="text"
|
||||
readOnly
|
||||
value={fileName}
|
||||
onClick={inputFileClick}
|
||||
className="w-lg 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" />
|
||||
<input type="file" ref={fileRef} className="hidden" accept="image/*" onChange={imgInput} />
|
||||
<button onClick={confirm} className="w-10 h-10 border rounded text-gray-700">확인</button>
|
||||
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ImgInputForm_client
|
||||
59
src/app/components/ImgInputForm.module.css
Normal file
59
src/app/components/ImgInputForm.module.css
Normal file
@@ -0,0 +1,59 @@
|
||||
.wrapper {
|
||||
@apply w-full max-w-md mx-auto p-4;
|
||||
}
|
||||
|
||||
.dropzone {
|
||||
@apply border-2 rounded-lg p-4 flex flex-col items-center justify-center transition-colors duration-150;
|
||||
}
|
||||
|
||||
.normal {
|
||||
@apply border-dashed border-gray-300 bg-white;
|
||||
}
|
||||
|
||||
.dragging {
|
||||
@apply border-indigo-400 bg-indigo-50;
|
||||
}
|
||||
|
||||
.fileInput {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.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 {
|
||||
@apply w-full flex gap-2 items-center;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply px-4 py-2 rounded text-white;
|
||||
}
|
||||
|
||||
.btnLoading {
|
||||
@apply bg-indigo-300;
|
||||
}
|
||||
|
||||
.btnPrimary {
|
||||
@apply bg-indigo-600 hover:bg-indigo-700;
|
||||
}
|
||||
|
||||
.errorMsg {
|
||||
@apply mt-3 text-sm text-red-600;
|
||||
}
|
||||
|
||||
.successMsg {
|
||||
@apply mt-3 text-sm text-green-600;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
'useClient'
|
||||
|
||||
import axios from "axios"
|
||||
|
||||
const ImgInputForm_server = () => {
|
||||
|
||||
}
|
||||
|
||||
export const isLogin = axios.create({
|
||||
|
||||
})
|
||||
|
||||
export default ImgInputForm_server
|
||||
149
src/app/components/ImgInputForm.tsx
Normal file
149
src/app/components/ImgInputForm.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
"use client"
|
||||
|
||||
import { useCallback, useRef, useState } 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'
|
||||
|
||||
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
|
||||
@@ -1,16 +1,9 @@
|
||||
|
||||
import ImgInputForm_client from '../components/ImgInputForm.client'
|
||||
import ImgInputForm_server from '../components/ImgInputForm.server'
|
||||
import ImgInputForm from '../components/ImgInputForm'
|
||||
|
||||
const MainPage = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ImgInputForm_server />
|
||||
<ImgInputForm_client />
|
||||
</>
|
||||
|
||||
)
|
||||
return <ImgInputForm />
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user