Skip to content

feat: 주소 검색 및 진단 폼 기능 구현#48

Merged
Dobbymin merged 24 commits intomainfrom
feat#41-address-search
Aug 22, 2025
Merged

feat: 주소 검색 및 진단 폼 기능 구현#48
Dobbymin merged 24 commits intomainfrom
feat#41-address-search

Conversation

@Dobbymin
Copy link
Copy Markdown
Contributor

📋 변경사항 요약

이번 PR에서는 주소 검색 기능과 진단 폼 기능을 구현하고, 관련 UI 컴포넌트들을 개선했습니다.

🚀 주요 기능

1. 주소 검색 기능 구현

  • 주소 관련 필드 컴포넌트 추가 (주소, 상세주소, 보증금, 임대 유형)
  • 주택 유형 선택 기능 구현
  • 진단 API 연동 및 요청 처리 로직 추가

2. 진단 폼 기능 구현

  • 진단 폼 컴포넌트 구현
  • API 요청을 위한 뮤테이션 추가
  • 폼 제출 로직 구현

3. UI/UX 개선

  • Select 컴포넌트 스타일을 Input과 일관성 있게 개선
  • 주택 유형 옵션의 value를 영어로 변경 (ONEROOM, TWOROOM, OFFICETEL, APARTMENT, DUPLEX)
  • HouseType 타입을 HOUSE_TYPE_OPTIONS의 value 기반으로 수정
  • AlternativeSection, LandlordPropertySection, LandlordReliabilitySection의 패딩 수정

📁 변경된 파일들

새로운 컴포넌트

    • 주소 입력 필드
    • 상세주소 입력 필드
    • 보증금 입력 필드
    • 임대 유형 선택 필드
    • 주택 유형 선택 필드
    • 진단 폼 컴포넌트

API 및 타입

    • 진단 API 구현
    • 주소 검색 스키마
    • 주택 유형 타입 정의

상수 및 설정

    • 주택 유형 상수 정의
    • 폼 타입 상수

UI 컴포넌트 개선

    • Select 컴포넌트 스타일 개선
  • 여러 Section 컴포넌트들의 패딩 수정

�� 테스트 체크리스트

  • 주소 검색 기능 정상 동작 확인
  • 진단 폼 제출 기능 확인
  • Select 컴포넌트 스타일 확인
  • 주택 유형 선택 기능 확인
  • 폼 유효성 검사 확인

🔧 기술적 개선사항

  • TypeScript 타입 안정성 향상
  • 컴포넌트 스타일 일관성 개선
  • 상수 관리 개선
  • API 요청 처리 로직 구현

📝 커밋 히스토리

  • e0e8011: style: AlternativeSection, LandlordPropertySection, LandlordReliabilitySection의 패딩 수정
  • db8ee24: feat: 진단 폼 기능 구현
  • 5029270: feat: 주택 유형 상수에서 '복층'을 '기타'로 변경 및 HOUSE_TYPE_OPTIONS의 값 수정
  • 763da62: feat: 주택 유형 상수에서 HOUSE_TYPES를 HOUSE_TYPE_OPTIONS로 변경 및 HouseType 타입 수정
  • 89304cc: feat: 진단 폼에서 API 요청을 위한 뮤테이션 추가 및 제출 로직 수정
  • f476d68: feat: 진단 API 구현 및 요청 처리 로직 추가
  • 0635573: feat: 주택 유형 필드에 Select 컴포넌트 적용 및 옵션 표시 기능 추가
  • 8980aca: feat: 주택 유형 상수에 선택 옵션 추가
  • 96ea367: feat: Select 컴포넌트에 선택 기능 추가 및 스타일 수정
  • d6c1f7c: feat: 주소 관련 필드 컴포넌트 추가

@Dobbymin Dobbymin linked an issue Aug 22, 2025 that may be closed by this pull request
@Dobbymin Dobbymin self-assigned this Aug 22, 2025
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @Dobbymin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이번 PR은 주소 검색 및 진단 폼 기능을 새롭게 구현하고, 관련 UI 컴포넌트들을 개선하는 것을 목표로 합니다. 사용자가 주소, 주택 유형, 상세 주소, 보증금 등의 정보를 입력하여 진단 API를 통해 주택 진단을 받을 수 있도록 하며, 기존 UI 요소들의 일관성과 사용자 경험을 향상시키는 데 중점을 두었습니다.

