Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
52976fa
style: 대댓글 달 때 스타일 변경
rusia9217 Apr 18, 2025
8d3990b
style: 자격증 선택 간격 수정
rusia9217 Apr 18, 2025
4007e9a
style: 모달 이상한 것들 수정
rusia9217 Apr 18, 2025
0243748
fix: 시작 캘린더를 누르면 종료 캘린더 닫히게
rusia9217 Apr 18, 2025
822e8e8
fix: 목표 점수 설정시, 직접 입력 + 10씩 추가 감소
rusia9217 Apr 18, 2025
b28198d
style: 모의고사 메인 페이지 수정 + 시간 조정
rusia9217 Apr 20, 2025
0bfa034
style: 모의고사 결과 table태그로 변경 -> 중복 boarder처리 제거
rusia9217 Apr 20, 2025
5cf06f0
fix: 뒤로갔다 돌아올경우 재랜더링시켜 좋아요 상태 변경
rusia9217 Apr 20, 2025
f46efb1
style: best댓글도 내가 좋아요 누른 상태에 따라 처리
rusia9217 Apr 20, 2025
da635cd
fix: 수정한 날짜가 있으면 수정한 날짜로 변경, optionMenu는 보이게 처리 => 내부에서 권한 설정
rusia9217 Apr 20, 2025
4abd188
feat: 틀린문제 애니메이션, 한 컴포넌트 열리면 다른 컴포넌트 닫히게
rusia9217 Apr 20, 2025
a383681
fix: 똑같은 번호를 다시 클릭하면 릿셋되도록 구현
rusia9217 Apr 20, 2025
40c9f32
fix: 문제번호 검색하고 다른 게시판 클릭하고 돌아오면 리셋되도록
rusia9217 Apr 20, 2025
dccce99
style: header가 modal 밑으로 들어가도록 구현
rusia9217 Apr 20, 2025
7f02e31
fix: 스톱워치 종료/일시정지 버튼 구현
rusia9217 Apr 20, 2025
bfadd41
fix: 새로고침시 스톱워치 시간 초기화 되지 않도록 구현
rusia9217 Apr 20, 2025
61247d5
fix: 타이머가 동작되도록 롤백
rusia9217 Apr 20, 2025
9836b22
fix: 목표 진행중 태그 삭제
rusia9217 Apr 20, 2025
d0be302
fix: 목표 수정 버튼 삭제
rusia9217 Apr 20, 2025
e8e34dc
fix: 스톱워치 새로고침시 유지되도록 수정
rusia9217 Apr 21, 2025
0867905
fix: api 변경에 따른, 데이터 body에 추가
rusia9217 Apr 24, 2025
7971016
feat: 검색 결과 화면에 띄우기
rusia9217 Apr 24, 2025
59df271
fix: 목표 설정 에러 수정 -> 요일 선택 렌더링 수정
rusia9217 Apr 24, 2025
33356d3
fix: onboarding으로 넘어갈 때, 바로 데이터 불러와지지 않는 문제 해결
rusia9217 Apr 24, 2025
64a1180
fix: 목표 설정을 하지 않을때만 모달창이 뜨도록
rusia9217 Apr 24, 2025
4308b7c
fix: 내가 쓴 댓글의 post글 에러 수정 -> api 변경 반영
rusia9217 Apr 24, 2025
5a9c1f2
style: 좋아요 누를 때, 아이콘 FillBlue로 변경
rusia9217 Apr 24, 2025
1503fcd
fix: 이전 목표 항목 제거
rusia9217 Apr 24, 2025
0df46ca
fix: 내가 쓴 글 api 파라미터명 변경
rusia9217 Apr 24, 2025
d0619c0
style: ActiveButton 애니메이션 시간 줄이기
rusia9217 Apr 24, 2025
500b570
fix: pretter error 해결
rusia9217 Apr 24, 2025
82d07bd
fix: 틀린문제 모아보기 props값 누락 추가
rusia9217 Apr 24, 2025
9e76e37
feat: 애플로그인 구현
rusia9217 Apr 24, 2025
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
10 changes: 10 additions & 0 deletions public/login/AppleLogo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/stopwatch/PauseIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/stopwatch/StartIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/stopwatch/StopIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions public/stopwatch/TimerIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 14 additions & 5 deletions src/app/(main)/community/[category]/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,21 @@ import ReportSubmittedModal from '@/components/community/ReportSubmittedModal';
import useBest3TipPosts from '@/lib/hooks/useBest3TipPosts';
import useGetCommunityPost from '@/lib/hooks/useGetCommunityPost';
import { certificateIdAtom } from '@/recoil/atom';
import { boardTypeInitAtom, boardTypeStateAtom, selectedReplyParentNameAtom } from '@/recoil/community/atom';
import { boardTypeInitAtom, boardTypeStateAtom, selectedAnswerUserIdAtom } from '@/recoil/community/atom';
import { BoardType } from '@/types/community/type';

