Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AUD-112] 코스탭에서 코스명 수정 기능 구현 #64

Merged
merged 13 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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
2 changes: 0 additions & 2 deletions src/apis/course/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,12 @@ export const CourseRepository = {
},

async deleteCourseAsync({
userId,
courseId,
}: CourseRequestParamType['deleteCourse']) {
await deleteAsync<
ApiResponseType<void>,
CourseRequestParamType['deleteCourse']
>('/v1/courses', {
userId,
courseId,
});
},
Expand Down
1 change: 0 additions & 1 deletion src/apis/course/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export interface CourseRequestParamType {
courseName: string;
};
deleteCourse: {
userId: number;
courseId: number;
};
}
Expand Down
1 change: 1 addition & 0 deletions src/components/pop-over/PopOverItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const PopOverItem = ({
const { closePopOver } = usePopOverContext();

const handlePopOverItem = (event: MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
onClick?.(event);
closePopOver();
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';

import { COLOR } from '@/styles/foundation';
import { sprinkles } from '@/styles/sprinkle.css';

export const modalHeader = style({
width: '100%',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
});

export const couseNameInput = style([
sprinkles({ typography: 'Regular15' }),
{
border: `1px solid ${COLOR.Gray200}`,
width: '100%',
flex: 1,
padding: '8px 12px',
borderRadius: '6px',
color: COLOR.Gray900,

'::placeholder': {
color: COLOR.Gray400,
},
},
]);

export const editButton = recipe({
base: [
sprinkles({ typography: 'Bold17' }),
{
height: '56px',
flex: 1,
textAlign: 'center',
borderRadius: '10px',
backgroundColor: COLOR.Gray900,
color: COLOR.MonoWhite,
},
],
variants: {
isButtonDisabled: {
true: {
backgroundColor: COLOR.Gray300,
cursor: 'default',
},
false: {
backgroundColor: COLOR.Gray900,
},
},
},
});

export const modalCloseButton = style({
padding: '6px',
color: COLOR.Gray400,
});
70 changes: 70 additions & 0 deletions src/features/course/course-name-edit-modal/CourseNameEditModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useState } from 'react';

import CloseIcon from '@/assets/icons/close.svg?react';
import Modal from '@/components/modal/Modal';
import { useModal } from '@/hooks/useModal';
import { useToast } from '@/hooks/useToast';
import { usePatchCourseName } from '@/query-hooks/course/mutation';

import * as S from './CourseNameEditModal.css';

interface PropsType {
courseId: number;
courseName: string;
}

const CourseNameEditModal = ({ courseId, courseName }: PropsType) => {
const { closeModal } = useModal();
const { setToast } = useToast();

const [isButtonDisabled, setIsButtonDisabled] = useState(false);
const [newCourseName, setNewCourseName] = useState(courseName);

const { mutate: updateCourseName } = usePatchCourseName({ courseId });

const handleEditButtonClick = () => {
updateCourseName(newCourseName);
closeModal();
setToast('코스 이름이 변경되었어요');
};

const handleNewCourseNameChange = ({
target,
}: React.ChangeEvent<HTMLInputElement>) => {
if (!target.value) setIsButtonDisabled(true);
else setIsButtonDisabled(false);

setNewCourseName(target.value);
};

return (
<Modal>
<div className={S.modalHeader}>
<Modal.Title>코스명 수정</Modal.Title>
<button className={S.modalCloseButton} onClick={closeModal}>
<CloseIcon width={28} height={28} />
</button>
</div>

<Modal.Content>
<input
className={S.couseNameInput}
value={newCourseName}
onChange={handleNewCourseNameChange}
/>
</Modal.Content>

<Modal.Footer>
<button
className={S.editButton({ isButtonDisabled })}
onClick={handleEditButtonClick}
disabled={isButtonDisabled}
>
수정
</button>
</Modal.Footer>
</Modal>
);
};

export default CourseNameEditModal;
3 changes: 3 additions & 0 deletions src/features/course/course-name-edit-modal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import CourseNameEditModal from './CourseNameEditModal';

export default CourseNameEditModal;
36 changes: 36 additions & 0 deletions src/features/course/course-remove-modal/CourseRemoveModal.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';

import { COLOR } from '@/styles/foundation';
import { sprinkles } from '@/styles/sprinkle.css';

export const informationText = style([
sprinkles({ typography: 'Medium18' }),
{
color: COLOR.Gray600,
},
]);

export const footerButton = recipe({
base: [
sprinkles({ typography: 'Bold17' }),
{
height: '56px',
flex: 1,
textAlign: 'center',
borderRadius: '10px',
},
],
variants: {
action: {
cancel: {
color: COLOR.Gray500,
backgroundColor: COLOR.Gray100,
},
remove: {
color: COLOR.MonoWhite,
backgroundColor: COLOR.Red500,
},
},
},
});
49 changes: 49 additions & 0 deletions src/features/course/course-remove-modal/CourseRemoveModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Modal from '@/components/modal/Modal';
import { useModal } from '@/hooks/useModal';
import { useToast } from '@/hooks/useToast';
import { useDeleteCourse } from '@/query-hooks/course/mutation';

import * as S from './CourseRemoveModal.css';

interface PropsType {
courseId: number;
}

const CourseRemoveModal = ({ courseId }: PropsType) => {
const { closeModal } = useModal();
const { setToast } = useToast();
const { mutate: deleteCourse } = useDeleteCourse({ courseId });

const handleRemoveButtonClick = () => {
deleteCourse();
closeModal();
setToast('코스가 삭제되었어요');
};

return (
<Modal>
<Modal.Title>이 코스를 삭제할까요?</Modal.Title>
<Modal.Content>
<p className={S.informationText}>
코스에 저장된 핀은 복구할 수 없어요
</p>
</Modal.Content>
<Modal.Footer>
<button
className={S.footerButton({ action: 'cancel' })}
onClick={closeModal}
>
취소
</button>
<button
className={S.footerButton({ action: 'remove' })}
onClick={handleRemoveButtonClick}
>
삭제
</button>
</Modal.Footer>
</Modal>
);
};

export default CourseRemoveModal;
3 changes: 3 additions & 0 deletions src/features/course/course-remove-modal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import CourseRemoveModal from './CourseRemoveModal';

export default CourseRemoveModal;
5 changes: 4 additions & 1 deletion src/features/course/course-tab/CourseTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ const CourseTab = ({
</div>
</div>
</div>
<ThreeDotButton courseId={courseId} />

{isMyCourse && (
<ThreeDotButton courseId={courseId} courseName={courseName} />
)}
</div>
);
};
Expand Down
20 changes: 14 additions & 6 deletions src/features/course/course-tab/ThreeDotButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,29 @@ import EditIcon from '@/assets/icons/edit.svg?react';
import ThreeDotIcon from '@/assets/icons/threeDot.svg?react';
import TrashCanIcon from '@/assets/icons/trashCan.svg?react';
import PopOver from '@/components/pop-over';
import { useModal } from '@/hooks/useModal';

import CourseNameEditModal from '../course-name-edit-modal';
import CourseRemoveModal from '../course-remove-modal/CourseRemoveModal';

import * as S from './ThreeDotButton.css';

interface PropsType {
courseId: number;
courseName: string;
}

const ThreeDotButton = ({ courseId }: PropsType) => {
// TODO : 모달 컴포넌트 개발 이후 수정 모달 추가 필요
const ThreeDotButton = ({ courseId, courseName }: PropsType) => {
const { openModal } = useModal();

const handleCourseEditIconClick = () => {
console.log(courseId);
openModal(
<CourseNameEditModal courseId={courseId} courseName={courseName} />,
);
};

const handleRemoveCourseIconClick = () => {
console.log(courseId);
openModal(<CourseRemoveModal courseId={courseId} />);
};

return (
Expand All @@ -27,12 +35,12 @@ const ThreeDotButton = ({ courseId }: PropsType) => {
<PopOver.Content>
<PopOver.Item onClick={handleCourseEditIconClick}>
<EditIcon />
<p className={S.text}>장소명 수정</p>
<p className={S.text}>코스명 수정</p>
</PopOver.Item>

<PopOver.Item onClick={handleRemoveCourseIconClick}>
<TrashCanIcon />
<p className={S.text}>장소 삭제</p>
<p className={S.text}>코스 삭제</p>
</PopOver.Item>
</PopOver.Content>
</PopOver>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ export const modalHeader = style({
alignItems: 'center',
});

export const couseNameInput = style({
border: `1px solid ${COLOR.Gray200}`,
width: '100%',
flex: 1,
padding: '16px',
borderRadius: '6px',
});
export const couseNameInput = style([
sprinkles({ typography: 'Regular15' }),
{
border: `1px solid ${COLOR.Gray200}`,
width: '100%',
flex: 1,
padding: '8px 12px',
borderRadius: '6px',
color: COLOR.Gray900,

'::placeholder': {
color: COLOR.Gray400,
},
},
]);

export const saveButton = recipe({
base: [
Expand Down
13 changes: 10 additions & 3 deletions src/query-hooks/course/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,24 @@ import { COURSE_QUERY_KEY } from './key';

// 특정 코스를 삭제하는 Hook useDeleteCourse
export const useDeleteCourse = ({
userId,
courseId,
...options
}: CourseRequestParamType['deleteCourse'] &
UseMutationOptions<void, AxiosError>) => {
const queryClient = useQueryClient();
return useMutation({
...options,
mutationFn: () =>
CourseRepository.deleteCourseAsync({ userId, courseId }),
mutationFn: () => CourseRepository.deleteCourseAsync({ courseId }),
onSuccess: () => {
queryClient.removeQueries({
queryKey: COURSE_QUERY_KEY.detail(courseId),
});
queryClient.invalidateQueries({
queryKey: COURSE_QUERY_KEY.list(),
});
queryClient.invalidateQueries({
queryKey: COURSE_QUERY_KEY.owned(),
});
},
throwOnError: true,
});
Expand All @@ -45,6 +49,9 @@ export const usePatchCourseName = ({
queryClient.invalidateQueries({
queryKey: COURSE_QUERY_KEY.detail(courseId),
});
queryClient.invalidateQueries({
queryKey: COURSE_QUERY_KEY.list(),
});
},
throwOnError: true,
});
Expand Down
Loading