📌 오늘의 작업 핵심 요약
- Backend 연결
- 지역 선택 기능 구현 (사용자 지역 선택 값에 따라 지지율에서 해당 데이터를 필터링됨 - 아마도?)
- 후보자 정보 파일을 candidate.js 추가해 코드 가독성 향상
- 후보 카드 레이아웃을 Figma 기준에 맞게 조정 + 반응형도 조정
- 텍스트 스타일 및 줄 바꿈 개선 (모바일에서 폰트 크기 및 간격 축소, 데스크탑에서 넉넉하게 확장)
📁 주요 기능 설명
🧾VoteApi.js
유저가 특정 지역과 후보를 선택한 뒤 투표를 전송하거나 취소할 수 있는 Api 요청 로직을 구현했다. axios를 사용해 비동기 요청을 처리하고, 에러 상황에 대비한 예외 처리도 함께 작성했다.
🔸 Api URL 설정
const API_URL = import.meta.env.VITE_API_URL;
- .env에 API 주소를 저장한 뒤 Vite의 환경변수를 통해 접근했다. ''
🔸 투표 요청
// 투표 요청
export const sendVote = async ({ region, candidate }) => {
try {
const response = await axios.post(`${API_URL}/api/vote`, {
region,
candidate,
});
return response.data;
} catch (error) {
console.error('투표 전송 실패:', error);
throw new Error('투표 전송 실패');
}
};
- 유저가 선택한 지역과 후보를 서버에 POST 요청
- 정상 응답시 .data를 반환하고 실패시 throw로 에러 전달
🔸 투표 취소
// 투표 취소 요청
export const cancelVote = async ({ region, candidate }) => {
try {
const response = await axios.post(`${API_URL}/api/vote/cancel`, {
region,
candidate,
});
return response.data;
} catch (error) {
console.error(' 투표 취소 실패:', error);
throw new Error('투표 취소 실패');
}
};
- 투표했던 내용을 초기화하고 다시 선택 가능하게 했다.
🤔 고민한 점
원래는 fetch를 사용해 구현했지만, 팀원이 axios로 작성한 코드를 보고 통일성을 위해 나도 axios로 변경했다. 실제로 사용해보니 에러 처리와 응답 데이터 접근이 fetch보다 훨씬 직관적이어서, 변경하길 잘했다고 느꼈다.
🧾 RegionSelect.jsx - 사용자 지역 선택 드롭다운 컴포넌트
사용자가 본인의 지역을 선택하면 해당 지역에 맞는 데이터를 지지율에서 필터링이 되기때문에 필요한 데이터임으로 구현을 하게 되었다. 지지율은 여론조사 데이터를 기반으로 하는데 거기에 지역이 있기 때문이다.
import React from 'react';
// 백엔드 지역 그대로 가져왔습니다.
const REGIONS = {
1: '서울',
2: '인천/경기',
3: '대전/세종/충청',
4: '광주/전라',
5: '대구/경북',
6: '부산/울산/경남',
7: '강원/제주',
};
const RegionSelect = ({ onRegionSelect }) => {
return (
<div className="flex w-full items-center justify-between">
<label htmlFor="region" className="text-lg whitespace-nowrap text-gray-700">
어느 지역에 계신가요?
</label>
<select
id="region"
className="w-34 rounded-md border p-2 text-sm sm:w-34"
defaultValue=""
onChange={(e) => {
const regionId = Number(e.target.value);
onRegionSelect(regionId);
}}
>
<option value="" disabled>
지역 선택
</option>
{Object.entries(REGIONS).map(([id, name]) => (
<option key={id} value={id}>
{name}
</option>
))}
</select>
</div>
);
};
export default RegionSelect;
🔸 지역 ID와 지역 이름 매핑한 객체 (백엔드에서 만들은거 그대로 가져옴)
// 백엔드 지역 그대로 가져왔습니다.
const REGIONS = {
1: '서울',
2: '인천/경기',
3: '대전/세종/충청',
4: '광주/전라',
5: '대구/경북',
6: '부산/울산/경남',
7: '강원/제주',
};
- 1~7 숫자는 백엔드와 통신할 때 쓰는 값 (id), 값은 화면에 보일 지역 이름
- 이 데이터를 기반으로 <select> 옵션이 만들어진다.
🔸 RegionSelect라는 함수형 컴포넌트를 선언
const RegionSelect = ({ onRegionSelect }) => {
- onRegionSelect는 props로 부모 컴포넌트에서 전달되는 콜백 함수야.
- 사용자가 지역을 선택했을 때 이 함수를 호출해서 선택된 ID를 전달
<label htmlFor="region" className="text-lg whitespace-nowrap text-gray-700">
어느 지역에 계신가요?
</label>
- htmlFor="region": 아래 select의 id="region"과 연결되어 클릭 시 해당 요소로 포커싱된다.
<select
id="region"
className="w-34 rounded-md border p-2 text-sm sm:w-34"
defaultValue=""
onChange={(e) => {
const regionId = Number(e.target.value);
onRegionSelect(regionId);
}}
>
- select: 드롭다운 메뉴
- id="region": 위 label과 연결
- defaultValue="": 처음엔 아무 것도 선택되지 않도록 설정
- e.target.value: 선택된 <option>의 값, onRegionSelect(regionId): 부모 컴포넌트에 선택된 값을 전달
<option value="" disabled>
지역 선택
</option>
- 초기 옵션 (placeholder 역할)
- disabled: 선택 불가로 설정해서 사용자에게 안내용으로만 보여줌
{Object.entries(REGIONS).map(([id, name]) => (
<option key={id} value={id}>
{name}
</option>
))}
- REGIONS 객체를 [id, name] 형태의 배열로 바꾸고 반복 렌더링함
- map()을 통해 각 지역에 대한 <option> 생성
- key={id}: React에 고유값을 전달해야 렌더링 최적화됨
- value={id}: 선택 시 이 id가 전송됨
- {name}: 사용자가 보는 드롭다운 항목 텍스트
❌ 겪었던 주요 문제 & 해결 방법
처음엔 지역 이름을 직접 <option>으로 입력하려 했으나, 지역 리스트가 백엔드와 계속 맞아야 해서 상수 객체로 관리하도록 변경했다.
지역 ID는 숫자이므로, e.target.value를 Number()로 변환해야 백엔드와 맞춰서 데이터 처리 가능
🖌️주요 작업 내용
📃 Vote 페이지 UI 및 반응형 개선
코드를 팀원과 병합하고 나서 디자인이 피그마 시안과 다른 것을 확인해 Vote 페이지의 UI를 개선하고, 모바일/웹 환경에 모두 대응이 잘되도록 수현했다.
1. Figma 시안 기반 반응형 레이아웃 수정
- md를 기준으로 pc에서 반응형이 되도록 구현했다.
- 텍스트 크기와 간격을 Tailwind의 반응형 유틸리티로 조정
- 이미지 크기 및 투표 상태 아이콘도 md:h-14, md:h-12 등으로 구현했다.
2. VoteHeader 컴폰너트 수정
import React from 'react';
const VoteHeader = () => {
return (
<div className="mb-6 w-full px-4 text-center overflow-hidden">
<h1 className="mb-2 text-2xl font-extrabold leading-snug md:text-5xl break-keep whitespace-nowrap">
나는 누구의 공약에 공감할까?
</h1>
<p className="text-sm leading-relaxed md:text-2xl">
후보들의 주요 공약을 비교해보고,
<br className="hidden md:inline" />
지금 내가 공감하는 정책과 가까운 후보를 선택해보세요.
</p>
</div>
);
};
export default VoteHeader;
- break-keep을 넣어서 한국어 단어 단위로 줄바꿈되도록 강제로 했다.
- 줄바꿈을 막기 위해 whitespace-nowrap도 같이 넣어줬다.
- <br className="md:hidden" />을 통해 모바일에서만 줄바꿈 처리
속성 | 설명 |
break-keep | 한국어 단어 단위 줄바꿈 유지 |
whitespace-nowrap | 아예 줄바꿈 안 함 (넘칠 수도 있음) |
overflow-hidden | 넘치는 텍스트 숨김 |
3. Disclaimer(안내문구) 개선
import React from 'react';
const Disclaimer = () => {
return (
<div className="mt-6 w-full px-4 text-center text-gray-400">
<p className="text-sm leading-relaxed">
※ 본 페이지는 실제 선거와 무관한 모의 체험 콘텐츠입니다.
</p>
<p className="text-sm leading-relaxed md:whitespace-nowrap">
모든 데이터는 사용자 참여 기반의 체험용으로 수집되며,
<br className="md:hidden" />
공식 지지율이나 선거 결과와는 관련이 없습니다.
</p>
</div>
);
};
export default Disclaimer;
- 모바일에서는 두 줄로, 웹에서는 한 줄로 보이도록 md:whitespace-nowrap와 <br className="md:hidden" /> 조합 적용
- PC: 한 줄 유지
목적 | 적용 방식 |
한 줄 고정 (강제) | whitespace-nowrap |
자연스러운 줄바꿈 허용 | 기본값 유지 (normal) |
모바일에서만 줄바꿈 유도 | <br className="md:hidden" /> |
✨ Tailwind 반응형 조절 팁!
속성 | 모바일 | 데스크탑 |
text-* | text-sm, text-base | md:text-xl, md:text-2xl |
px, py | px-4, py-2 | md:px-6, md:py-4 |
gap | gap-2, gap-3 | md:gap-4, md:gap-6 |
border | border-2 | md:border-4 |
width, height | h-10 w-10 | md:h-14 md:w-14 |
❌ 겪었던 주요 문제 & 해결 방법
문제 | 원인 | 해결 방법 |
까?가 한 줄에서 떨어짐 | Tailwind의 text-5xl처럼 글자가 크면 부모 요소의 너비 (max-w-xl)에 다 안 들어가서 자동 줄바꿈됨 | break-keep, whitespace-nowrap, max-w 조정 |
모바일에서 카드 UI 깨짐 | 가로 정렬(flex-row) 고정 | flex-col로 전환하고 md:flex-row로 반응형 적용 |
Prettier eslint 오류 (Insert ·) | - | Tailwind 플러그인 설치 |
🎠회고
오늘 오전 내내 백엔드와 통신을 위해 .env 파일에 로컬호스트 주소를 설정하고 테스트를 반복했다.
처음에는 배포용 주소인 cloudtype.app을 .env에 넣었는데 통신이 제대로 되지 않아서 계속 삽질을 했다. 지금 생각해보면, 애초에 .env 파일을 먼저 확인했더라면 금방 해결할 수 있었을 텐데... 유독 .env만 안 보고 다른 파일만 열심히 뒤졌던 게 아쉽다.
오류 메시지도 딱히 뜨지 않았고, 내가 직접 테스트할 땐 200 응답이 와서 문제없는 줄 알았는데... 다행히 팀원분이 함께 확인해주셔서 원인을 파악하고 해결할 수 있었다. 감사한 마음이 크다.
수업 막바지에는 팀원과 함께 메인 브랜치로 병합 작업도 진행했다. 내 화면을 공유하면서 팀원분이 직접 봐주시고, 도와주셔서 순조롭게 마무리할 수 있었다.
충돌 해결은 VSCode에서 하려니까 생각보다 어려웠고 시간이 좀 걸렸는데, 나한테는 GitHub 웹페이지에서 처리하는 게 더 편한 것 같다.
'💡 URECA > 📽️ 프로젝트' 카테고리의 다른 글
[URECA] 픽미업: 복잡한 정보를 쉽게 전달하는 정치 참여 플랫폼 #9 (2) | 2025.05.19 |
---|---|
[URECA] 픽미업: 복잡한 정보를 쉽게 전달하는 정치 참여 플랫폼 #6 (0) | 2025.05.14 |
[URECA] 픽미업: 복잡한 정보를 쉽게 전달하는 정치 참여 플랫폼 #4 (0) | 2025.05.13 |
[URECA] 픽미업: 복잡한 정보를 쉽게 전달하는 정치 참여 플랫폼(기획_최최종일까요?) #3 (2) | 2025.05.09 |
[URECA] 픽미업: 복잡한 정보를 쉽게 전달하는 정치 참여 플랫폼 #2 (4) | 2025.05.08 |