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
4 changes: 2 additions & 2 deletions src/api/comments/getComments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export interface CommentData {
creatorId: number;
creatorProfileImageUrl: string | null;
creatorNickname: string;
alias: string;
aliasName: string;
aliasColor: string;
postDate: string;
content: string;
Expand All @@ -21,7 +21,7 @@ export interface ReplyData {
creatorId: number;
creatorProfileImageUrl: string | null;
creatorNickname: string;
alias: string;
aliasName: string;
aliasColor: string;
postDate: string;
content: string;
Expand Down
2 changes: 1 addition & 1 deletion src/api/feeds/getFeedDetail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface FeedDetailData {
creatorId: number;
creatorNickname: string;
creatorProfileImageUrl: string;
alias: string;
aliasName: string;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

API 계약 변경(alias → aliasName)으로 인한 런타임 불일치 위험 — 응답 정규화 또는 점진적 전환 장치 필요

서버 응답이 아직 alias로 내려올 경우, 프론트에서는 aliasName만 참조하므로 값이 undefined가 되어 UI에 누락될 수 있습니다. 백엔드가 모두 반영되기 전까지는 프론트에서 하위 호환 정규화를 적용하는 것을 권장합니다.

예시(파일 내 getFeedDetail 함수에서 응답 정규화):

// 응답 정규화 예시: aliasName이 없고 alias가 있으면 aliasName으로 대입
export const getFeedDetail = async (feedId: number) => {
  const response = await apiClient.get<FeedDetailResponse>(`/feeds/${feedId}`);
  const raw = response.data;

  const normalizedData = {
    ...raw.data,
    aliasName: raw.data.aliasName ?? (raw.data as any).alias ?? '',
  };

  return { ...raw, data: normalizedData };
};

레포 내 남아있는 alias 사용처도 함께 점검해 주세요:

추가로, 본 PR 제목/설명(로딩 스피너)과 실제 변경 파일이 일치하지 않는 것으로 보입니다. 의도된 분리 PR인지 확인 부탁드립니다.


🏁 Script executed:

#!/bin/bash
# aliasName으로 모두 이전되었는지, alias 단독 사용이 남아있는지 점검
rg -n --hidden -S "\baliasName\b" -A 2
echo "-----"
# aliasName이 아닌 'alias' 단독 사용 탐지(변수/속성명 전체 단어 매칭)
rg -n --hidden -S "\balias\b" | rg -v "aliasName"

Length of output: 6848


getFeedDetail 하위 호환 응답 정규화 필요 (alias → aliasName)

백엔드가 아직 alias 키로 응답을 내려줄 경우, 현재 aliasName만 참조하도록 타입을 바꾼 상태에서는 UI에 값이 누락될 수 있습니다. 다음을 적용해 주세요.

• 적용 위치

  • src/api/feeds/getFeedDetail.ts (getFeedDetail 함수 내부)

• 정규화 예시

export const getFeedDetail = async (feedId: number) => {
  const response = await apiClient.get<FeedDetailResponse>(`/feeds/${feedId}`);
  const raw = response.data;

  const normalizedData = {
    ...raw.data,
    // aliasName이 없고 구 필드 alias가 있으면 aliasName으로 대체
    aliasName: raw.data.aliasName ?? (raw.data as any).alias ?? '',
  };

  return { ...raw, data: normalizedData };
};

• 부가 확인 요청

  1. 다른 사용자 관련 API 응답 타입에서 alias가 남아있는지 (getComments 등)
  2. 이 PR의 제목/설명(로딩 스피너)과 실제 변경 파일이 일치하는지
🤖 Prompt for AI Agents
In src/api/feeds/getFeedDetail.ts around line 9, the response normalization must
handle legacy `alias` field so UI doesn't miss names; modify getFeedDetail to
fetch the response, create a normalized data object spreading raw.data and set
aliasName to raw.data.aliasName ?? (raw.data as any).alias ?? '' and return the
response with data replaced by this normalizedData. Also scan other user-related
API responses (e.g., getComments) for remaining `alias` fields and confirm the
PR title/description matches the actual changed files.

aliasColor: string;
postDate: string;
isbn: string;
Expand Down
3 changes: 3 additions & 0 deletions src/assets/common/loadingspinner.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 57 additions & 0 deletions src/components/common/LoadingSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import styled from '@emotion/styled';
import LoadingSpinnerIcon from '@/assets/common/loadingspinner.svg';
import { colors, typography } from '@/styles/global/global';

interface LoadingSpinnerProps {
message?: string;
size?: 'small' | 'medium' | 'large';
fullHeight?: boolean;
}

const LoadingSpinner = ({
message = '로딩 중...',
size = 'medium',
fullHeight = false,
}: LoadingSpinnerProps) => {
return (
<Container fullHeight={fullHeight}>
<SpinnerImage size={size} src={LoadingSpinnerIcon} alt="로딩 중" />
{message && <LoadingText>{message}</LoadingText>}
</Container>
);
};

const Container = styled.div<{ fullHeight: boolean }>`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
${({ fullHeight }) =>
fullHeight &&
`
min-height: 100vh;
background-color: var(--color-black-main);
`}
`;

const SpinnerImage = styled.img<{ size: string }>`
${({ size }) => {
switch (size) {
case 'small':
return 'width: 24px; height: 24px;';
case 'large':
return 'width: 48px; height: 48px;';
default: // medium
return 'width: 32px; height: 32px;';
}
}}
`;

const LoadingText = styled.p`
margin-top: 20px;
color: ${colors.white};
font-size: ${typography.fontSize.lg};
font-weight: ${typography.fontWeight.semibold};
`;

export default LoadingSpinner;
8 changes: 4 additions & 4 deletions src/components/common/Post/PostHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled from '@emotion/styled';
interface PostHeaderProps {
creatorProfileImageUrl?: string;
creatorNickname?: string;
alias?: string;
aliasName?: string;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

프로퍼티 rename(alias → aliasName) 전파 여부 점검

컴포넌트/타입/데이터 계층 전체에서 alias 사용 흔적이 남아있으면 런타임에 값 누락이 발생할 수 있습니다. FeedPostProps, FeedDetailData 소비부, 서버 응답 정규화 상태를 함께 확인해 주세요.

레포 전역 점검 스크립트:

Also applies to: 37-37


🏁 Script executed:

#!/bin/bash
# PostHeader props에서 alias → aliasName 변경 누락 확인
rg -n --hidden -S "\baliasName\b" -A 2

echo "----- 남아있는 alias 단독 사용(의도치 않은 잔존) -----"
rg -n --hidden -S "\balias\b" | rg -v "aliasName"

Length of output: 6918


alias → aliasName 전파 누락 – 수정 필요

다음 파일들에서 여전히 alias 프로퍼티가 사용되고 있어 런타임에 aliasName이 누락될 수 있습니다. 모두 aliasName으로 통일해 주세요:

  • src/types/post.ts
    • 6번 줄의 alias?: string;
    • 46, 61번 줄의 alias: string;
  • src/api/comments/getComments.ts
    • 응답 타입 정의의 alias: string;
  • src/components/common/Post/Reply.tsx
  • src/components/common/Post/SubReply.tsx
    • 두 컴포넌트의 props 및 JSX 내 alias
  • src/mocks/searchBook.mock.ts
  • src/data/postData.ts
    • mock/fixture 데이터의 alias 필드

추가로, FeedPostProps·FeedDetailData 소비부 및 서버 응답 정규화 로직에서도 aliasName이 올바르게 매핑되는지 함께 검증해 주세요.

aliasColor?: string;
postDate: string;
creatorId?: number;
Expand All @@ -13,8 +13,8 @@ interface PostHeaderProps {
const PostHeader = ({
creatorProfileImageUrl,
creatorNickname,
alias,
aliasColor = '#FFFFFF', // 기본값 설정
aliasName,
aliasColor,
postDate,
creatorId,
type = 'post',
Expand All @@ -34,7 +34,7 @@ const PostHeader = ({
<div className="infoBox">
<div className="username">{creatorNickname}</div>
<div className="usertitle" style={{ color: aliasColor }}>
{alias}
{aliasName}
</div>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/Post/Reply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const Reply = ({
creatorId,
creatorProfileImageUrl,
creatorNickname,
alias,
aliasName,
aliasColor,
postDate,
content,
Expand Down Expand Up @@ -117,7 +117,7 @@ const Reply = ({
<PostHeader
creatorProfileImageUrl={creatorProfileImageUrl || undefined}
creatorNickname={creatorNickname}
alias={alias}
aliasName={aliasName}
aliasColor={aliasColor}
postDate={postDate}
creatorId={creatorId}
Expand Down
9 changes: 4 additions & 5 deletions src/components/common/Post/SubReply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const SubReply = ({
creatorId,
creatorProfileImageUrl,
creatorNickname,
alias,
aliasName,
aliasColor,
postDate,
content,
Expand Down Expand Up @@ -139,7 +139,7 @@ const SubReply = ({
<PostHeader
creatorProfileImageUrl={creatorProfileImageUrl || ''}
creatorNickname={creatorNickname}
alias={alias}
aliasName={aliasName || ''}
aliasColor={aliasColor}
postDate={postDate}
creatorId={creatorId}
Expand All @@ -148,8 +148,7 @@ const SubReply = ({
<ReplySection onClick={handleMoreClick}>
<div className="left">
<div className="reply">
<div className="reply-nickname">@{parentCommentCreatorNickname} </div>
{content}
<span className="reply-nickname">@{parentCommentCreatorNickname}</span> {content}
</div>
<div
className="sub-reply"
Expand Down Expand Up @@ -229,7 +228,7 @@ const ReplySection = styled.div`
line-height: 20px;

.reply-nickname {
color: ${colors.grey[100]};
color: ${colors.white};
font-size: ${typography.fontSize.sm};
font-weight: ${typography.fontWeight.regular};
line-height: 20px;
Expand Down
8 changes: 6 additions & 2 deletions src/components/feed/FeedDetailPost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ const Container = styled.div`

const BorderBottom = styled.div`
width: 100%;
height: 10px;
background-color: var(--color-darkgrey-dark);
min-width: 280px;
max-width: 500px;
margin: 0 auto;
padding: 0 20px;
height: 6px;
background: #1c1c1c;
Comment on lines +21 to +26
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

BorderBottom: width:100% + padding으로 인한 모바일 가로 오버플로우 가능성 — box-sizing 추가 권장

현재 width: 100%padding: 0 20px이 결합되어 있어(기본 content-box) 작은 화면(컨테이너 폭이 320px 등)에서 실제 렌더링 폭이 컨테이너를 초과할 수 있습니다. box-sizing: border-box로 패딩을 포함시키거나, width: 100% 대신 min/max만 사용하도록 조정하세요.

적용 예시(diff):

 const BorderBottom = styled.div`
   width: 100%;
   min-width: 280px;
   max-width: 500px;
   margin: 0 auto;
   padding: 0 20px;
+  box-sizing: border-box;
   height: 6px;
   background: #1c1c1c;
 `;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
min-width: 280px;
max-width: 500px;
margin: 0 auto;
padding: 0 20px;
height: 6px;
background: #1c1c1c;
const BorderBottom = styled.div`
width: 100%;
min-width: 280px;
max-width: 500px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box;
height: 6px;
background: #1c1c1c;
`;
🤖 Prompt for AI Agents
In src/components/feed/FeedDetailPost.tsx around lines 21 to 26, the rule
combining width (implicit 100% via margins) and padding (0 20px) with the
default content-box can cause horizontal overflow on small screens; update the
style to include box-sizing: border-box so padding is included in the element's
width (or alternatively remove any explicit 100% width and rely on
min-width/max-width constraints), ensuring the padding does not expand the
rendered box beyond the container.

`;

interface FeedDetailPostProps extends FeedDetailData {
Expand Down
8 changes: 6 additions & 2 deletions src/components/feed/FeedPost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ const Container = styled.div`

const BorderBottom = styled.div`
width: 100%;
height: 10px;
background-color: var(--color-darkgrey-dark);
min-width: 280px;
max-width: 500px;
margin: 0 auto;
padding: 0 20px;
height: 6px;
background: #1c1c1c;
Comment on lines +21 to +26
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

BorderBottom: width:100% + padding으로 인한 오버플로우 — box-sizing 추가 권장

FeedDetailPost와 동일한 구조로, 작은 화면에서 가로 스크롤/잘림이 발생할 수 있습니다.

적용 예시(diff):

 const BorderBottom = styled.div`
   width: 100%;
   min-width: 280px;
   max-width: 500px;
   margin: 0 auto;
   padding: 0 20px;
+  box-sizing: border-box;
   height: 6px;
   background: #1c1c1c;
 `;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
min-width: 280px;
max-width: 500px;
margin: 0 auto;
padding: 0 20px;
height: 6px;
background: #1c1c1c;
const BorderBottom = styled.div`
width: 100%;
min-width: 280px;
max-width: 500px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box;
height: 6px;
background: #1c1c1c;
`;
🤖 Prompt for AI Agents
In src/components/feed/FeedPost.tsx around lines 21 to 26, the bottom border
element uses width plus horizontal padding which causes overflow/horizontal
scroll on small screens; add box-sizing: border-box to that element (or its
container) so padding is included in the width calculation (alternatively remove
horizontal padding or change to width: calc(100% - 40px)), ensuring the element
no longer overflows and matches FeedDetailPost behavior.

`;

const FeedPost = ({ showHeader, isLast, isMyFeed, ...postData }: FeedPostProps) => {
Expand Down
5 changes: 4 additions & 1 deletion src/components/feed/MyFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { colors, typography } from '@/styles/global/global';
import TotalBar from './TotalBar';
import { getMyProfile } from '@/api/feeds/getMyProfile';
import type { MyProfileData } from '@/types/profile';
import LoadingSpinner from '@/components/common/LoadingSpinner';

const MyFeed = ({ showHeader, posts = [], isMyFeed, isLast = false }: FeedListProps) => {
const [profileData, setProfileData] = useState<MyProfileData | null>(null);
Expand All @@ -32,7 +33,9 @@ const MyFeed = ({ showHeader, posts = [], isMyFeed, isLast = false }: FeedListPr
}, []);

if (loading || !profileData) {
return <></>;
return (
<LoadingSpinner message="내 피드 정보를 불러오는 중..." size="large" fullHeight={true} />
);
}

return (
Expand Down
87 changes: 76 additions & 11 deletions src/pages/signup/SignupGenre.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,91 @@ const SignupGenre = () => {
const nickname = location.state?.nickname;

// 쿠키에서 Authorization 토큰 추출
const getAuthTokenFromCookie = () => {
const getAuthTokenFromCookie = async () => {
console.log('=== 쿠키 디버깅 ===');
console.log('현재 페이지 URL:', window.location.href);
console.log('현재 도메인:', window.location.hostname);
console.log('전체 쿠키:', document.cookie);

const cookies = document.cookie.split(';');
console.log('분리된 쿠키들:', cookies);
// 방법 1: document.cookie 사용
if (document.cookie) {
const cookies = document.cookie.split(';');
console.log('분리된 쿠키들:', cookies);

for (const cookie of cookies) {
const [name, value] = cookie.trim().split('=');
console.log('쿠키 이름:', name, '값:', value);
if (name === 'Authorization') {
console.log('Authorization 토큰 발견:', value);
return value;
}
}
}

// 방법 2: 직접 쿠키 이름으로 검색
const authCookie = document.cookie
.split(';')
.find(cookie => cookie.trim().startsWith('Authorization='));

if (authCookie) {
const token = authCookie.split('=')[1];
console.log('직접 검색으로 Authorization 토큰 발견:', token);
return token;
}

for (const cookie of cookies) {
const [name, value] = cookie.trim().split('=');
console.log('쿠키 이름:', name, '값:', value);
if (name === 'Authorization') {
console.log('Authorization 토큰 발견:', value);
return value;
// 방법 3: 정규식으로 검색
const cookieMatch = document.cookie.match(/Authorization=([^;]+)/);
if (cookieMatch && cookieMatch[1]) {
console.log('정규식으로 Authorization 토큰 발견:', cookieMatch[1]);
return cookieMatch[1];
}

// 방법 4: 모든 쿠키를 순회하며 검색
const allCookies = document.cookie.split(';');
for (let i = 0; i < allCookies.length; i++) {
const cookie = allCookies[i].trim();
if (cookie.startsWith('Authorization=')) {
const token = cookie.substring('Authorization='.length);
console.log('순회 검색으로 Authorization 토큰 발견:', token);
return token;
}
}

// 방법 5: 쿠키가 비어있는지 확인
if (!document.cookie || document.cookie.trim() === '') {
console.log('document.cookie가 비어있습니다.');
}

// 방법 6: 쿠키 길이 확인
console.log('쿠키 총 길이:', document.cookie.length);
console.log('쿠키 원본 문자열:', JSON.stringify(document.cookie));

// 방법 7: 서버에서 쿠키 정보 가져오기 (HttpOnly 쿠키일 경우)
try {
console.log('서버에서 쿠키 정보를 가져오는 중...');
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/users/me`, {
method: 'GET',
credentials: 'include', // 쿠키 포함
headers: {
'Content-Type': 'application/json',
},
});

if (response.ok) {
const data = await response.json();
console.log('서버 응답:', data);
// 서버에서 토큰을 헤더로 보내주거나, 사용자 정보를 통해 인증 상태 확인
if (data.data && data.data.userId) {
console.log('서버에서 사용자 정보 확인됨, 인증 상태 유효');
return 'VALID_AUTH'; // 특별한 값으로 인증 상태 표시
}
}
} catch (error) {
console.log('서버 쿠키 확인 실패:', error);
}

console.log('Authorization 토큰을 찾을 수 없습니다.');
console.log('가능한 원인: 도메인 불일치, 경로 불일치, 쿠키 만료');
console.log('가능한 원인: 도메인 불일치, 경로 불일치, 쿠키 만료, HttpOnly 설정');
return null;
};

Expand Down Expand Up @@ -74,7 +139,7 @@ const SignupGenre = () => {
if (!selectedAlias || !nickname) return;

// 쿠키에서 토큰 추출
const authToken = getAuthTokenFromCookie();
const authToken = await getAuthTokenFromCookie();
if (!authToken) {
console.log('쿠키에서 Authorization 토큰을 찾을 수 없습니다.');
console.log('토큰이 없어 회원가입을 진행할 수 없습니다.');
Expand Down