Skip to content

Conversation

@kss2002
Copy link
Owner

@kss2002 kss2002 commented Nov 17, 2025

  1. 기초가 되는 api 코드 세팅.
  2. 뉴스 api 함수 세팅.
  3. 게시판 api 함수 세팅.
  4. 함수들을 컴포넌트에 이식시키기
  5. vite 프록시 설정
  6. feat/aianalyzer의 코드를 merge해서 최신화함.

@vercel
Copy link

vercel bot commented Nov 17, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
s-camp Ready Ready Preview Comment Nov 17, 2025 9:27am

@kss2002 kss2002 requested review from Copilot and tlsgud0 November 17, 2025 08:56
@kss2002 kss2002 self-assigned this Nov 17, 2025
@kss2002 kss2002 added the enhancement New feature or request label Nov 17, 2025
@kss2002
Copy link
Owner Author

kss2002 commented Nov 17, 2025

/gemini-review

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR establishes the foundational API integration layer for the application, connecting the frontend to the backend API for news and board functionality. It includes comprehensive API setup with error handling, loading states, and responsive design improvements.

Key Changes

  • Implemented complete API client infrastructure with axios interceptors and error handling
  • Integrated live news and board data fetching to replace static mock data
  • Added Vite proxy configuration for seamless API communication in development

Reviewed Changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
vite.config.js Configures proxy to route /api requests to the backend server
src/api/apiClient.js Core axios client with interceptors for request/response handling
src/api/newsApi.js API functions for fetching fraud-related news articles
src/api/postApi.js API functions for fetching posts by category and ID
src/api/index.js Centralized export point for all API modules
src/api/api-docs/docs.md Documentation tracking API specification versions
src/api/api-docs/api-docs.json OpenAPI specification from backend Swagger docs
src/pages/news/News.jsx Refactored to fetch and display news from API with loading/error states
src/pages/news/News.css Added styles for loading spinner, error states, and date display
src/pages/board/Board.jsx Integrated API calls for posts with category filtering
src/pages/board/Board.css Added loading/error state styles and empty state handling
src/components/board/Category.jsx Made categories interactive with click handlers and state management
src/components/board/Category.css Updated layout with border styling and mobile responsiveness
src/pages/not/NotFound.css Added responsive styles for mobile devices (max-width: 500px)
src/index.css Added global button border reset

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

<div className="news-container">
<div className="error-wrapper">
<h2 className="tossface">😢 뉴스를 불러오는데 실패했습니다</h2>
<p>{error}</p>
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The error message displays the raw error object directly to users ({error}), which may expose technical implementation details or unfriendly messages. This can confuse users or potentially leak sensitive information.

Consider providing a more user-friendly, consistent error message:

<h2 className="tossface">😢 뉴스를 불러오는데 실패했습니다</h2>
<p>일시적인 오류가 발생했습니다. 잠시  다시 시도해주세요.</p>
{import.meta.env.DEV && <p className="error-details">{error}</p>}

This way, technical details are only shown in development mode.

Suggested change
<p>{error}</p>
<p>일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요.</p>
{import.meta.env.DEV && <p className="error-details">{error}</p>}

Copilot uses AI. Check for mistakes.
key={category.id}
className={selectedCategory === category.id ? 'active' : ''}
onClick={() => onSelectCategory(category.id)}
style={{ cursor: 'pointer' }}
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

[nitpick] Using inline styles for the cursor property is inconsistent with the rest of the codebase, which uses CSS files for styling. Consider moving this to the CSS file:

In Category.css:

.category-sidebar li {
  cursor: pointer;
}

Then remove the inline style from the JSX.

Suggested change
style={{ cursor: 'pointer' }}

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +47
border-bottom: 1px solid var(--border-color);
width: 80vw;
}
}
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

Duplicate CSS rule: border-bottom: 1px solid var(--border-color); is already defined on line 3 and is repeated here. Consider removing the duplicate declaration.

@media (max-width: 500px) {
  .category-sidebar {
    margin: 0 auto;
    width: 80vw;
  }
}
Suggested change
border-bottom: 1px solid var(--border-color);
width: 80vw;
}
}
width: 80vw;
}
}

Copilot uses AI. Check for mistakes.
// 백엔드에서 최신화가 된 날짜와 swagger를 반영한 날짜를 전부 이 docs.md에 작성합니다.

```
백엔드에서 최신화가 된 날짜 :
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The documentation is missing the backend update date, which is mentioned as a required field. This makes it unclear when the backend API was last updated and whether the frontend is synchronized.

Please fill in the backend update date:

백엔드에서 최신화가 된 날짜 : [YYYY년 MM월 DD일]
프론트에서 반영한 날짜 : 2024년 11월 17일
Suggested change
백엔드에서 최신화가 된 날짜 :
백엔드에서 최신화가 된 날짜 : 2025년 11월 10일

Copilot uses AI. Check for mistakes.

```
백엔드에서 최신화가 된 날짜 :
프론트에서 반영한 날짜 : 2025년 11월 17일
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The date format in the documentation metadata appears to be incorrect. November cannot be the 11th month in 2025, as we're currently in November 2025 and it's only been 11 months. The date states "2025년 11월 17일" (November 17, 2025) but this seems to be in the future relative to the context.