Highlights

  • 주소 검색 및 진단 폼 기능 구현: 주소, 상세 주소, 보증금, 임대 유형, 주택 유형을 입력받는 새로운 필드 컴포넌트들이 추가되었으며, 이를 활용한 진단 폼이 구현되었습니다. 진단 API 연동 및 폼 제출 로직이 포함됩니다.
  • UI/UX 개선: Select 컴포넌트의 스타일이 Input 컴포넌트와 일관성 있게 개선되었고, 주택 유형 옵션의 value가 영어로 변경되었습니다. 또한, 여러 섹션 컴포넌트들의 패딩이 조정되어 전반적인 레이아웃이 개선되었습니다.
  • 기술적 개선: react-hook-form과 zod를 활용하여 폼 유효성 검사 및 상태 관리가 강화되었으며, TypeScript 타입 안정성이 향상되었습니다. 새로운 API 연동 로직이 추가되었습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@Dobbymin Dobbymin merged commit c4d45d9 into main Aug 22, 2025
2 checks passed
@Dobbymin Dobbymin deleted the feat#41-address-search branch August 22, 2025 06:14
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

이번 PR은 주소 검색 및 진단 폼 기능을 구현하고 관련 UI를 개선하는 중요한 변경사항을 담고 있습니다. react-hook-formreact-query를 도입하여 상태 관리와 서버 통신 로직을 크게 개선한 점이 인상적입니다. 전반적으로 훌륭한 작업이지만, 타입 안정성, 유지보수성, 그리고 웹 표준 준수 측면에서 몇 가지 개선점을 제안합니다.

