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: 2 additions & 1 deletion src/api/auth/getToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export interface GetTokenResponse {
code: number;
message: string;
data: {
token: string; // 토큰
token: string; // 토큰 (임시 또는 액세스)
isNewUser: boolean; // true면 회원가입 필요 (임시 토큰), false면 액세스 토큰
};
}

Expand Down
26 changes: 19 additions & 7 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,35 @@ export const apiClient = axios.create({
'Content-Type': 'application/json',
},
});
// Request 인터셉터: localStorage의 토큰을 헤더에 자동 추가
// Request 인터셉터: 토큰 부재 시 비공개 API 요청을 선제 차단(리다이렉트 + 요청 취소)
apiClient.interceptors.request.use(
config => {
// localStorage에서 토큰 확인
const authToken = localStorage.getItem('authToken');
const preAuthToken = localStorage.getItem('preAuthToken');
// 공개 API(완전 공개)
const publicPaths = ['/auth/token'];
// 회원가입 진행 중 필요한 경로(임시 토큰 허용)
const signupPaths = ['/users/nickname', '/users/signup'];
const isPublic = publicPaths.some(path => config.url?.startsWith(path));
const isSignupPath = signupPaths.some(path => config.url?.startsWith(path));

if (!authToken && !isPublic && !(preAuthToken && isSignupPath)) {
console.log('❌ 토큰 없음: 요청을 취소하고 홈으로 이동합니다.');
window.location.href = '/';
// 요청 자체를 취소하여 불필요한 네트워크 왕복 방지
return Promise.reject(new Error('Request cancelled: missing auth token'));
}

if (authToken) {
config.headers.Authorization = `Bearer ${authToken}`;
} else {
console.log('❌ localStorage에 토큰이 없습니다.');
} else if (preAuthToken && isSignupPath) {
// 회원가입 경로에서는 임시 토큰을 사용
config.headers.Authorization = `Bearer ${preAuthToken}`;
}

return config;
},
error => {
return Promise.reject(error);
},
error => Promise.reject(error),
);

// Response 인터셉터: 401 에러 시 로그인 페이지로 리다이렉트
Expand Down
35 changes: 35 additions & 0 deletions src/api/notifications/getNotifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { apiClient } from '../index';

export interface NotificationItem {
notificationId: number;
title: string;
content: string;
isChecked: boolean;
notificationType: string;
postDate: string;
}

export interface GetNotificationsResponse {
isSuccess: boolean;
code: number;
message: string;
data: {
notifications: NotificationItem[];
nextCursor: string;
isLast: boolean;
};
}

export interface GetNotificationsParams {
cursor?: string | null;
type?: 'feed' | 'room';
}

export const getNotifications = async (
params?: GetNotificationsParams,
): Promise<GetNotificationsResponse> => {
const response = await apiClient.get<GetNotificationsResponse>('/notifications', {
params,
});
return response.data;
};
13 changes: 13 additions & 0 deletions src/api/users/deleteUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { apiClient } from '../index';

export interface DeleteUsersResponse {
isSuccess: boolean;
code: number;
message: string;
data: null;
}

export const deleteUsers = async (): Promise<DeleteUsersResponse> => {
const response = await apiClient.delete<DeleteUsersResponse>('/users');
return response.data;
};
21 changes: 20 additions & 1 deletion src/components/group/CompletedGroupModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import { Modal, Overlay } from './Modal.styles';
import { getMyRooms, type Room } from '@/api/rooms/getMyRooms';
import { getMyProfile } from '@/api/users/getMyProfile';
import { colors, typography } from '@/styles/global/global';
import { useNavigate } from 'react-router-dom';

interface CompletedGroupModalProps {
onClose: () => void;
}