If this should be November 2024, please correct it to:

프론트에서 반영한 날짜 : 2024년 11월 17일
Suggested change
프론트에서 반영한 날짜 : 2025년 11월 17일
프론트에서 반영한 날짜 : 2024년 11월 17일

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +74
.loading-wrapper,
.error-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400px;
text-align: center;
gap: 20px;
}

.loading-spinner {
animation: spin 1s linear infinite;
color: var(--primary-color);
}

@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The loading and error state styles (.loading-wrapper, .error-wrapper, .loading-spinner, @keyframes spin) are duplicated between Board.css (lines 49-73) and News.css (lines 182-205). This violates the DRY (Don't Repeat Yourself) principle.

Consider extracting these common styles to a shared CSS file or the global index.css:

/* In index.css or a new shared file */
.loading-wrapper,
.error-wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 400px;
  text-align: center;
  gap: 20px;
}

.loading-spinner {
  animation: spin 1s linear infinite;
  color: var(--primary-color);
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
Suggested change
.loading-wrapper,
.error-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400px;
text-align: center;
gap: 20px;
}
.loading-spinner {
animation: spin 1s linear infinite;
color: var(--primary-color);
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +7 to 14

const categories = [
{ id: 'all', label: '전체', value: null },
{ id: 'notice', label: '공지사항', value: 'NOTICE' },
{ id: 'prevention', label: '예방수칙', value: 'PREVENTION' },
{ id: 'case', label: '사례공유', value: 'CASE' },
];

Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The categories array is hardcoded in the component file. Consider extracting it to a separate constants file (e.g., src/constants/categories.js) for better maintainability and reusability.

// src/constants/categories.js
export const BOARD_CATEGORIES = [
  { id: 'all', label: '전체', value: null },
  { id: 'notice', label: '공지사항', value: 'NOTICE' },
  { id: 'prevention', label: '예방수칙', value: 'PREVENTION' },
  { id: 'case', label: '사례공유', value: 'CASE' },
];

Then import it:

import { BOARD_CATEGORIES as categories } from '../../constants/categories';
Suggested change
const categories = [
{ id: 'all', label: '전체', value: null },
{ id: 'notice', label: '공지사항', value: 'NOTICE' },
{ id: 'prevention', label: '예방수칙', value: 'PREVENTION' },
{ id: 'case', label: '사례공유', value: 'CASE' },
];
import { BOARD_CATEGORIES as categories } from '../../constants/categories';

Copilot uses AI. Check for mistakes.
Comment on lines +9 to 16
<li
key={category.id}
className={selectedCategory === category.id ? 'active' : ''}
onClick={() => onSelectCategory(category.id)}
style={{ cursor: 'pointer' }}
>
<span>{category.label}</span>
</li>
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The clickable list items lack proper accessibility attributes. Since these are interactive elements that don't use native buttons or links, they need proper ARIA attributes and keyboard navigation support.

Consider these improvements:

<li
  key={category.id}
  className={selectedCategory === category.id ? 'active' : ''}
  onClick={() => onSelectCategory(category.id)}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      onSelectCategory(category.id);
    }
  }}
  role="button"
  tabIndex={0}
  aria-pressed={selectedCategory === category.id}
  style={{ cursor: 'pointer' }}
>
  <span>{category.label}</span>
</li>

Copilot uses AI. Check for mistakes.
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

🤖 Gemini AI Code Review

Found 9 suggestions for improvement:

  • 💡 Medium: 9

This review was automatically generated by Gemini AI. Please review the suggestions carefully.

