Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions public/images/visibility-off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/images/visibility-on.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions src/app/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use client'

import Input from '@components/Input'
import { useForm } from 'react-hook-form'

interface FormData {
email: string
password: string
}

export default function MyForm() {
const {
register,
handleSubmit,
formState: { errors, touchedFields },
} = useForm<FormData>({
mode: 'onChange',
})

const onSubmit = (data: FormData) => {
console.log(data)
}
Comment on lines +20 to +22
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

민감 데이터 console.log 출력 자제

비밀번호가 그대로 브라우저 콘솔에 노출됩니다. 배포 전 반드시 제거하거나 서버 전송 로직으로 교체하세요.

🤖 Prompt for AI Agents
In src/app/auth/page.tsx around lines 20 to 22, the onSubmit function currently
logs the entire form data including sensitive information like passwords to the
browser console. Remove the console.log statement to prevent exposing sensitive
data, and replace it with logic that securely sends the form data to the server
for processing.


return (
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-4">
{/* 이메일 */}
<Input
labelName="이메일"
type="email"
placeholder="이메일 입력"
autoComplete="email"
{...register('email', {
required: '이메일을 입력해 주세요.',
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: '유효한 이메일 주소를 입력해주세요',
},
})}
hasError={touchedFields.email && !!errors.email}
errorMessage={touchedFields.email ? errors.email?.message : ''}
/>
Comment on lines +32 to +41
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

에러 표시 조건이 제출 후에는 작동하지 않을 수 있음

touchedFields만 의존하면, 사용자가 필드를 건드리지 않고 제출했을 때 오류 메시지가 안 보일 수 있습니다.
isSubmitted 또는 errors만을 활용하여 제출 후에도 오류가 표시되도록 조건을 보완하는 것을 권장합니다.

Also applies to: 47-56

🤖 Prompt for AI Agents
In src/app/auth/page.tsx around lines 32 to 41 and similarly in lines 47 to 56,
the error display condition relies solely on touchedFields, which may prevent
error messages from showing after form submission if the user hasn't interacted
with the field. To fix this, update the error display condition to also consider
isSubmitted or directly check errors, ensuring that error messages appear after
submission regardless of field interaction.


{/* 비밀번호 */}
<Input
labelName="비밀번호"
type="password"
{...register('password', {
required: '비밀번호를 입력해 주세요.',
minLength: {
value: 8,
message: '비밀번호는 최소 8자 이상이어야 합니다.',
},
})}
hasError={touchedFields.password && !!errors.password}
errorMessage={touchedFields.password ? errors.password?.message : ''}
/>

<button
type="submit"
className="mt-4 rounded bg-blue-500 py-2 text-white"
>
제출하기
</button>
</form>
)
}
6 changes: 6 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ body {
.Text-white {
@apply text-[#FFFFFF] dark:text-[#333236];
}
.Text-error {
@apply text-[#D6173A];
}
.Border-error {
@apply border border-[#D6173A];
}
.Border-btn {
@apply border border-[#D9D9D9] dark:border-[#747474];
}
Expand Down
95 changes: 95 additions & 0 deletions src/app/shared/components/Input.tsx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const Input = forwardRef<HTMLInputElement, CustomInputProps>(function Input(props, ref) {...}) 이 코드 구조가,
forwardRef에 컴포넌트를 통째로 넘기고, 호출되는 건 const Input인 건가요?

Copy link
Author

@Insung-Jo Insung-Jo Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞습니다! 공식 문서 내용에서도 확인해보시면

const SomeComponent = forwardRef(render)

이처럼 forwardRef 안에 render 함수를 인자로 받아서 ref를 지원하는새로운 컴포넌트로 반환합니다!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

처음 알았네용!!

Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use client'

import Image from 'next/image'
import { forwardRef, InputHTMLAttributes, useState } from 'react'

import { cn } from '../lib/cn'

interface CustomInputProps extends InputHTMLAttributes<HTMLInputElement> {
labelName: string
name: string
type?: React.HTMLInputTypeAttribute
autoComplete?: string
placeholder?: string
hasError?: boolean
errorMessage?: string
}

const Input = forwardRef<HTMLInputElement, CustomInputProps>(
function Input(props, ref) {
const {
labelName,
name,
type = 'text',
placeholder,
hasError,
errorMessage,
autoComplete,
...rest
} = props

const [showPassword, setShowPassword] = useState(false)
const isPassword = type === 'password'
const inputType = isPassword && showPassword ? 'text' : type

return (
<div className="flex flex-col gap-8">
<label htmlFor={name} className="Text-black text-base font-normal">
{labelName}
</label>

{isPassword ? (
<div className="relative w-full">
<input
id={name}
className={cn(
'Text-black h-50 w-full rounded-8 px-16 py-12 text-base font-normal',
hasError ? 'Border-error' : 'Border-btn',
)}
type={inputType}
placeholder={placeholder}
name={name}
autoComplete={autoComplete}
{...rest}
ref={ref}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-16 top-1/2 translate-y-[-50%]"
>
<Image
src={`/images/visibility-${showPassword ? 'on' : 'off'}.svg`}
alt={showPassword ? '비밀번호 보기' : '비밀번호 숨기기'}
width={24}
height={24}
/>
</button>
</div>
) : (
<input
id={name}
className={cn(
'Text-black h-50 w-full rounded-8 px-16 py-12 text-base font-normal',
hasError ? 'Border-error' : 'Border-btn',
)}
Comment on lines +41 to +75
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

비밀번호 존재 유무에 따라 조건부 렌더링으로 보여주는 로직이군요!!
이 부분은 조금 코드가 길어서 가독성이 떨어져 보이는데 따로 분리해서 컴포넌트로 불러오는 방식은 어떨까용?

type={inputType}
placeholder={placeholder}
name={name}
autoComplete={autoComplete}
{...rest}
ref={ref}
/>
)}

{hasError && errorMessage && (
<p className="Text-error text-sm font-normal">{errorMessage}</p>
)}
</div>
)
},
)

Input.displayName = 'Input'

export default Input
Loading