Comment on lines +3 to +22
export const RentTypeField = () => {
return (
<div className='flex flex-col gap-2'>
<Label className='text-sm font-medium'>임대 유형</Label>
<RadioGroup value='jeonse' className='flex items-center gap-4'>
<div className='flex items-center gap-2'>
<RadioGroupItem
value='jeonse'
id='jeonse'
checked
className='data-[state=checked]:border-[#8A8A8A] data-[state=checked]:before:bg-[#4353FF]'
/>
<Label htmlFor='jeonse' className='text-sm'>
전세
</Label>
</div>
</RadioGroup>
</div>
);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

RentTypeField 컴포넌트가 현재 정적인 상태로 구현되어 있습니다.

  1. RadioGroupvalue가 'jeonse'로 하드코딩되어 있고, RadioGroupItemchecked 속성이 있어 사용자가 다른 옵션을 선택할 수 없습니다. 현재는 '전세'만 지원하지만, 컴포넌트의 이름(RentTypeField)을 고려할 때 향후 확장을 위해 react-hook-form과 연동되어야 합니다. 만약 '전세'만 표시하는 것이 목적이라면, RadioGroup 대신 단순 텍스트로 표시하는 것이 더 명확할 수 있습니다.
  2. classNameborder-[#8A8A8A]before:bg-[#4353FF] 같은 색상 값이 하드코딩되어 있습니다. 테마 일관성을 위해 tailwind.config.js에 정의된 값을 사용해주세요.

Comment on lines +46 to +64
<form
onSubmit={(e) => e.preventDefault()}
className='mt-10 flex w-full flex-col items-center gap-2'
>
<div className='flex w-4/5 flex-col gap-2'>
<div className='flex w-full flex-col gap-5'>
<AddressField />
<HouseTypeField />
<DetailAddressField />
<RentTypeField />
<DepositField />
</div>
<div className='flex flex-col gap-2'>
<Label htmlFor={FORM_FIELDS.DETAIL_ADDRESS} className='text-sm font-medium'>
{LABEL_TEXTS.DETAIL_ADDRESS}
</Label>
<Input id={FORM_FIELDS.DETAIL_ADDRESS} placeholder={PLACEHOLDER_TEXTS.DETAIL_ADDRESS} />
<div className='py-10'>
<Button className='w-full' onClick={form.handleSubmit(onSubmit)}>
진단하기
</Button>
</div>
<div className='flex flex-col gap-2'>
<Label className='text-sm font-medium'>{LABEL_TEXTS.DEPOSIT}</Label>
<RadioGroup
value={rentType}
onValueChange={handleRentTypeChange}
className='flex items-center gap-4'
>
{Object.entries(RENT_TYPE_CONFIG).map(([value, config]) => (
<div key={value} className='flex items-center gap-2'>
<RadioGroupItem value={value} id={value} />
<Label htmlFor={value} className='text-sm'>
{config.label}
</Label>
</div>
))}
</RadioGroup>
</div>
<div className='flex flex-col gap-2'>
<div className='relative'>
<Input placeholder={currentRentConfig.placeholder} className='pr-12' />
<span className='absolute top-1/2 right-4 -translate-y-1/2 transform text-sm font-semibold text-gray-600'>
</span>
</div>
</div>
</div>
<div className='py-10'>
<Button className='w-full'>진단하기</Button>
</div>
</div>
</div>
</form>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

폼 제출 로직이 <Button>onClick 이벤트에 연결되어 있습니다. 웹 표준과 접근성을 준수하기 위해 <form>onSubmit 이벤트를 사용하고, 버튼의 typesubmit으로 설정하는 것이 좋습니다. 이렇게 하면 사용자가 엔터 키를 눌러 폼을 제출하는 동작도 자연스럽게 지원됩니다.

Suggested change
<form
onSubmit={(e) => e.preventDefault()}
className='mt-10 flex w-full flex-col items-center gap-2'
>
<div className='flex w-4/5 flex-col gap-2'>
<div className='flex w-full flex-col gap-5'>
<AddressField />
<HouseTypeField />
<DetailAddressField />
<RentTypeField />
<DepositField />
</div>
<div className='flex flex-col gap-2'>
<Label htmlFor={FORM_FIELDS.DETAIL_ADDRESS} className='text-sm font-medium'>
{LABEL_TEXTS.DETAIL_ADDRESS}
</Label>
<Input id={FORM_FIELDS.DETAIL_ADDRESS} placeholder={PLACEHOLDER_TEXTS.DETAIL_ADDRESS} />
<div className='py-10'>
<Button className='w-full' onClick={form.handleSubmit(onSubmit)}>
진단하기
</Button>
</div>
<div className='flex flex-col gap-2'>
<Label className='text-sm font-medium'>{LABEL_TEXTS.DEPOSIT}</Label>
<RadioGroup
value={rentType}
onValueChange={handleRentTypeChange}
className='flex items-center gap-4'
>
{Object.entries(RENT_TYPE_CONFIG).map(([value, config]) => (
<div key={value} className='flex items-center gap-2'>
<RadioGroupItem value={value} id={value} />
<Label htmlFor={value} className='text-sm'>
{config.label}
</Label>
</div>
))}
</RadioGroup>
</div>
<div className='flex flex-col gap-2'>
<div className='relative'>
<Input placeholder={currentRentConfig.placeholder} className='pr-12' />
<span className='absolute top-1/2 right-4 -translate-y-1/2 transform text-sm font-semibold text-gray-600'>
</span>
</div>
</div>
</div>
<div className='py-10'>
<Button className='w-full'>진단하기</Button>
</div>
</div>
</div>
</form>
<form
onSubmit={form.handleSubmit(onSubmit)}
className='mt-10 flex w-full flex-col items-center gap-2'
>
<div className='flex w-4/5 flex-col gap-2'>
<div className='flex w-full flex-col gap-5'>
<AddressField />
<HouseTypeField />
<DetailAddressField />
<RentTypeField />
<DepositField />
</div>
<div className='py-10'>
<Button type='submit' className='w-full'>
진단하기
</Button>
</div>
</div>
</form>

Comment on lines +1 to +10
import { z } from 'zod';

export const searchAddressSchema = z.object({
address: z.string().min(1, { message: '주소를 입력해주세요.' }),
houseType: z.string().min(1, { message: '주거형태를 선택해주세요.' }),
detailAddress: z.string().min(1, { message: '상세주소를 입력해주세요.' }),
deposit: z.number().min(1, { message: '보증금을 입력해주세요.' }),
});

export type SearchAddressType = z.infer<typeof searchAddressSchema>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

houseType 필드가 z.string()으로 정의되어 있어 타입 안정성이 떨어집니다. HOUSE_TYPE_OPTIONS에 정의된 값들만 허용하도록 z.enum()을 사용하면 타입 캐스팅(as HouseType) 없이 안전하게 타입을 사용할 수 있습니다.

Suggested change
import { z } from 'zod';
export const searchAddressSchema = z.object({
address: z.string().min(1, { message: '주소를 입력해주세요.' }),
houseType: z.string().min(1, { message: '주거형태를 선택해주세요.' }),
detailAddress: z.string().min(1, { message: '상세주소를 입력해주세요.' }),
deposit: z.number().min(1, { message: '보증금을 입력해주세요.' }),
});
export type SearchAddressType = z.infer<typeof searchAddressSchema>;
import { z } from 'zod';
import { HOUSE_TYPE_OPTIONS } from '../../constants';
const houseTypeValues = HOUSE_TYPE_OPTIONS.map((option) => option.value);
export const searchAddressSchema = z.object({
address: z.string().min(1, { message: '주소를 입력해주세요.' }),
houseType: z.enum(houseTypeValues, {
errorMap: () => ({ message: '주거형태를 선택해주세요.' }),
}),
detailAddress: z.string().min(1, { message: '상세주소를 입력해주세요.' }),
deposit: z.number().min(1, { message: '보증금을 입력해주세요.' }),
});
export type SearchAddressType = z.infer<typeof searchAddressSchema>;

Comment on lines +7 to +28
interface DiagnosisApiResponse {
address: string;
addressDetail: string;
houseType: HouseType;
deposit: number;
}

export const diagnosisApi = async ({
address,
addressDetail,
houseType,
deposit,
}: DiagnosisApiResponse) => {
const response = await fetchInstance.post<DiagnosisApiResponse>(DIAGNOSIS_API_PATH, {
address,
addressDetail,
houseType,
deposit,
});

return response.data;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

DiagnosisApiResponse라는 인터페이스 이름이 요청 페이로드와 API 응답에 모두 사용될 수 있어 혼란을 줄 수 있습니다. 요청 데이터를 나타내는 타입은 DiagnosisApiPayload 또는 DiagnosisApiRequest와 같이 더 명확한 이름으로 변경하는 것이 좋습니다. 또한, diagnosisApi 함수는 인자로 받은 객체를 그대로 post 메소드에 전달하도록 단순화할 수 있습니다.

interface DiagnosisApiPayload {
  address: string;
  addressDetail: string;
  houseType: HouseType;
  deposit: number;
}

export const diagnosisApi = async (payload: DiagnosisApiPayload) => {
  const response = await fetchInstance.post<DiagnosisApiPayload>(DIAGNOSIS_API_PATH, payload);

  return response.data;
};

Comment on lines +34 to +36
<span className='absolute top-1/2 right-4 -translate-y-1/2 transform text-sm font-semibold text-[#333D4B]'>
</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

text-[#333D4B]와 같이 색상 값이 하드코딩되어 있습니다. 유지보수성과 일관성을 위해 tailwind.config.js에 정의된 테마 색상(예: text-gray-800)을 사용하는 것이 좋습니다.

Suggested change
<span className='absolute top-1/2 right-4 -translate-y-1/2 transform text-sm font-semibold text-[#333D4B]'>
</span>
<span className='absolute top-1/2 right-4 -translate-y-1/2 transform text-sm font-semibold text-gray-800'>
</span>

<FormLabel>{LABEL_TEXTS.HOUSE_TYPE}</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger className='w-full rounded-full border-none shadow-[0px_4px_30px_0px_#0000001A] focus-visible:border-lighthouse-blue focus-visible:shadow-lighthouse-blue-shadow [&_span]:text-input-placeholder-gray'>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

SelectTrigger에 적용된 shadow-[0px_4px_30px_0px_#0000001A] 클래스는 src/shared/components/ui/select.tsxSelectTrigger 컴포넌트에 이미 기본으로 적용되어 있습니다. 중복된 클래스이므로 제거하는 것이 좋습니다.

Suggested change
<SelectTrigger className='w-full rounded-full border-none shadow-[0px_4px_30px_0px_#0000001A] focus-visible:border-lighthouse-blue focus-visible:shadow-lighthouse-blue-shadow [&_span]:text-input-placeholder-gray'>
<SelectTrigger className='w-full rounded-full border-none focus-visible:border-lighthouse-blue focus-visible:shadow-lighthouse-blue-shadow [&_span]:text-input-placeholder-gray'>

data-slot='select-trigger'
data-size={size}
className={cn(
'flex h-9 w-full min-w-0 rounded-full bg-transparent py-1 pr-3 pl-5 text-base shadow-[0px_4px_30px_0px_#0000001A] transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-input-placeholder-gray disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

shadow-[0px_4px_30px_0px_#0000001A]와 같이 그림자 값이 하드코딩되어 있습니다. tailwind.config.jstheme.extend.boxShadow에 추가하여 재사용성과 유지보수성을 높이는 것을 권장합니다. 예를 들어, input-shadow와 같은 이름으로 정의할 수 있습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[기능 구현] 주소 검색 기능 구현

1 participant