{
"openapi": "3.0.1",
"info": {
"title": "Contact API",

Choose a reason for hiding this comment

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

The API title is quotContact APIquot, but the endpoints are for news, posts, and analysis. Consider updating the title to something more descriptive that reflects the API#x27s functionality.

"requestBody": {
"content": {
"application/json": {
"schema": {

Choose a reason for hiding this comment

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

The request body for this endpoint is defined as a raw JSON string. This is unconventional and can be problematic for some HTTP clients and tools. It also makes it harder to add more parameters to the request in the future.

Consider using a JSON object instead. For example:
json
{
quotschemaquot: {
quottypequot: quotobjectquot,
quotpropertiesquot: {
quottextquot: {
quottypequot: quotstringquot
}
},
quotrequiredquot: [quottextquot]
}
}

"*/*": {
"schema": {
"type": "integer",
"format": "int32"

Choose a reason for hiding this comment

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

Throughout this OpenAPI document, response content types are set to /. For APIs that return JSON, it is best practice to explicitly use application/json. This helps tooling and clients correctly interpret the response.

This applies to all responses sections in this file.

},
"category": {
"type": "string"
},

Choose a reason for hiding this comment

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

The category field is a string here. However, the /api/posts/category/{category} endpoint defines a specific enum for it: [quotNOTICEquot, quotPREVENTIONquot, quotCASEquot].

For better consistency and validation, you should also define this enum in the PostDto schema.

json
quotcategoryquot: {
quottypequot: quotstringquot,
quotenumquot: [quotNOTICEquot, quotPREVENTIONquot, quotCASEquot]
}

.error-wrapper {
display: flex;
flex-direction: column;
align-items: center;

Choose a reason for hiding this comment

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

For better accessibility and scalability, consider using relative units like rem instead of px.

Also, if the intent is to only add vertical spacing, it#x27s more precise to use margin-block or specify horizontal margin as 0. Applying margin to all four sides might cause unintended horizontal layout issues.

For example:
css
margin: 3.125rem 0 /* Assuming 16px root font size */

const categories = [
{ id: 'all', label: '전체', value: null },
{ id: 'notice', label: '공지사항', value: 'NOTICE' },
{ id: 'prevention', label: '예방수칙', value: 'PREVENTION' },

Choose a reason for hiding this comment

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

To prevent a potential runtime error if posts is null or undefined while data is being fetched, it#x27s safer to use optional chaining. This ensures the application doesn#x27t crash when attempting to access .length on a non-array value.

suggestion
{!posts?.length ? (
ltdiv className=quotboard-empty-state tossfacequotgt
ltpgt아직 등록된 게시글이 없습니다. 🛠️lt/pgt
lt/divgt
) : (
posts.map(item =gt ltBoardItem key={item.id} item={item} /gt)
)}

다시 시도
</button>
</div>
</div>

Choose a reason for hiding this comment

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

For a better user experience in a Single Page Application (SPA), it#x27s preferable to re-fetch the data without a full page reload.

You can achieve this by extracting the fetchNews function from the useEffect hook, wrapping it in useCallback, and then calling it on the button#x27s onClick event.

Here#x27s an example of how you could refactor the component:
jsx
import { useState, useEffect, useCallback } from #x27react#x27
import { newsApi } from #x27../../api#x27
import #x27./News.css#x27
import { ArrowRight, Loader } from #x27lucide-react#x27

export default function News() {
const [news, setNews] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)

const fetchNews = useCallback(async () =gt {
try {
setLoading(true)
setError(null)
const data = await newsApi.getNews()
setNews(data)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}, [])

useEffect(() =gt {
fetchNews()
}, [fetchNews])

if (loading) {
// ... loading UI
}

if (error) {
return (
ltdiv className=quotnews-containerquotgt
ltdiv className=quoterror-wrapperquotgt
lth2 className=quottossfacequotgt😢 뉴스를 불러오는데 실패했습니다lt/h2gt
ltpgt{error}lt/pgt
ltbutton onClick={fetchNews} className=quotbtn btn-primaryquotgt
다시 시도
lt/buttongt
lt/divgt
lt/divgt
)
}

return (
// ... news list UI
)
}

This change will make your component more efficient and provide a smoother experience for the user when an error occurs.

<p>{error}</p>
<button onClick={() => window.location.reload()} className="btn btn-primary">
다시 시도
</button>

Choose a reason for hiding this comment

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

Using the array index as a key is an anti-pattern in React. It can lead to performance issues and bugs with component state when the list is reordered, added to, or filtered.

Please use a unique and stable identifier from the data, such as a news item ID. If item.link is guaranteed to be unique, it would be a much better choice.

jsx
lta href={item.link} key={item.link} ... gt

다시 시도
</button>
</div>
</div>

Choose a reason for hiding this comment

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

Using dangerouslySetInnerHTML can expose your application to Cross-Site Scripting (XSS) vulnerabilities if the HTML content from item.title and item.description is not properly sanitized. Malicious scripts embedded in the news content could be executed in the user#x27s browser.

Recommendation:
If you must render HTML from the API, sanitize it on the client-side before rendering. A library like dompurify is excellent for this.

Example:
jsx
import DOMPurify from #x27dompurify#x27

// ... inside your component

lth3 dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(item.title) }} /gt
ltp dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(item.description) }} /gt

This ensures that only safe HTML is rendered, mitigating the XSS risk.

copilot 커밋 제안에 따라 코드 업데이트

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
코파일럿 제안에 따라 주석 내용 변경

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Owner Author

Choose a reason for hiding this comment

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

로컬에서도 잘 동작하기 위해서 vite의 환경에 프록시를 설정하였습니다.

Copy link
Owner Author

Choose a reason for hiding this comment

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

swagger의 json 내역을 프론트에도 보기 쉽게 올려둡니다.

Updated the dates for backend and frontend updates.
@tlsgud0
Copy link
Collaborator

tlsgud0 commented Nov 17, 2025

확인했어, 이상 없음

@tlsgud0 tlsgud0 merged commit a13fe52 into develop Nov 17, 2025
2 checks passed
@kss2002 kss2002 deleted the feat/news branch November 17, 2025 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

브랜치 전략 - 프로젝트가 끝날 때까지 이 이슈는 지우지 마세요! -

3 participants