const CompletedGroupModal = ({ onClose }: CompletedGroupModalProps) => {
const navigate = useNavigate();
const [rooms, setRooms] = useState<Room[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
Expand All @@ -29,9 +31,14 @@ const CompletedGroupModal = ({ onClose }: CompletedGroupModalProps) => {
coverUrl: room.bookImageUrl,
deadLine: '',
isOnGoing: false,
type: room.type,
};
};

const handleGroupCardClick = (group: Group) => {
navigate(`/group/detail/joined/${group.id}`);
};

useEffect(() => {
const fetchCompletedRooms = async () => {
try {
Expand Down Expand Up @@ -84,7 +91,14 @@ const CompletedGroupModal = ({ onClose }: CompletedGroupModalProps) => {
) : error ? (
<ErrorMessage>{error}</ErrorMessage>
) : convertedGroups.length > 0 ? (
convertedGroups.map(group => <GroupCard key={group.id} group={group} type={'modal'} />)
convertedGroups.map(group => (
<GroupCard
key={group.id}
group={group}
type={'modal'}
onClick={() => handleGroupCardClick(group)}
/>
))
) : (
<EmptyState data-empty="true">
<EmptyTitle>완료된 모임방이 없어요</EmptyTitle>
Expand Down Expand Up @@ -116,6 +130,11 @@ const Content = styled.div<{ isEmpty?: boolean }>`
@media (min-width: 584px) {
grid-template-columns: 1fr 1fr;
}

//항목이 하나일 때는 전체 열을 사용하여 2열 그리드처럼 보이지 않도록 처리
& > *:only-child {
grid-column: 1 / -1;
}
`;

const LoadingMessage = styled.div`
Expand Down
19 changes: 10 additions & 9 deletions src/components/group/GroupCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,16 @@ export const GroupCard = forwardRef<HTMLDivElement, Props>(
<p>{group.participants}</p>
<MaximumParticipants>/ {group.maximumParticipants}명</MaximumParticipants>
</Participant>
{isOngoing === true ? (
<RecruitingDeadline isRecommend={isRecommend}>
{group.deadLine} 종료
</RecruitingDeadline>
) : (
<OngoingDeadline isRecommend={isRecommend}>
{group.deadLine} 모집 마감
</OngoingDeadline>
)}
{(type !== 'modal' || group.type !== 'expired') &&
(isOngoing === true ? (
<RecruitingDeadline isRecommend={isRecommend}>
{group.deadLine} 종료
</RecruitingDeadline>
) : (
<OngoingDeadline isRecommend={isRecommend}>
{group.deadLine} 모집 마감
</OngoingDeadline>
))}
</Bottom>
</Info>
</Card>
Expand Down
1 change: 1 addition & 0 deletions src/components/group/MyGroupBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface Group {
genre?: string;
isOnGoing?: boolean;
isPublic?: boolean;
type?: string;
}

const convertJoinedRoomToGroup = (room: JoinedRoomItem): Group => ({
Expand Down
21 changes: 14 additions & 7 deletions src/hooks/useSocialLoginToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,23 @@ export const useSocialLoginToken = () => {
console.log('🔑 소셜 로그인 토큰 발급 요청');
console.log('📋 loginTokenKey:', loginTokenKey);

// /auth/token API 호출하여 토큰 발급 (임시 토큰 또는 access 토큰)
// /auth/token API 호출하여 토큰 발급 (임시 토큰)
const response = await getToken({ loginTokenKey });

if (response.isSuccess) {
const { token } = response.data;
const { token, isNewUser } = response.data;

// 토큰을 localStorage에 저장 (request header에 사용)
localStorage.setItem('authToken', token);

console.log('✅ Access 토큰 발급 성공 (바로 홈 화면)');
if (isNewUser) {
// 회원가입 진행용 임시 토큰 저장
localStorage.setItem('preAuthToken', token);
localStorage.removeItem('authToken');
console.log('✅ 신규 사용자: 임시 토큰 저장 (회원가입 진행)');
} else {
// 기존 사용자: 액세스 토큰 저장
localStorage.setItem('authToken', token);
localStorage.removeItem('preAuthToken');
console.log('✅ 기존 사용자: 액세스 토큰 저장');
}

// URL에서 loginTokenKey 파라미터 제거
const newUrl = window.location.pathname;
Expand All @@ -53,7 +60,7 @@ export const useSocialLoginToken = () => {
// 토큰 발급 Promise를 저장
tokenPromise.current = handleSocialLoginToken();
}
}, [location.pathname]);
}, [location.pathname, location.search]);

// 토큰 발급 완료를 기다리는 함수 반환
const waitForToken = useCallback(async (): Promise<void> => {
Expand Down
10 changes: 9 additions & 1 deletion src/pages/feed/Feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ const Feed = () => {
navigate('/feed/search');
};

const handleNoticeButton = () => {
navigate('/notice');
};

// 전체 피드 로드 함수
const loadTotalFeeds = useCallback(async (_cursor?: string) => {
try {
Expand Down Expand Up @@ -175,7 +179,11 @@ const Feed = () => {

return (
<Container>
<MainHeader type="home" leftButtonClick={handleSearchButton} />
<MainHeader
type="home"
leftButtonClick={handleSearchButton}
rightButtonClick={handleNoticeButton}
/>
<TabBar tabs={tabs} activeTab={activeTab} onTabClick={setActiveTab} />
{initialLoading || tabLoading ? (
<LoadingSpinner size="large" fullHeight={true} />
Expand Down
10 changes: 9 additions & 1 deletion src/pages/group/Group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ const Group = () => {
navigate('/group/search');
};

const handleNoticeButton = () => {
navigate('/notice');
};

const handleAllRoomsClick = () => {
navigate('/group/search', {
state: {
Expand All @@ -105,7 +109,11 @@ const Group = () => {
<Wrapper>
{isMyGroupModalOpen && <MyGroupModal onClose={closeMyGroupModal} />}
{isCompletedGroupModalOpen && <CompletedGroupModal onClose={closeCompletedGroupModal} />}
<MainHeader type="group" leftButtonClick={openCompletedGroupModal} />
<MainHeader
type="group"
leftButtonClick={openCompletedGroupModal}
rightButtonClick={handleNoticeButton}
/>
<SearchBar placeholder="모임방 참여할 사람!" onClick={handleSearchBarClick} />
<MyGroupBox onMyGroupsClick={openMyGroupModal}></MyGroupBox>
<Blank height={'10px'} margin={'32px 0'}></Blank>
Expand Down
52 changes: 41 additions & 11 deletions src/pages/mypage/WithdrawPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { colors, typography } from '@/styles/global/global';
import leftArrow from '../../assets/common/leftArrow.svg';
import withdraw from '@/assets/mypage/withdraw.svg';
import check from '@/assets/mypage/check.svg';
import { deleteUsers } from '@/api/users/deleteUsers';

const WithdrawPage = () => {
const navigate = useNavigate();
const { openConfirm, closePopup } = usePopupActions();
const { openConfirm, closePopup, openSnackbar } = usePopupActions();
const [isChecked, setIsChecked] = useState(false);

const handleBack = () => {
Expand All @@ -27,10 +28,35 @@ const WithdrawPage = () => {
title: '정말 탈퇴하시겠어요?',
disc: "'예'를 누르면 Thip에서의 모든 기록이 사라져요",
onConfirm: () => {
// 회원탈퇴 API 호출 부분 (비워둠)
console.log('회원탈퇴 API 호출');
closePopup();
navigate('/mypage/withdraw/done');
void (async () => {
try {
const response = await deleteUsers();
if (response.isSuccess) {
closePopup();
navigate('/mypage/withdraw/done');
localStorage.removeItem('authToken');
} else {
closePopup();
openSnackbar({
message: response.message,
variant: 'top',
onClose: () => {},
});
}
} catch (error) {
let serverMessage = '요청 처리 중 오류가 발생했어요.';
if (error && typeof error === 'object' && 'response' in error) {
const axiosError = error as { response?: { data?: { message?: string } } };
serverMessage = axiosError.response?.data?.message || serverMessage;
}
closePopup();
openSnackbar({
message: serverMessage,
variant: 'top',
onClose: () => {},
});
}
})();
},
onClose: () => {
closePopup();
Expand All @@ -50,11 +76,11 @@ const WithdrawPage = () => {
<Content>
<ContentTitle>회원탈퇴 주의사항</ContentTitle>
<ContentText>
회원탈퇴 시 계정정보는 복구 불가능하며 90일 이후 재가입이 가능합니다.
회원탈퇴 시 계정 및 활동 데이터는 <span className="danger">즉시 삭제</span>되며,
<span className="danger"> 복구가 불가능</span>합니다.
</ContentText>
<ContentText>등록된 기록 및 게시물은 삭제되지 않습니다.</ContentText>
<ContentText>등록된 기록 및 게시물은 삭제되지 않습니다.</ContentText>
<ContentText>등록된 기록 및 게시물은 삭제되지 않습니다.</ContentText>
<ContentText>백업 및 로그 역시 보안 저장 후 최대 90일 내 자동 삭제됩니다.</ContentText>
<ContentText>법령상 보존 의무가 있는 정보는 해당 기간 동안 보관됩니다.</ContentText>
</Content>
<CheckSection>
<CheckboxContainer onClick={handleCheckboxChange}>
Expand Down Expand Up @@ -91,7 +117,7 @@ const Container = styled.div`
min-width: 320px;
max-width: 540px;
gap: 30px;
padding: 40px 0px 105px 0px;
padding: 40px 20px 105px 20px;
`;

const Content = styled.div`
Expand All @@ -115,6 +141,10 @@ const ContentText = styled.div`
font-size: ${typography.fontSize.sm};
font-weight: ${typography.fontWeight.regular};
line-height: 20px;

.danger {
color: #ff9496;
}
`;

const CheckSection = styled.div`
Expand Down Expand Up @@ -146,7 +176,7 @@ const Checkbox = styled.div<{ checked: boolean }>`

const CheckLabel = styled.div`
color: ${colors.white};
font-size: ${typography.fontSize.base};
font-size: ${typography.fontSize.sm};
font-weight: ${typography.fontWeight.regular};
line-height: 24px;
`;
Expand Down
Loading
Loading