const CommunityDetailPage = () => {
const router = useRouter();
const params = useParams();
const certificateId = useRecoilValue(certificateIdAtom);
const userId = Cookies.get('userId');
const loginUserId = Cookies.get('userId');
//커뮤니티 포스트에 해당하는 데이터를 가져옴
const { communityPostData, isLoading, isError, communityPostDataMutate } = useGetCommunityPost(params.id);
const [isQuestionModalOpen, setIsQuestionModalOpen] = useState(false);
const [isPostOptionModalOpen, setPostIsOptionModalOpen] = useState(false);
const [isCommentOptionModalOpen, setCommentIsOptionModalOpen] = useState(false);
const [selectedCommentId, setSelectedCommentId] = useState(0);
const [selectedAnswerUserId, setSelectedAnswerUserId] = useRecoilState(selectedAnswerUserIdAtom);
const [isClickEditPost, setIsClickEditPost] = useState(false);
const [isReportSubmittedModalOpen, setIsReportSubmittedModalOpen] = useState(false);
const { bestTipPosts } = useBest3TipPosts(certificateId);
Expand Down Expand Up @@ -136,14 +137,19 @@ const CommunityDetailPage = () => {
)}
{isCommentOptionModalOpen && (
<CommentOptionModal
commentId={selectedCommentId}
selectedCommentId={selectedCommentId}
loginUserId={loginUserId}
writerUserId={selectedAnswerUserId}
setSelectedAnswerUserId={setSelectedAnswerUserId}
setIsReportSubmittedModalOpen={setIsReportSubmittedModalOpen}
setCommentIsOptionModal={setCommentIsOptionModalOpen}
communityPostDataMutate={communityPostDataMutate}
/>
)}
{isPostOptionModalOpen && (
<PostOptionModal
loginUserId={loginUserId}
writerUserId={postData.user.userId}
communityId={params.category}
postId={postData.postId}
setPostIsOptionModal={setPostIsOptionModalOpen}
Expand Down Expand Up @@ -187,8 +193,11 @@ const CommunityDetailPage = () => {
setPostIsOptionModalOpen(true);
}}
profileUrl={postData.user.profileImage}
isWriter={parseInt(userId as string) === postData.user.userId}
createdTime={postData.dateTime.createdAt}
createdTime={
postData.dateTime.createdAt === postData.dateTime.modifiedAt
? postData.dateTime.createdAt
: postData.dateTime.modifiedAt
}
nickName={postData.user.nickname}
/>
<CommunityPost
Expand Down
8 changes: 4 additions & 4 deletions src/app/(main)/community/[category]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ export default function CommunityCategoryPage() {
const [isClickedWriteButton, setIsClickedWriteButton] = useState(false);
const router = useRouter();
//해설 게시글 검색
const [searchValue, setSearchValue] = useRecoilState<number>(commentarySearchQuestionSequence);
const debouncedValue = useDebounce<number>(searchValue, 100);
const [searchValue, setSearchValue] = useRecoilState<number | undefined>(commentarySearchQuestionSequence);
const debouncedValue = useDebounce<number | undefined>(searchValue, 100);
const { commentarySearchResults } = useGetCommentarySearchResults(
certificateId,
selectedCommentaryYearFilterContent,
selectedCommentaryRoundFilterContent,
searchValue,
searchValue as number,
);
const [isModalOpen, setIsModalOpen] = useState<boolean>(true);
const { reviewWriteAccess } = useCheckReviewWriteAccess(certificateId);
Expand Down Expand Up @@ -199,7 +199,7 @@ export default function CommunityCategoryPage() {
boardType={boardType}
setSearchValue={setSearchValue}
searchValue={searchValue}
debouncedValue={debouncedValue}
debouncedValue={debouncedValue as number}
/>
) : boardType === 'TIP' ? (
<NormalAndTipBoardList boardType={boardType} init={boardTypeInit} />
Expand Down
9 changes: 8 additions & 1 deletion src/app/(main)/exam/wrong/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import React, { useCallback, useEffect } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { useInView } from 'react-intersection-observer';

import Header from '@/components/common/Header';
Expand All @@ -13,6 +13,7 @@ import { ReviewIncorrectAnswers, ReviewIncorrectAnswersContent } from '@/types/g
const IncorrectQuestion = () => {
const [ref, inView] = useInView();
const { incorrectQuestions, setSize, mutate, isLoading } = useAllIncorrectQuestions();
const [openCardId, setOpenCardId] = useState<number | null>(null); // ✅ 현재 열려있는 카드 ID

const getMoreItem = useCallback(async () => {
if (incorrectQuestions) {
Expand All @@ -39,6 +40,12 @@ const IncorrectQuestion = () => {
return (
<div key={index} ref={ref}>
<IncorrectQuestionCard
isOpen={openCardId === wrongQuestion.userAnswerId}
onToggle={() =>
setOpenCardId((prev) =>
prev === wrongQuestion.userAnswerId ? null : wrongQuestion.userAnswerId,
)
}
incorrectQuestionsMutate={mutate}
userAnswerId={wrongQuestion.userAnswerId}
selectOptionSeq={wrongQuestion.selectOptionSeq}
Expand Down
109 changes: 61 additions & 48 deletions src/app/(main)/home/goal-setting/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useRouter } from 'next/navigation';
import * as React from 'react';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useSWRConfig } from 'swr';

Expand Down Expand Up @@ -32,6 +32,7 @@ const GoalSetting = () => {
const { userGoals } = useGetUserGoals(certificateId);
const { mutate } = useSWRConfig();
const router = useRouter();

/**
* 최근 목표ID를 불러오는 함수
*/
Expand All @@ -42,9 +43,11 @@ const GoalSetting = () => {
return null; // 또는 적절한 기본값/오류 처리
};
// 기존에 설정한 목표를 불러오는 데이터 패칭
const { goalSettingData, isLoading, isError } = useGetGoalSettingData(getLastGoalId() || 0);
const { goalSettingData } = useGetGoalSettingData(getLastGoalId() || 0);
const [goalData, setGoalData] = useRecoilState(goalSettingState);
const [isResetButtonClick, setIsResetButtonClick] = useState(false);
const [isTrigger, setIsTrigger] = useState(true);
const [isGoalDataReady, setIsGoalDataReady] = useState(false);

/**
* Recoil 상태를 초기화하는 함수
Expand Down Expand Up @@ -107,13 +110,15 @@ const GoalSetting = () => {
if (response) {
const initialState = initializeGoalSettingState(response.result);
setGoalData(initialState);
setIsGoalDataReady(true); // 🔥 렌더링 트리거
setIsTrigger(false);
} else {
// 에러 처리를 수행할 수 있습니다.
console.error('Failed to fetch goal setting data');
console.error('목표 데이터를 불러오지 못했습니다.');
setIsTrigger(false);
}
} catch (error) {
// 네트워크 오류 또는 다른 예외에 대한 처리를 수행할 수 있습니다.
console.error('Error fetching goal setting data:', error);
console.error('목표 데이터 로딩 실패:', error);
setIsTrigger(false);
}
};

Expand All @@ -123,10 +128,11 @@ const GoalSetting = () => {
const resetData = () => {
const resetState = resetGoalSettingState();
setGoalData(resetState);
setIsGoalDataReady(true); // 🔥 이게 있어야 새 목표도 바로 렌더됨
};

return (
<div className={'min-h-screen'}>
<>
{isSettingNewGoalModalOpen && userGoals ? (
<SettingNewGoalModal
isFirstGoalSetting={userGoals.length === 0} //새로 생성된 목표가 없을 경우
Expand All @@ -137,47 +143,54 @@ const GoalSetting = () => {
setIsSettingNewModal={setIsSettingNewGoalModalOpen}
/>
) : null}
<Header
headerType={'dynamic'}
title={'목표설정'}
rightElement={
isResetButtonClick ? (
<Button
onClick={async () => {
await postGoalSettingData(goalData, selectedCertificationId).then((r) => {
console.log('목표 저장 성공~r', r);
});
await mutate(`/certificates/${certificateId}/goals`);
setIsResetButtonClick(false);
router.push('/home');
}}
className={' w-fit'}>
저장
</Button>
) : (
<Button
onClick={() => {
putGoalSettingData(goalData, getLastGoalId() || 0).then((r) => {
console.log('목표 수정 성공', r);
router.push('/home');
});
}}
className={'w-fit'}>
수정
</Button>
)
}></Header>
<div className="flex flex-col gap-y-8 mx-5 mb-8">
{/*자격증 선택*/}
{isResetButtonClick ? <SelectCertification /> : null}
{/*목표 점수 설정*/}
<SetGoalScore />
{/*자격증 준비기간 설정*/}
<PreparationPeriodSetting />
{/*매일 목표 설정*/}
<SetDailyGoals />
</div>
</div>
{!isGoalDataReady ? (
<></>
) : (
<div className={''}>
<Header
className={'fixed'}
headerType={'dynamic'}
title={'목표설정'}
rightElement={
isResetButtonClick ? (
<Button
onClick={async () => {
await postGoalSettingData(goalData, selectedCertificationId).then((r) => {
console.log('목표 저장 성공~r', r);
});
await mutate(`/certificates/${certificateId}/goals`);
setIsResetButtonClick(false);
router.push('/home');
}}
className={' w-fit'}>
저장
</Button>
) : (
<Button
onClick={() => {
putGoalSettingData(goalData, getLastGoalId() || 0).then((r) => {
console.log('목표 수정 성공', r, goalData);
router.push('/home');
});
}}
className={'w-fit'}>
수정
</Button>
)
}></Header>
<div className="flex flex-col gap-y-8 pt-20 mx-5 mb-8">
{/*자격증 선택*/}
{isResetButtonClick ? <SelectCertification /> : null}
{/*목표 점수 설정*/}
<SetGoalScore />
{/*자격증 준비기간 설정*/}
<PreparationPeriodSetting />
{/*매일 목표 설정*/}
<SetDailyGoals />
</div>
</div>
)}
</>
);
};
export default GoalSetting;
15 changes: 8 additions & 7 deletions src/app/(main)/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,19 @@ function HomeComponents() {
useEffect(() => {
const existingAccessToken = Cookies.get('accessToken');
const existingRefreshToken = Cookies.get('refreshToken');
if (existingAccessToken && existingRefreshToken) {
setIsCookieSet(true);
return;
}

// 쿠키가 없고 URL에서 토큰이 있으면 세팅
// ✅ 쿼리에 토큰이 있으면 무조건 쿠키 갱신
if (accessToken && refreshToken) {
Cookies.set('accessToken', accessToken, { expires: Date.now() + 604800000 });
Cookies.set('refreshToken', refreshToken, { expires: Date.now() + 604800000 });
setIsCookieSet(true);
return;
}

// ✅ 쿼리 없지만 쿠키 있으면 그대로 진행
if (existingAccessToken && existingRefreshToken) {
setIsCookieSet(true);
} else {
// 둘 다 없으면 그래도 isCookieSet은 true로 둬야 UI 진행됨
setIsCookieSet(false);
}
}, [accessToken, refreshToken]);
Expand Down Expand Up @@ -150,7 +151,7 @@ function HomeComponents() {
};

useEffect(() => {
if (goalSettingStatus) {
if (goalSettingStatus && !goalSettingStatus.result) {
setIsGoalSettingStatusModalOpen(!goalSettingStatus.result);
}
}, [goalSettingStatus]);
Expand Down
17 changes: 15 additions & 2 deletions src/app/(main)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
'use client';

import React from 'react';
import React, { useState } from 'react';
import { useRecoilState } from 'recoil';

export default function layout({ children }: { children: React.ReactNode }) {
import AccumulatedTime from '@/components/stopwatch/AccumulatedTime';
import StopwatchAlert from '@/components/stopwatch/StopwatchAlert';
import { onModalAtom } from '@/recoil/stopwatch/atom';

export default function Layout({ children }: { children: React.ReactNode }) {
const [onModal, setOnModal] = useRecoilState<boolean>(onModalAtom); //기록하기 알림창 onoff조절
const [onAccumulatedModal, setOnAccumulatedModal] = useState<boolean>(false); //기록완료 알림창 onoff조절
return (
<>
{onModal ? ( //기록하기 알림창 열림OnOff
<StopwatchAlert setOnAccumulatedModal={setOnAccumulatedModal} setOnModal={setOnModal} />
) : null}
{onAccumulatedModal ? ( //기록완료 알림창OnOff
<AccumulatedTime setOnAccumulatedModal={setOnAccumulatedModal} />
) : null}
<div>{children}</div>
</>
);
Expand Down
Loading