diff --git a/src/app/admin/block/link/components/form-input.tsx b/src/app/admin/block/link/components/form-input.tsx index 1a8cde2b..e0190815 100644 --- a/src/app/admin/block/link/components/form-input.tsx +++ b/src/app/admin/block/link/components/form-input.tsx @@ -3,17 +3,20 @@ type FormInputProps = { label: string; id: string; + selectedStyle?: string; } & React.ComponentPropsWithoutRef<"input">; export default function FormInput({ label, id, + selectedStyle, ...inputProps }: FormInputProps) { return (
diff --git a/src/app/admin/block/link/components/link-form.tsx b/src/app/admin/block/link/components/link-form.tsx index 70b1eac8..e428551c 100644 --- a/src/app/admin/block/link/components/link-form.tsx +++ b/src/app/admin/block/link/components/link-form.tsx @@ -1,6 +1,12 @@ "use client"; -import { FormEvent, useEffect, useState } from "react"; +import { + ChangeEvent, + FormEvent, + useCallback, + useEffect, + useState, +} from "react"; import StylePreview from "./style-preview"; import StyleType from "./style-type"; import FormInput from "./form-input"; @@ -43,7 +49,14 @@ export default function LinkForm() { const [title, setTitle] = useState(""); const [linkUrl, setLinkUrl] = useState(""); const [linkImg, setLinkImg] = useState(""); - const [isImageError, setIsImageError] = useState(false); + const [isLinkUrlError, setIsLinkUrlError] = useState(false); + const [isImgUrlError, setIsImgUrlError] = useState(false); + const [isImgUrlConnectionError, setIsImgUrlConnectionError] = useState(false); + + const isValidUrl = useCallback( + (url: string) => /^https?:\/\/.+\..+/.test(url), + [], + ); async function postLink() { const token = await getToken(); @@ -89,7 +102,7 @@ export default function LinkForm() { } useEffect(() => { - if (linkImg) setIsImageError(false); + if (linkImg) setIsImgUrlConnectionError(false); }, [linkImg]); const handleSubmit = (e: FormEvent) => { @@ -101,13 +114,33 @@ export default function LinkForm() { setLinkImg(""); }; + const handleLinkUrlChange = (e: ChangeEvent) => { + const newUrl = e.target.value; + setLinkUrl(newUrl); + if (newUrl.trim() === "") { + setIsLinkUrlError(false); + } else { + setIsLinkUrlError(!isValidUrl(newUrl)); + } + }; + const handleImgUrlChange = (e: ChangeEvent) => { + const newUrl = e.target.value; + setLinkImg(newUrl); + if (newUrl.trim() === "") { + setIsImgUrlError(false); + } else { + setIsImgUrlError(!isValidUrl(newUrl)); + } + }; + return ( <>
@@ -124,6 +157,8 @@ export default function LinkForm() { imgIdx={idx} selectedStyle={selectedStyle} onSelect={setSelectedStyle} + setLinkImg={setLinkImg} + setIsImgUrlConnectionError={setIsImgUrlConnectionError} /> ))} @@ -132,38 +167,55 @@ export default function LinkForm() {
{/* Info */} -
- setLinkUrl(e.target.value)} - placeholder="연결할 주소 url을 입력해주세요" - required - /> - setTitle(e.target.value)} - placeholder="타이틀을 입력해주세요" - required - /> -
+
+
+ + {isLinkUrlError && ( +
+ 올바른 URL 형식을 입력해주세요 +
+ )} +
+
+ setTitle(e.target.value)} + placeholder="타이틀을 입력해주세요" + required + /> +
+
setLinkImg(e.target.value)} + onChange={handleImgUrlChange} + selectedStyle={selectedStyle} placeholder="이미지 url을 입력해주세요" disabled={selectedStyle === "심플"} required={selectedStyle !== "심플"} /> - {isImageError && ( -
잘못된 이미지 경로입니다
+ {isImgUrlError && ( +
+ 올바른 URL 형식을 입력해주세요 +
+ )} + {isImgUrlConnectionError && ( +
+ 잘못된 이미지 경로입니다 +
)}
diff --git a/src/app/admin/block/link/components/style-preview.tsx b/src/app/admin/block/link/components/style-preview.tsx index 5fd46c2f..da827f6b 100644 --- a/src/app/admin/block/link/components/style-preview.tsx +++ b/src/app/admin/block/link/components/style-preview.tsx @@ -8,12 +8,14 @@ export default function StylePreview({ selectedStyle, title, linkImg, - setIsImageError, + setIsImgUrlConnectionError, + isValidUrl, }: { selectedStyle: string; title: string; linkImg: string; - setIsImageError: Dispatch>; + setIsImgUrlConnectionError: Dispatch>; + isValidUrl: (url: string) => boolean; }) { const placeholderImage = "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="; @@ -24,7 +26,7 @@ export default function StylePreview({ useEffect(() => { if (selectedStyle === "배경" && linkImg && isValidUrl(linkImg)) { setHasImgError(false); - setIsImageError(false); + setIsImgUrlConnectionError(false); // 이미지 로드 비동기 처리 const img = new window.Image(); @@ -36,22 +38,20 @@ export default function StylePreview({ img.onerror = () => { setHasImgError(true); // 이미지 로드 실패 - setIsImageError(true); + setIsImgUrlConnectionError(true); }; } else if (!isValidUrl(linkImg)) { // URL이 유효하지 않으면 에러 상태 설정하지 않음 setHasImgError(false); - setIsImageError(false); + setIsImgUrlConnectionError(false); } - }, [linkImg, selectedStyle, setIsImageError]); + }, [linkImg, selectedStyle, setIsImgUrlConnectionError, isValidUrl]); const imgErrorHandler = () => { setHasImgError(true); - setIsImageError(true); + setIsImgUrlConnectionError(true); }; - const isValidUrl = (url: string) => /^https?:\/\/.+\..+/.test(url); - const imgUrl = !hasImgError && isValidUrl(linkImg) ? linkImg : placeholderImage; diff --git a/src/app/admin/block/link/components/style-type.tsx b/src/app/admin/block/link/components/style-type.tsx index 39f4827e..d6e8783b 100644 --- a/src/app/admin/block/link/components/style-type.tsx +++ b/src/app/admin/block/link/components/style-type.tsx @@ -1,4 +1,5 @@ import Image from "next/image"; +import { Dispatch, SetStateAction } from "react"; import { twMerge } from "tailwind-merge"; export default function StyleType({ @@ -6,20 +7,32 @@ export default function StyleType({ imgIdx, selectedStyle, onSelect, + setLinkImg, + setIsImgUrlConnectionError, }: { name: string; imgIdx: number; selectedStyle: string; onSelect: (style: string) => void; + setLinkImg: Dispatch>; + setIsImgUrlConnectionError: Dispatch>; }) { const isSelected = selectedStyle === name; + function clickHandler() { + onSelect(name); + if (selectedStyle === "심플") { + setLinkImg(""); + setIsImgUrlConnectionError(false); + } + } + return (