- {nickNamesnackBar && (
+ {nickNameSnackBar && (
}
message="닉네임 변경 완료"
type="success"
/>
)}
- {imagenackBar && (
+ {imageSnackBar && (
}
message="프로필 변경 완료"
type="success"
/>
)}
+ {nickNameErrorSnackBar && (
+
}
+ message="동일한 이름입니다."
+ type="error"
+ />
+ )}
);
}
diff --git a/src/components/mysetting/usermockdata.ts b/src/components/mysetting/usermockdata.ts
deleted file mode 100644
index 30b0fc40..00000000
--- a/src/components/mysetting/usermockdata.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-const userMockData = {
- teamId: 1,
- image:
- 'https://i.pinimg.com/736x/0c/c7/16/0cc7169aec1d81898f1daf4b46d41857.jpg',
- nickname: '장용한',
- updatedAt: '2024-10-12T03:49:32.544Z',
- createdAt: '2024-10-12T03:49:33.544Z',
- email: 'wkddydgks1@naver.com',
- id: 1,
-};
-
-export default userMockData;
diff --git a/src/components/tasks/AddTaskForm.tsx b/src/components/tasks/AddTaskForm.tsx
index 1fe9e5cc..8c23672e 100644
--- a/src/components/tasks/AddTaskForm.tsx
+++ b/src/components/tasks/AddTaskForm.tsx
@@ -3,7 +3,7 @@ import { useForm } from 'react-hook-form';
import { Dayjs } from 'dayjs';
import { useDate } from '@/src/contexts/DateContext';
import { useMutation, useQueryClient } from '@tanstack/react-query';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import { postTask, TaskUrlParams } from '@/src/api/tasks/taskAPI';
import { toKSTISOString } from '@utils/toKSTISOString';
diff --git a/src/components/tasks/TaskCard.tsx b/src/components/tasks/TaskCard.tsx
index c0abdbd1..a437365c 100644
--- a/src/components/tasks/TaskCard.tsx
+++ b/src/components/tasks/TaskCard.tsx
@@ -6,7 +6,7 @@ import type { TaskDto } from '@/src/types/tasks/taskDto';
import { formatTaskCardDate } from '@utils/getFormattedDate';
import { useRouter } from 'next/router';
import { useModal } from '@hooks/useModal';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import useDropdownModals from '@hooks/useDropdownModals';
import Image from 'next/image';
diff --git a/src/components/tasks/TaskComments.tsx b/src/components/tasks/TaskComments.tsx
index a0647633..2dfa62eb 100644
--- a/src/components/tasks/TaskComments.tsx
+++ b/src/components/tasks/TaskComments.tsx
@@ -11,7 +11,7 @@ import {
patchComment,
CommentUrlParams,
} from '@/src/api/tasks/commentAPI';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import getSortedDate from '@utils/getSortedDate';
import getTimeAgo from '@utils/getTimeAgo';
import ClickMotion from '@components/@shared/animation/ClickMotion';
@@ -222,7 +222,7 @@ export function TaskComments() {
@@ -230,7 +230,7 @@ export function TaskComments() {
type="submit"
disabled={!isValid}
className={`my-auto h-9 w-14 rounded-md border-[1px] px-2 py-1 text-xs-medium text-white
- ${!isValid ? 'border-background-tertiary bg-text-default ' : 'border-brand-primary bg-brand-primary hover:bg-brand-secondary'}`}
+ ${!isValid ? 'border-background-tertiary bg-text-disabled ' : 'border-brand-primary bg-brand-primary hover:bg-interaction-hover'}`}
>
수정
diff --git a/src/components/tasks/TaskDate.tsx b/src/components/tasks/TaskDate.tsx
index 293349cb..4aa95b75 100644
--- a/src/components/tasks/TaskDate.tsx
+++ b/src/components/tasks/TaskDate.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { formatTaskListDate } from '@utils/getFormattedDate';
import { useDate } from '@/src/contexts/DateContext';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import BounceTextMotion from '@components/@shared/animation/BounceTextMotion';
import SlideInMotion from '@components/@shared/animation/SlideInMotion';
import DatePagination from './UI/DatePagination';
diff --git a/src/components/tasks/TaskDetails.tsx b/src/components/tasks/TaskDetails.tsx
index ea07f58b..b1731da5 100644
--- a/src/components/tasks/TaskDetails.tsx
+++ b/src/components/tasks/TaskDetails.tsx
@@ -6,7 +6,7 @@ import RepeatIcon from '@icons/repeat.svg';
import { useRouter } from 'next/router';
import { useQuery } from '@tanstack/react-query';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import { useDate } from '@/src/contexts/DateContext';
import { toKSTISOString } from '@utils/toKSTISOString';
import { getTask, patchTask, TaskUrlParams } from '@/src/api/tasks/taskAPI';
diff --git a/src/components/tasks/TaskList.tsx b/src/components/tasks/TaskList.tsx
index 875ed41c..89e29315 100644
--- a/src/components/tasks/TaskList.tsx
+++ b/src/components/tasks/TaskList.tsx
@@ -1,9 +1,9 @@
import Link from 'next/link';
import { useEffect, useCallback, useState, useRef } from 'react';
import { useRouter } from 'next/router';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import getResponsiveValue from '@utils/getResponsiveValue';
-import TextButtonMotion from '@components/@shared/animation/TextButtonMotion';
+import ButtonMotion from '@components/@shared/animation/ButtonMotion';
import TaskCard from './TaskCard';
import NoTaskCard from './UI/NoTaskCard';
import ListPagination from './UI/ListPagination';
@@ -135,14 +135,14 @@ export default function TaskList() {
query: { taskListId: taskList.id },
}}
>
-
+
-
+
))}
diff --git a/src/components/tasks/UI/CheckBox.tsx b/src/components/tasks/UI/CheckBox.tsx
index 49fe65f3..da345f94 100644
--- a/src/components/tasks/UI/CheckBox.tsx
+++ b/src/components/tasks/UI/CheckBox.tsx
@@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { patchTask, TaskUrlParams } from '@/src/api/tasks/taskAPI';
import ActiveCheckBoxIcon from '@icons/checkbox_active.svg';
import InActiveCheckBoxIcon from '@icons/checkbox_inactive.svg';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import type { TaskRequestBody } from '@/src/types/tasks/taskDto';
import ClickMotion from '@components/@shared/animation/ClickMotion';
diff --git a/src/components/tasks/UI/ListPagination.tsx b/src/components/tasks/UI/ListPagination.tsx
index 39c3f7f6..9649a167 100644
--- a/src/components/tasks/UI/ListPagination.tsx
+++ b/src/components/tasks/UI/ListPagination.tsx
@@ -1,6 +1,6 @@
import ICON_PATHS from '@constants/iconPaths';
import Image from 'next/image';
-import TextButtonMotion from '@components/@shared/animation/TextButtonMotion';
+import ButtonMotion from '@components/@shared/animation/ButtonMotion';
interface PaginationProps {
currentPage: number;
@@ -17,7 +17,7 @@ export default function ListPagination({
}: PaginationProps) {
return (
-
+
이전 목록
-
-
+
@@ -63,7 +63,7 @@ export default function ListPagination({
className="m-auto"
/>
-
+
);
}
diff --git a/src/components/tasks/UI/button/AddTaskButton.tsx b/src/components/tasks/UI/button/AddTaskButton.tsx
index bca33983..45b67242 100644
--- a/src/components/tasks/UI/button/AddTaskButton.tsx
+++ b/src/components/tasks/UI/button/AddTaskButton.tsx
@@ -1,5 +1,5 @@
import { useModal } from '@hooks/useModal';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import { useDate } from '@/src/contexts/DateContext';
import ClickMotion from '@components/@shared/animation/ClickMotion';
import Button from '@components/@shared/Button';
diff --git a/src/components/tasks/UI/button/AddTaskListButton.tsx b/src/components/tasks/UI/button/AddTaskListButton.tsx
index 9dd3fffe..529db7ca 100644
--- a/src/components/tasks/UI/button/AddTaskListButton.tsx
+++ b/src/components/tasks/UI/button/AddTaskListButton.tsx
@@ -2,7 +2,7 @@ import Image from 'next/image';
import { useModal } from '@hooks/useModal';
import useViewportSize from '@hooks/useViewportSize';
import ICON_PATHS from '@constants/iconPaths';
-import TextButtonMotion from '@components/@shared/animation/TextButtonMotion';
+import ButtonMotion from '@components/@shared/animation/ButtonMotion';
import AddTaskListModal from '../modal/AddTaskListModal';
export default function AddTaskListButton() {
@@ -16,7 +16,7 @@ export default function AddTaskListButton() {
onClick={onOpen}
className=" flex items-center gap-2"
>
-
+
{isMobile ? '목록 추가' : '새로운 목록 추가하기'}
-
+
{isOpen &&
}
diff --git a/src/components/tasks/UI/modal/AddTaskListModal.tsx b/src/components/tasks/UI/modal/AddTaskListModal.tsx
index 660e5014..fc2612ab 100644
--- a/src/components/tasks/UI/modal/AddTaskListModal.tsx
+++ b/src/components/tasks/UI/modal/AddTaskListModal.tsx
@@ -5,7 +5,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';
import { TASKLIST_REQUEST_INIT } from '@constants/initValues';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import { postTaskList, TaskListUrlParams } from '@/src/api/tasks/taskListAPI';
import type { TaskListRequestBody } from '@/src/types/tasks/taskListDto';
import NameInput from '../input/NameInput';
diff --git a/src/components/tasks/UI/modal/DeleteRecurringModal.tsx b/src/components/tasks/UI/modal/DeleteRecurringModal.tsx
index b8338a1f..4bf5e141 100644
--- a/src/components/tasks/UI/modal/DeleteRecurringModal.tsx
+++ b/src/components/tasks/UI/modal/DeleteRecurringModal.tsx
@@ -3,7 +3,7 @@ import Button from '@components/@shared/Button';
import { Modal } from '@components/@shared/Modal';
import { deleteRecurringTask, TaskUrlParams } from '@/src/api/tasks/taskAPI';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import { useMutation, useQueryClient } from '@tanstack/react-query';
interface DeleteRecurringModalProps {
diff --git a/src/components/tasks/UI/modal/DeleteTaskModal.tsx b/src/components/tasks/UI/modal/DeleteTaskModal.tsx
index bf7ad85a..2b6f6498 100644
--- a/src/components/tasks/UI/modal/DeleteTaskModal.tsx
+++ b/src/components/tasks/UI/modal/DeleteTaskModal.tsx
@@ -3,7 +3,7 @@ import Button from '@components/@shared/Button';
import { Modal } from '@components/@shared/Modal';
import { deleteTask, TaskUrlParams } from '@/src/api/tasks/taskAPI';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import { useMutation, useQueryClient } from '@tanstack/react-query';
interface DeleteTaskModalProps {
diff --git a/src/components/tasks/UI/modal/EditTaskModal.tsx b/src/components/tasks/UI/modal/EditTaskModal.tsx
index 776504c5..8dd67b38 100644
--- a/src/components/tasks/UI/modal/EditTaskModal.tsx
+++ b/src/components/tasks/UI/modal/EditTaskModal.tsx
@@ -2,7 +2,7 @@ import Button from '@components/@shared/Button';
import { Modal } from '@components/@shared/Modal';
import { patchTask, TaskUrlParams } from '@/src/api/tasks/taskAPI';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';
import { TASK_REQUEST_INIT } from '@constants/initValues';
diff --git a/src/components/team/CircleGraph.tsx b/src/components/team/CircleGraph.tsx
index 69e6a97e..bbe4faff 100644
--- a/src/components/team/CircleGraph.tsx
+++ b/src/components/team/CircleGraph.tsx
@@ -2,6 +2,7 @@ import {
calculateCircleProgress,
calculateCircleViewBox,
} from '@utils/calculateCircleProgress';
+import { useEffect, useRef } from 'react';
type GraphProps = {
backgroundColor: string; // 배경 원의 색상
@@ -26,6 +27,8 @@ export default function CircleGraph({
additionalTextColor,
isTextShown = false,
}: GraphProps) {
+ const circleRef = useRef
(null);
+
const { viewBox, centerX, centerY } = calculateCircleViewBox({
radius,
strokeWidth,
@@ -41,6 +44,22 @@ export default function CircleGraph({
const boxSize = 2 * (radius + strokeWidth);
+ useEffect(() => {
+ if (!circleRef.current) return;
+ if (circleRef.current) {
+ // 초기 애니메이션 효과 설정
+ circleRef.current.style.transition = 'stroke-dashoffset 1s ease-out';
+
+ // 초기에는 가득 채워진 상태에서 시작
+ circleRef.current.style.strokeDashoffset = '100%';
+
+ // 이후 목표 퍼센트에 맞춰 채워지도록 설정
+ requestAnimationFrame(() => {
+ circleRef.current!.style.strokeDashoffset = `${strokeDashoffset}px`;
+ });
+ }
+ }, [strokeDashoffset]);
+
return (
링크 복사
},
+ { label: 'email', component: 이메일로 추가
},
+];
+
+interface OptionDropdownProps {
+ triggerIcon: ReactNode;
+ onSelect: (option: Option) => void;
+}
+
+export default function OptionDropdown({
+ triggerIcon,
+ onSelect,
+}: OptionDropdownProps) {
+ return (
+
+ );
+}
diff --git a/src/components/team/Report.tsx b/src/components/team/Report.tsx
index 0bef75f8..8a798914 100644
--- a/src/components/team/Report.tsx
+++ b/src/components/team/Report.tsx
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
-import { useTeamStore } from '@/src/stores/teamStore';
+import { useTeamStore } from '@/src/stores/useTeamStore';
+import SlideInMotion from '@components/@shared/animation/SlideInMotion';
import Image from 'next/image';
import CircleGraph from './CircleGraph';
@@ -28,8 +29,8 @@ export default function Report() {
return (
);
}
diff --git a/src/components/team/banner/DeleteTeamModal.tsx b/src/components/team/banner/DeleteTeamModal.tsx
index 75faf695..bd3f2159 100644
--- a/src/components/team/banner/DeleteTeamModal.tsx
+++ b/src/components/team/banner/DeleteTeamModal.tsx
@@ -1,5 +1,5 @@
import { deleteGroupById } from '@/src/api/team/teamAPI';
-import { useTeamStore } from '@/src/stores/teamStore';
+import { useTeamStore } from '@/src/stores/useTeamStore';
import Button from '@components/@shared/Button';
import { Modal } from '@components/@shared/Modal';
import { useMutation, useQueryClient } from '@tanstack/react-query';
@@ -76,11 +76,7 @@ export default function DeleteTeamModal({
width={24}
height={24}
/>
- {isAdmin ? (
- 목록을 삭제하시겠어요?
- ) : (
- 권한 없음
- )}
+ {isAdmin ? 팀을 삭제하시겠어요? : 권한 없음}
{isAdmin && (
diff --git a/src/components/team/banner/EditTeamModal.tsx b/src/components/team/banner/EditTeamModal.tsx
index 725e2d71..fc7a8218 100644
--- a/src/components/team/banner/EditTeamModal.tsx
+++ b/src/components/team/banner/EditTeamModal.tsx
@@ -1,6 +1,6 @@
import { postImage } from '@/src/api/imageAPI';
import { patchGroupById } from '@/src/api/team/teamAPI';
-import { useTeamStore } from '@/src/stores/teamStore';
+import { useTeamStore } from '@/src/stores/useTeamStore';
import Image from 'next/image';
import Button from '@components/@shared/Button';
import { Input } from '@components/@shared/Input';
@@ -90,6 +90,7 @@ export default function EditTeamModal({ isOpen, onClose }: EditTeamModalProps) {
validateValueOnSubmit('teamName', TeamNames, localTeamName, teamName)
) {
editGroup({ groupId: id, image: imgUrl, name: localTeamName });
+ setTeamName(localTeamName); // 최종적으로 store에 반영
}
},
onSettled: () => {
@@ -106,13 +107,16 @@ export default function EditTeamModal({ isOpen, onClose }: EditTeamModalProps) {
if (!isAdmin) {
return;
}
- setTeamName(localTeamName); // 최종적으로 store에 반영
- if (imageFile) {
- // 새로 선택된 파일이 있으면 업로드
- uploadImageMutate.mutate(imageFile);
- } else {
- // 파일이 없으면 기존 URL로 그룹 수정
- editGroup({ groupId: id, image: imageUrl, name: localTeamName });
+
+ if (validateValueOnSubmit('teamName', TeamNames, localTeamName, teamName)) {
+ if (imageFile) {
+ // 새로 선택된 파일이 있으면 업로드
+ uploadImageMutate.mutate(imageFile);
+ } else {
+ // 파일이 없으면 기존 URL로 그룹 수정
+ editGroup({ groupId: id, image: imageUrl, name: localTeamName });
+ setTeamName(localTeamName); // 최종적으로 store에 반영
+ }
}
};
diff --git a/src/components/team/banner/TeamBanner.tsx b/src/components/team/banner/TeamBanner.tsx
index fc369a21..166f3f81 100644
--- a/src/components/team/banner/TeamBanner.tsx
+++ b/src/components/team/banner/TeamBanner.tsx
@@ -1,7 +1,8 @@
import { useModal } from '@hooks/useModal';
import { Option } from '@components/@shared/Dropdown';
-import { useTeamStore } from '@/src/stores/teamStore';
+import { useTeamStore } from '@/src/stores/useTeamStore';
import Image from 'next/image';
+import SlideInMotion from '@components/@shared/animation/SlideInMotion';
import EditDropdown from '../EditDropdown';
import EditTeamModal from './EditTeamModal';
import DeleteTeamModal from './DeleteTeamModal';
@@ -40,7 +41,7 @@ export default function TeamBanner() {
} // '삭제하기'를 선택했을 때
};
return (
-
+
+
);
}
diff --git a/src/components/team/member/EmailInvitationModal.tsx b/src/components/team/member/EmailInvitationModal.tsx
new file mode 100644
index 00000000..64b3667f
--- /dev/null
+++ b/src/components/team/member/EmailInvitationModal.tsx
@@ -0,0 +1,134 @@
+import Button from '@components/@shared/Button';
+import { Modal } from '@components/@shared/Modal';
+import { inviteMember } from '@/src/api/team/memberAPI';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { useTeamStore } from '@/src/stores/useTeamStore';
+import Snackbar from '@components/article/Snackbar';
+import SuccessIcon from 'public/icons/successicon.svg';
+import FailIcon from 'public/icons/failIcon.svg';
+import { useState } from 'react';
+import { Input } from '@components/@shared/Input';
+import useClipboardCopy from '@hooks/useClipBoardCopy';
+import { postAcceptInvitation } from '@/src/api/addteam/participateTeamAPI';
+
+interface EmailInvitationModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+export default function EmailInvitationModal({
+ isOpen,
+ onClose,
+}: EmailInvitationModalProps) {
+ const { showSnackbar, isSnackBarOpen, snackBarMessage, snackBarType } =
+ useClipboardCopy();
+ const [emailValue, setEmailValue] = useState('');
+ const [isError, setIsError] = useState(false);
+ const [icon, SetIcon] = useState(
);
+ const [errorMessage, setErrorMessage] = useState('');
+ const queryClient = useQueryClient();
+
+ const { id, members } = useTeamStore();
+
+ const { data: teamToken } = useQuery({
+ queryKey: ['inviteMember', id],
+ queryFn: () => inviteMember(Number(id)),
+ });
+
+ // 그룹 내에 존재하는 팀원인지 판별
+ const isAlreadyMember =
+ members && members.find((member) => member.userEmail === emailValue);
+
+ // 그룹 수정 Mutation
+ const inviteMemberByEmail = useMutation({
+ mutationFn: ({ userEmail, token }: { userEmail: string; token: string }) =>
+ postAcceptInvitation(userEmail, token),
+ onSuccess: () => {
+ onClose();
+ setEmailValue('');
+ SetIcon(
);
+ showSnackbar('멤버 초대 성공!', 'success');
+ },
+ onSettled: () => {
+ // 쿼리 무효화 및 리패치
+ queryClient.invalidateQueries({ queryKey: ['group', id] });
+ },
+ onError: (error: any) => {
+ if (error.response?.data?.message === '유저가 존재하지 않습니다.') {
+ setIsError(true);
+ setErrorMessage('존재하지 않는 유저입니다.');
+ SetIcon(
);
+ showSnackbar('멤버 초대 실패', 'error');
+ } else {
+ SetIcon(
);
+ showSnackbar('멤버 초대 실패', 'error');
+ setEmailValue('');
+ }
+ },
+ });
+
+ const handleChange = (e: React.ChangeEvent
) => {
+ const { value } = e.target;
+ setIsError(false);
+ setEmailValue(value);
+ };
+
+ const handleInviteClick = () => {
+ if (isAlreadyMember) {
+ setIsError(true);
+ setErrorMessage('이미 그룹에 소속된 유저입니다.');
+ SetIcon();
+ showSnackbar('멤버 초대 실패', 'error');
+ }
+ if (teamToken && emailValue !== '') {
+ inviteMemberByEmail.mutate({ userEmail: emailValue, token: teamToken });
+ }
+ };
+
+ const handleClose = () => {
+ onClose();
+ setEmailValue('');
+ setIsError(false);
+ };
+
+ return (
+ <>
+
+
+ 멤버 초대
+
+ 이메일을 입력해 멤버를 초대합니다.
+
+
+
+
+
+ {isSnackBarOpen && (
+
+ )}
+ >
+ );
+}
diff --git a/src/components/team/member/ExileDropdown.tsx b/src/components/team/member/ExileDropdown.tsx
index 37ae749e..a8438a4d 100644
--- a/src/components/team/member/ExileDropdown.tsx
+++ b/src/components/team/member/ExileDropdown.tsx
@@ -3,11 +3,21 @@ import Image from 'next/image';
interface ExileDropdownProps {
onSelect: () => void;
+ isSelf?: boolean;
}
-export default function ExileDropdown({ onSelect }: ExileDropdownProps) {
+export default function ExileDropdown({
+ onSelect,
+ isSelf = false,
+}: ExileDropdownProps) {
const Option = [
- { component: 추방하기
},
+ {
+ component: (
+
+ {isSelf ? '탈퇴하기' : '추방하기'}
+
+ ),
+ },
];
const moreIcon = (
diff --git a/src/components/team/member/ExileUserModal.tsx b/src/components/team/member/ExileUserModal.tsx
index e2ac7d63..fbc8a33e 100644
--- a/src/components/team/member/ExileUserModal.tsx
+++ b/src/components/team/member/ExileUserModal.tsx
@@ -1,15 +1,18 @@
import { deleteMemberById } from '@/src/api/team/memberAPI';
-import { useTeamStore } from '@/src/stores/teamStore';
+import { useTeamStore } from '@/src/stores/useTeamStore';
import Button from '@components/@shared/Button';
import { Modal } from '@components/@shared/Modal';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import Image from 'next/image';
+import { useRouter } from 'next/router';
interface ExileUserModalProps {
isOpen: boolean;
onClose: () => void;
memberName: string;
userId: number;
+ role: string;
+ isSelf?: boolean;
}
interface Group {
@@ -47,13 +50,12 @@ export default function ExileUserModal({
onClose,
memberName,
userId,
+ role,
+ isSelf = false,
}: ExileUserModalProps) {
const { id } = useTeamStore();
+ const router = useRouter();
const queryClient = useQueryClient();
-
- // 'user' 키로 캐싱된 유저 데이터 가져오기
- const userData = queryClient.getQueryData(['user']);
-
// 멤버 삭제 Mutation
const { mutate: deleteMember } = useMutation({
mutationFn: ({ groupid, userid }: { groupid: string; userid: number }) =>
@@ -61,6 +63,9 @@ export default function ExileUserModal({
onSuccess: () => {
console.log('멤버 정보 삭제 완료!');
onClose();
+ if (role !== 'ADMIN' && isSelf) {
+ router.push('/myteam');
+ }
},
onSettled: () => {
// 쿼리 무효화 및 리패치
@@ -71,22 +76,14 @@ export default function ExileUserModal({
},
});
- // 팀 관리자만 삭제 가능
- const isAdmin =
- userData &&
- userData.memberships.find((m) => m.groupId === Number(id))?.role ===
- 'ADMIN';
- // 본인 삭제 불가 체크
- const isSelf = userData && userData.id === userId;
-
const handleDeleteClick = () => {
// 팀 관리자만 삭제 가능
- if (!isAdmin) {
+ if (role !== 'ADMIN' && !isSelf) {
return;
}
- // 본인 삭제 불가 체크
- if (isSelf) {
+ // 관리자 본인 삭제 불가 체크
+ if (role === 'ADMIN' && isSelf) {
return;
}
@@ -116,22 +113,30 @@ export default function ExileUserModal({
width={24}
height={24}
/>
- {isAdmin && !isSelf && (
+ {role === 'ADMIN' && !isSelf && (
{memberName} 유저를 삭제하시겠어요?
)}
- {!isAdmin && 권한 없음}
+ {role !== 'ADMIN' && isSelf && 팀에서 탈퇴하시겠어요?}
+ {role !== 'ADMIN' && !isSelf && 권한 없음}
- {isAdmin && !isSelf && (
+ {role === 'ADMIN' && !isSelf && (
팀 내에서 멤버를 삭제합니다. 정말로 진행하시겠습니까?
)}
- {!isAdmin && (
+ {role !== 'ADMIN' && isSelf && (
+
+ 팀에서 탈퇴합니다. 정말로 진행하시겠습니까?
+
+ )}
+ {role !== 'ADMIN' && !isSelf && (
관리자만 삭제할 수 있습니다.
)}
- {isAdmin && isSelf && (
- 본인은 삭제할 수 없습니다.
+ {role === 'ADMIN' && isSelf && (
+
+ 관리자는 탈퇴할 수 없습니다. 팀을 삭제해주세요.
+
)}
@@ -145,11 +150,16 @@ export default function ExileUserModal({
>
취소
- {isAdmin && !isSelf && (
+ {role === 'ADMIN' && !isSelf && (
)}
+ {role !== 'ADMIN' && isSelf && (
+
+ )}
diff --git a/src/components/team/member/GetUserDetailModal.tsx b/src/components/team/member/GetUserDetailModal.tsx
index ff1cc18f..5bd8e12c 100644
--- a/src/components/team/member/GetUserDetailModal.tsx
+++ b/src/components/team/member/GetUserDetailModal.tsx
@@ -2,6 +2,9 @@ import Button from '@components/@shared/Button';
import { Modal } from '@components/@shared/Modal';
import { useModal } from '@hooks/useModal';
import Image from 'next/image';
+import useClipboardCopy from '@hooks/useClipBoardCopy';
+import Snackbar from '@components/article/Snackbar';
+import SuccessIcon from 'public/icons/successicon.svg';
import ProfileImageModal from './ProfileImageModal';
interface GetUserDetailModalProps {
@@ -21,76 +24,77 @@ export default function GetUserDetailModal({
img,
role,
}: GetUserDetailModalProps) {
+ const { isSnackBarOpen, snackBarMessage, snackBarType, handleCopyClick } =
+ useClipboardCopy();
+
const {
isOpen: IsProfileModalOpen,
onClose: ProfileModalClose,
onOpen: ProfileModalOpen,
} = useModal();
- const handleCopyClick = (text: string) => {
- navigator.clipboard
- .writeText(text)
- .then(() => {
- console.log('복사한 이메일: ', email);
- })
- .catch((err) => {
- console.error('복사에 실패했습니다!: ', err);
- });
- onClose();
- };
return (
-
-
-
-
-
-
-
-
-
- {role === 'ADMIN' && (
-
- )}
- {name}
-
-
{email}
+ <>
+
+
+
+
+
+
+
+
+
+ {role === 'ADMIN' && (
+
+ )}
+ {name}
+
+
{email}
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {isSnackBarOpen && (
+
}
+ message={snackBarMessage}
+ type={snackBarType}
+ />
+ )}
+ >
);
}
diff --git a/src/components/team/member/HowToModal.tsx b/src/components/team/member/HowToModal.tsx
new file mode 100644
index 00000000..c8158928
--- /dev/null
+++ b/src/components/team/member/HowToModal.tsx
@@ -0,0 +1,39 @@
+import { Modal } from '@components/@shared/Modal';
+import Image from 'next/image';
+
+interface HowToModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+export default function HowToModal({ isOpen, onClose }: HowToModalProps) {
+ return (
+
+
+
+
+
+
+ 관리자의 링크를 받아서 팀 참여하기 페이지에 입력!
+
+
+
+ );
+}
diff --git a/src/components/team/member/InvitationModal.tsx b/src/components/team/member/InvitationModal.tsx
index 0ef2bb27..1565af89 100644
--- a/src/components/team/member/InvitationModal.tsx
+++ b/src/components/team/member/InvitationModal.tsx
@@ -2,7 +2,13 @@ import Button from '@components/@shared/Button';
import { Modal } from '@components/@shared/Modal';
import { inviteMember } from '@/src/api/team/memberAPI';
import { useQuery } from '@tanstack/react-query';
-import { useTeamStore } from '@/src/stores/teamStore';
+import { useTeamStore } from '@/src/stores/useTeamStore';
+import Snackbar from '@components/article/Snackbar';
+import SuccessIcon from 'public/icons/successicon.svg';
+import useClipboardCopy from '@hooks/useClipBoardCopy';
+import { useModal } from '@hooks/useModal';
+import Image from 'next/image';
+import HowToModal from './HowToModal';
interface InvitationModalProps {
isOpen: boolean;
@@ -13,48 +19,76 @@ export default function InvitationModal({
isOpen,
onClose,
}: InvitationModalProps) {
+ const {
+ isOpen: IsHowToModalOpen,
+ onClose: HowToModalClose,
+ onOpen: HowToModalOpen,
+ } = useModal();
+
+ const { isSnackBarOpen, snackBarMessage, snackBarType, handleCopyClick } =
+ useClipboardCopy();
+
const { id } = useTeamStore();
- const { data } = useQuery({
+ const { data: token } = useQuery({
queryKey: ['inviteMember', id],
queryFn: () => inviteMember(Number(id)),
});
- const handleCopyClick = (text: string) => {
- navigator.clipboard
- .writeText(text)
- .then(() => {
- console.log('복사한 토큰: ', data);
- })
- .catch((err) => {
- console.error('복사에 실패했습니다!: ', err);
- });
- onClose();
- };
-
return (
-
-
- 멤버 초대
-
- 그룹에 참여할 수 있는 링크를 복사합니다.
-
-
-
-
-
+
+ {isSnackBarOpen && (
+
}
+ message={snackBarMessage}
+ type={snackBarType}
+ />
+ )}
+ >
);
}
diff --git a/src/components/team/member/MemberBox.tsx b/src/components/team/member/MemberBox.tsx
index f2049974..fef543ef 100644
--- a/src/components/team/member/MemberBox.tsx
+++ b/src/components/team/member/MemberBox.tsx
@@ -1,8 +1,10 @@
import { useModal } from '@hooks/useModal';
import Image from 'next/image';
+import { useQueryClient } from '@tanstack/react-query';
+import SlideItemsMotion from '@components/@shared/animation/SlideItemsMotion';
import GetUserDetailModal from './GetUserDetailModal';
import ExileDropdown from './ExileDropdown';
-import ExileUserModal from './ExileUserModal';
+import ExileUserModal, { UserData } from './ExileUserModal';
export interface MemberProps {
role: string;
@@ -10,6 +12,7 @@ export interface MemberProps {
userEmail: string;
userName: string;
userId: number;
+ index: number;
}
export default function MemberBox({
@@ -18,6 +21,7 @@ export default function MemberBox({
userImage,
role,
userId,
+ index,
}: MemberProps) {
const { isOpen, onOpen, onClose } = useModal();
const {
@@ -26,6 +30,14 @@ export default function MemberBox({
onClose: deleteCloseModal,
} = useModal();
+ const queryClient = useQueryClient();
+
+ // 'user' 키로 캐싱된 유저 데이터 가져오기
+ const userData = queryClient.getQueryData
(['user']);
+
+ // 본인인가?
+ const isSelf = userData && userData.id === userId;
+
const handleDropdownClick = (e: React.MouseEvent) => {
e.stopPropagation(); // 클릭 이벤트 전파 방지
};
@@ -35,12 +47,13 @@ export default function MemberBox({
};
return (
<>
-
-
-
+
+
-
+
{role === 'ADMIN' && (
-
+
-
+
>
);
diff --git a/src/components/team/member/MemberList.tsx b/src/components/team/member/MemberList.tsx
index 18fdfe62..58704aa3 100644
--- a/src/components/team/member/MemberList.tsx
+++ b/src/components/team/member/MemberList.tsx
@@ -1,38 +1,69 @@
import { useModal } from '@hooks/useModal';
-import { useTeamStore } from '@/src/stores/teamStore';
+import { useTeamStore } from '@/src/stores/useTeamStore';
+import { Option } from '@components/@shared/Dropdown';
import MemberBox from './MemberBox';
import InvitationModal from './InvitationModal';
import styles from '../../../styles/scroll.module.css';
+import OptionDropdown from '../OptionDropdown';
+import EmailInvitationModal from './EmailInvitationModal';
export default function MemberList() {
- const { isOpen, onOpen, onClose } = useModal();
+ const {
+ isOpen: tokenIsOpen,
+ onOpen: tokenOpenModal,
+ onClose: tokenCloseModal,
+ } = useModal();
+
+ const {
+ isOpen: emailIsOpen,
+ onOpen: emailOpenModal,
+ onClose: emailCloseModal,
+ } = useModal();
const { members } = useTeamStore();
const memberCount = members.length;
+
+ // 드롭다운에서 선택된 옵션을 처리하는 함수
+ const handleSelect = (option: Option) => {
+ if (option.label === 'token') {
+ tokenOpenModal(); // '수정하기'를 선택했을 때
+ } else {
+ emailOpenModal();
+ } // '삭제하기'를 선택했을 때
+ };
+
+ const InviteButton = (
+
+ +새로운 멤버 초대하기
+
+ );
+
return (
-
+
-
- +새로운 멤버 초대하기
-
-
+
+
+
-
- {members.map((member) => (
+
+ {members.map((member, index) => (
))}
-
+
);
diff --git a/src/components/team/myteam/EmptyTeamPage.tsx b/src/components/team/myteam/EmptyTeamPage.tsx
index 60142a39..cc52441b 100644
--- a/src/components/team/myteam/EmptyTeamPage.tsx
+++ b/src/components/team/myteam/EmptyTeamPage.tsx
@@ -1,17 +1,20 @@
import Button from '@components/@shared/Button';
+import Image from 'next/image';
import Link from 'next/link';
export default function EmptyTeamPage() {
return (
-
- 아직 소속된 팀이 없습니다.
+ 아직 소속된 팀이 없습니다.
팀을 생성하거나 팀에 참여해보세요.
diff --git a/src/components/team/myteam/TeamBox.tsx b/src/components/team/myteam/TeamBox.tsx
index 55b25591..a49d9ce5 100644
--- a/src/components/team/myteam/TeamBox.tsx
+++ b/src/components/team/myteam/TeamBox.tsx
@@ -1,11 +1,14 @@
import getTimeAgo from '@utils/getTimeAgo';
+import Image from 'next/image';
import Link from 'next/link';
+import SlideItemsMotion from '@components/@shared/animation/SlideItemsMotion';
interface TeamBoxProps {
teamName: string;
image: string;
groupId: number;
updatedAt: string;
+ index: number;
}
export default function TeamBox({
@@ -13,18 +16,25 @@ export default function TeamBox({
image,
groupId,
updatedAt,
+ index,
}: TeamBoxProps) {
const lastActiveAt = getTimeAgo(updatedAt);
return (
-
+
-
-
![]()
+
+
{teamName}
@@ -33,8 +43,8 @@ export default function TeamBox({
최근 활동 : {lastActiveAt}
-
+
-
+
);
}
diff --git a/src/components/team/taskList/AddTaskListModal.tsx b/src/components/team/taskList/AddTaskListModal.tsx
index 714513b2..0f295143 100644
--- a/src/components/team/taskList/AddTaskListModal.tsx
+++ b/src/components/team/taskList/AddTaskListModal.tsx
@@ -4,7 +4,8 @@ import { Input } from '@components/@shared/Input';
import { Modal } from '@components/@shared/Modal';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
-import { TeamStore } from '@/src/stores/teamStore';
+import useTaskListStorage from '@hooks/team/useTaskListStorage';
+import { TeamStore } from '@/src/stores/useTeamStore';
import { useValidation } from '@hooks/useValidation';
import { tagColors } from './tagColors';
@@ -19,6 +20,7 @@ export default function AddTaskListModal({
onClose,
groupId,
}: AddTaskListModalProps) {
+ const { updateTaskList } = useTaskListStorage(groupId);
const [taskListName, setTaskListName] = useState('');
const newGroupId = Number(groupId);
const queryClient = useQueryClient();
@@ -78,50 +80,7 @@ export default function AddTaskListModal({
if (validateValueOnSubmit('taskListName', taskListNames, taskListName)) {
createTaskList({ id: newGroupId, name: taskListName });
- const taskListData = {
- name: taskListName,
- color: selectedColor,
- };
- // 로컬 스토리지에서 기존 TaskLists 가져오기 (없으면 빈 배열)
- const existingTaskListsString = localStorage.getItem(
- `TaskLists_${newGroupId}`
- );
-
- let existingTaskLists = [];
- if (existingTaskListsString) {
- try {
- existingTaskLists = JSON.parse(existingTaskListsString);
-
- // JSON 파싱 후 배열인지 확인
- if (!Array.isArray(existingTaskLists)) {
- existingTaskLists = []; // 배열이 아닐 경우 빈 배열로 초기화
- }
- } catch (error) {
- console.error(
- '로컬 스토리지에서 TaskLists를 파싱하는 중 오류 발생:',
- error
- );
- existingTaskLists = []; // 파싱 오류 발생 시 빈 배열로 초기화
- }
- }
-
- // 기존 이름과 겹치는지 확인
- const existingTaskIndex = existingTaskLists.findIndex(
- (task) => task.name === taskListData.name
- );
-
- // 기존 이름이 있는 경우 색상 업데이트, 없으면 새로 추가
- if (existingTaskIndex !== -1) {
- existingTaskLists[existingTaskIndex].color = taskListData.color;
- } else {
- existingTaskLists.push(taskListData);
- }
-
- // 업데이트된 배열을 로컬 스토리지에 저장
- localStorage.setItem(
- `TaskLists_${newGroupId}`,
- JSON.stringify(existingTaskLists)
- );
+ updateTaskList({ name: taskListName, color: selectedColor });
}
};
diff --git a/src/components/team/taskList/DeleteTaskListModal.tsx b/src/components/team/taskList/DeleteTaskListModal.tsx
index 0a31f241..c8fe7cc4 100644
--- a/src/components/team/taskList/DeleteTaskListModal.tsx
+++ b/src/components/team/taskList/DeleteTaskListModal.tsx
@@ -1,9 +1,10 @@
import { deleteTaskList } from '@/src/api/tasks/taskListAPI';
-import { useTeamStore } from '@/src/stores/teamStore';
+import { useTeamStore } from '@/src/stores/useTeamStore';
import Button from '@components/@shared/Button';
import { Modal } from '@components/@shared/Modal';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import Image from 'next/image';
+import useTaskListStorage from '@hooks/team/useTaskListStorage';
import { UserData } from '../member/ExileUserModal';
interface DeleteTaskListModalProps {
@@ -21,6 +22,7 @@ export default function DeleteTaskListModal({
}: DeleteTaskListModalProps) {
const queryClient = useQueryClient();
const { id } = useTeamStore();
+ const { deleteStoredTaskList } = useTaskListStorage(id);
// 'user' 키로 캐싱된 유저 데이터 가져오기
const userData = queryClient.getQueryData(['user']);
@@ -52,34 +54,7 @@ export default function DeleteTaskListModal({
}
deleteList(String(taskListId));
- // 로컬 스토리지에서 기존 TaskLists 가져오기 (없으면 빈 배열)
- const existingTaskListsString = localStorage.getItem(`TaskLists_${id}`);
-
- let existingTaskLists = [];
- if (existingTaskListsString) {
- try {
- existingTaskLists = JSON.parse(existingTaskListsString);
-
- // JSON 파싱 후 배열인지 확인
- if (!Array.isArray(existingTaskLists)) {
- existingTaskLists = []; // 배열이 아닐 경우 빈 배열로 초기화
- }
- } catch (error) {
- console.error(
- '로컬 스토리지에서 TaskLists를 파싱하는 중 오류 발생:',
- error
- );
- existingTaskLists = []; // 파싱 오류 발생 시 빈 배열로 초기화
- }
- }
-
- // 삭제할 항목의 name을 가진 요소 필터링
- const updatedTaskLists = existingTaskLists.filter(
- (task) => task.name !== taskName
- );
-
- // 업데이트된 배열을 로컬 스토리지에 저장
- localStorage.setItem(`TaskLists_${id}`, JSON.stringify(updatedTaskLists));
+ deleteStoredTaskList(taskName);
};
return (
diff --git a/src/components/team/taskList/EditTaskListModal.tsx b/src/components/team/taskList/EditTaskListModal.tsx
index ebca69f2..0e05febc 100644
--- a/src/components/team/taskList/EditTaskListModal.tsx
+++ b/src/components/team/taskList/EditTaskListModal.tsx
@@ -1,11 +1,12 @@
import { patchTaskList } from '@/src/api/tasks/taskListAPI';
-import { TeamStore, useTeamStore } from '@/src/stores/teamStore';
+import { TeamStore, useTeamStore } from '@/src/stores/useTeamStore';
import Button from '@components/@shared/Button';
import { Input } from '@components/@shared/Input';
import { Modal } from '@components/@shared/Modal';
import { useValidation } from '@hooks/useValidation';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
+import useTaskListStorage from '@hooks/team/useTaskListStorage';
import { tagColors } from './tagColors';
interface EditTaskListModalProps {
@@ -26,9 +27,10 @@ export default function EditTaskListModal({
initialTaskListName = '',
taskListId,
}: EditTaskListModalProps) {
- const [TaskListName, setTaskListName] = useState(initialTaskListName);
+ const [taskListName, setTaskListName] = useState(initialTaskListName);
const { id } = useTeamStore();
const queryClient = useQueryClient();
+ const { updateTaskList } = useTaskListStorage(id);
const {
errors,
@@ -49,7 +51,7 @@ export default function EditTaskListModal({
// onBlur 시 이름이 비어 있는지 검사
const handleBlurName = () => {
- validateOnBlur('taskListName', TaskListName);
+ validateOnBlur('taskListName', taskListName);
};
const handleChange = (e: React.ChangeEvent) => {
@@ -102,6 +104,11 @@ export default function EditTaskListModal({
},
onSettled: () => {
+ // 로컬 스토리지 업데이트
+ localStorage.setItem('taskListUpdated', Date.now().toString());
+
+ // CustomEvent 트리거
+ window.dispatchEvent(new Event('taskListUpdate'));
// 쿼리 무효화 및 리패치
queryClient.invalidateQueries({ queryKey: ['group', id] });
},
@@ -119,58 +126,16 @@ export default function EditTaskListModal({
validateValueOnSubmit(
'taskListName',
taskListNames,
- TaskListName,
+ taskListName,
initialTaskListName
)
) {
editGroup({
groupId: Number(id),
listId: String(taskListId),
- name: TaskListName,
+ name: taskListName,
});
-
- const taskListData = {
- name: TaskListName,
- color: selectedColor,
- };
- // 로컬 스토리지에서 기존 TaskLists 가져오기 (없으면 빈 배열)
- const existingTaskListsString = localStorage.getItem(`TaskLists_${id}`);
-
- let existingTaskLists = [];
- if (existingTaskListsString) {
- try {
- existingTaskLists = JSON.parse(existingTaskListsString);
-
- // JSON 파싱 후 배열인지 확인
- if (!Array.isArray(existingTaskLists)) {
- existingTaskLists = []; // 배열이 아닐 경우 빈 배열로 초기화
- }
- } catch (error) {
- console.error(
- '로컬 스토리지에서 TaskLists를 파싱하는 중 오류 발생:',
- error
- );
- existingTaskLists = []; // 파싱 오류 발생 시 빈 배열로 초기화
- }
- }
-
- // 기존과 동일한 name이 있는지 확인
- const existingTaskIndex = existingTaskLists.findIndex(
- (task) => task.name === TaskListName
- );
-
- // 동일한 name이 있으면 color만 업데이트, 없으면 새로 추가
- if (existingTaskIndex !== -1) {
- existingTaskLists[existingTaskIndex].color = selectedColor;
- } else {
- existingTaskLists.push(taskListData);
- }
-
- // 업데이트된 배열을 로컬 스토리지에 저장
- localStorage.setItem(
- `TaskLists_${id}`,
- JSON.stringify(existingTaskLists)
- );
+ updateTaskList({ name: taskListName, color: selectedColor });
}
};
@@ -199,7 +164,7 @@ export default function EditTaskListModal({
{
+ return JSON.parse(localStorage.getItem(`TaskLists_${groupId}`) || '[]');
+ });
+
+ useEffect(() => {
+ const handleTaskListUpdate = () => {
+ const updatedTaskLists = JSON.parse(
+ localStorage.getItem(`TaskLists_${groupId}`) || '[]'
+ );
+ setStoredTaskLists(updatedTaskLists);
+ };
+
+ // 커스텀 이벤트 리스너 추가
+ window.addEventListener('taskListUpdate', handleTaskListUpdate);
+
+ // 컴포넌트 언마운트 시 리스너 제거
+ return () => {
+ window.removeEventListener('taskListUpdate', handleTaskListUpdate);
+ };
+ }, [groupId]);
useEffect(() => {
const pointColors = [
@@ -48,11 +76,6 @@ export default function TaskBar({ name, tasks, id, isDragging }: TaskBarProps) {
'#ff9d35', // orange
'#E6FE0B', // yellow
];
- // 로컬 스토리지에서 taskLists 가져오기
- const storedTaskLists = JSON.parse(
- localStorage.getItem(`TaskLists_${groupId}`) || '[]'
- );
-
// 기존 taskList에 색상이 없으면 순서에 맞는 색상 할당
let currentTaskList = storedTaskLists.find(
(taskList: StoredTaskList) => taskList.name === name
@@ -60,10 +83,10 @@ export default function TaskBar({ name, tasks, id, isDragging }: TaskBarProps) {
if (!currentTaskList) {
// 새로운 taskList일 경우 순서에 따라 색상 할당
- const colorIndex = storedTaskLists.length % pointColors.length;
+ const colorIndex = id % pointColors.length;
const newColor = pointColors[colorIndex];
currentTaskList = { name, color: newColor };
-
+ setStoredTaskLists([...storedTaskLists, currentTaskList]);
// taskList 저장
localStorage.setItem(
`TaskLists_${groupId}`,
@@ -73,59 +96,61 @@ export default function TaskBar({ name, tasks, id, isDragging }: TaskBarProps) {
// 설정된 색상 사용
setCurrentTagColor(currentTaskList.color);
- }, [groupId, name]);
+ }, [groupId, name, id, storedTaskLists, taskLists]);
return (
-
-
+
-
-
-
-
-
- {doneRate === 100 ? (
-
- ) : (
-
- )}
-
- {doneTasksCount}/{totalTasks}
-
+
+
+
+ {doneRate === 100 ? (
+
+ ) : (
+
+ )}
+
+ {doneTasksCount}/{totalTasks}
+
+
-
-
-
+
+
+
);
}
diff --git a/src/components/team/taskList/TaskList.tsx b/src/components/team/taskList/TaskList.tsx
index 3ca3f960..6fb932c5 100644
--- a/src/components/team/taskList/TaskList.tsx
+++ b/src/components/team/taskList/TaskList.tsx
@@ -1,5 +1,5 @@
import { useModal } from '@hooks/useModal';
-import { useTeamStore } from '@/src/stores/teamStore';
+import { useTeamStore } from '@/src/stores/useTeamStore';
import { Option } from '@components/@shared/Dropdown';
import { useCallback, useEffect, useRef, useState } from 'react';
import {
@@ -21,6 +21,7 @@ import {
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { patchTaskListOrder } from '@/src/api/tasks/taskListAPI';
import styles from '@styles/scroll.module.css';
+import Image from 'next/image';
import TaskBar from './TaskBar';
import AddTaskListModal from './AddTaskListModal';
import EditDropdown from '../EditDropdown';
@@ -29,6 +30,8 @@ import DeleteTaskListModal from './DeleteTaskListModal';
import { moreIcon } from '../MoreIcon';
export default function TaskList() {
+ const [isVisible, setIsVisible] = useState(false);
+
const scrollableRef = useRef
(null);
const {
@@ -202,18 +205,46 @@ export default function TaskList() {
};
}, [handleTouchStart]);
+ const handleVisibleClick = () => {
+ if (isVisible === false) {
+ setIsVisible(true);
+ } else {
+ setIsVisible(false);
+ }
+ };
+
return (
-
-
+
+
할 일 목록
({listCount}개)
+
+ {isVisible && (
+ <>
+
+ 할 일 목록을 꾹 눌러 드래그해서 순서를 편집할 수 있어요!
+
+
+ 할 일 목록을 꾹 눌러 드래그해서
순서를 편집할 수
+ 있어요!
+
+ >
+ )}
+새로운 목록 추가하기
@@ -248,7 +279,7 @@ export default function TaskList() {
className="flex flex-col gap-[10px]"
onMouseDown={(e) => e.stopPropagation()}
>
- {displayedTaskList.map((taskList) => (
+ {displayedTaskList.map((taskList, index) => (
{
@@ -262,6 +293,7 @@ export default function TaskList() {
/>
{
return useMutation({
mutationFn: fetchArticleDelete,
- onSuccess: () => {
- console.log('게시글 삭제 완료');
- },
- onError: (error) => {
- console.log('게시글 삭제 실패', error);
- },
});
};
diff --git a/src/hooks/article/useCommentAdd.tsx b/src/hooks/article/useCommentAdd.tsx
index 262d96b5..1ff9ee85 100644
--- a/src/hooks/article/useCommentAdd.tsx
+++ b/src/hooks/article/useCommentAdd.tsx
@@ -28,12 +28,6 @@ export const useCommentAdd = () => {
articleId: number;
content: string;
}) => fetchCommentadd({ articleId, content }),
- onSuccess: () => {
- console.log('게시글등록 완료 ');
- },
- onError: (error) => {
- console.error('게시글 등록안됨', error);
- },
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['CommentCards'] });
queryClient.invalidateQueries({ queryKey: ['DetailCard'] });
diff --git a/src/hooks/article/useCommentDelete.tsx b/src/hooks/article/useCommentDelete.tsx
index f953b175..b0375899 100644
--- a/src/hooks/article/useCommentDelete.tsx
+++ b/src/hooks/article/useCommentDelete.tsx
@@ -5,12 +5,6 @@ export const useCommentDelete = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: fetchCommentDelete,
- onSuccess: () => {
- console.log('게시글 삭제 완료');
- },
- onError: (error) => {
- console.error('게시글 삭제 실패', error);
- },
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['CommentCards'] });
queryClient.invalidateQueries({ queryKey: ['DetailCard'] });
diff --git a/src/hooks/article/useCommentEdit.tsx b/src/hooks/article/useCommentEdit.tsx
index f26c1afa..e24e8f14 100644
--- a/src/hooks/article/useCommentEdit.tsx
+++ b/src/hooks/article/useCommentEdit.tsx
@@ -5,12 +5,6 @@ export const useCommentEdit = () => {
const QueryClient = useQueryClient();
return useMutation({
mutationFn: fetchCommentPatch,
- onSuccess: () => {
- console.log('게시글 수정 완료');
- },
- onError: (error) => {
- console.error('게시글 수정 실패', error);
- },
onSettled: () => {
QueryClient.invalidateQueries({
queryKey: ['CommentCards'],
diff --git a/src/hooks/article/useEditArticle.tsx b/src/hooks/article/useEditArticle.tsx
index 920a2f10..4ebf0dd3 100644
--- a/src/hooks/article/useEditArticle.tsx
+++ b/src/hooks/article/useEditArticle.tsx
@@ -4,11 +4,5 @@ import { useMutation } from '@tanstack/react-query';
export const useEditArticle = () => {
return useMutation({
mutationFn: fetchEditArticle,
- onSuccess: () => {
- console.log('게시글 수정 완료');
- },
- onError: (error) => {
- console.error('게시글 수정 실패', error);
- },
});
};
diff --git a/src/hooks/article/useNewArticle.tsx b/src/hooks/article/useNewArticle.tsx
index b7705b34..dd079d29 100644
--- a/src/hooks/article/useNewArticle.tsx
+++ b/src/hooks/article/useNewArticle.tsx
@@ -4,11 +4,5 @@ import { useMutation } from '@tanstack/react-query';
export const useNewArticle = () => {
return useMutation({
mutationFn: fetchNewArticle,
- onSuccess: () => {
- console.log('게시글 등록 완료');
- },
- onError: (error) => {
- console.error('게시글 등록 실패', error);
- },
});
};
diff --git a/src/hooks/mysetting/useImageURL.ts b/src/hooks/mysetting/useImageURL.ts
index 44fb1d18..1104526e 100644
--- a/src/hooks/mysetting/useImageURL.ts
+++ b/src/hooks/mysetting/useImageURL.ts
@@ -4,11 +4,5 @@ import { useMutation } from '@tanstack/react-query';
export const useImageURL = () => {
return useMutation({
mutationFn: fetchImageURL,
- onSuccess: () => {
- console.log('url 변경 완료');
- },
- onError: (error) => {
- console.error('url변경 안됨', error);
- },
});
};
diff --git a/src/hooks/mysetting/useNicknameChange.ts b/src/hooks/mysetting/useNicknameChange.ts
index 905bc6a6..14975f6c 100644
--- a/src/hooks/mysetting/useNicknameChange.ts
+++ b/src/hooks/mysetting/useNicknameChange.ts
@@ -5,12 +5,6 @@ export const useNicknameChange = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: fetchProfileNickname,
- onSuccess: () => {
- console.log('닉네임 변경!');
- },
- onError: (error) => {
- console.error('닉네임 변경 실패', error);
- },
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['user'] });
},
diff --git a/src/hooks/mysetting/usePasswordChange.ts b/src/hooks/mysetting/usePasswordChange.ts
index 02db2ead..09947b4c 100644
--- a/src/hooks/mysetting/usePasswordChange.ts
+++ b/src/hooks/mysetting/usePasswordChange.ts
@@ -4,11 +4,5 @@ import { useMutation } from '@tanstack/react-query';
export const usePasswordChange = () => {
return useMutation({
mutationFn: fetchChangePassword,
- onSuccess: () => {
- console.log('password 변경!');
- },
- onError: (error) => {
- console.error('password 변경 실패', error);
- },
});
};
diff --git a/src/hooks/mysetting/usePasswordCheck.ts b/src/hooks/mysetting/usePasswordCheck.ts
index 2e05aef6..a70e7fb3 100644
--- a/src/hooks/mysetting/usePasswordCheck.ts
+++ b/src/hooks/mysetting/usePasswordCheck.ts
@@ -4,11 +4,5 @@ import { useMutation } from '@tanstack/react-query';
export const usePasswordCheck = () => {
return useMutation({
mutationFn: fetchPasswordCheck,
- onSuccess: () => {
- console.log('password 일치');
- },
- onError: (error) => {
- console.error('password 불일치', error);
- },
});
};
diff --git a/src/hooks/mysetting/useProfileChange.ts b/src/hooks/mysetting/useProfileChange.ts
index 15fcc824..62d7e7e9 100644
--- a/src/hooks/mysetting/useProfileChange.ts
+++ b/src/hooks/mysetting/useProfileChange.ts
@@ -5,12 +5,7 @@ export const useProfileChange = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: fetchProfileImage,
- onSuccess: () => {
- console.log('프로필 변경!');
- },
- onError: (error) => {
- console.error('프로필 변경 실패', error);
- },
+
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['user'] });
},
diff --git a/src/hooks/mysetting/useUserDelete.ts b/src/hooks/mysetting/useUserDelete.ts
index f4f0ce92..0d63266f 100644
--- a/src/hooks/mysetting/useUserDelete.ts
+++ b/src/hooks/mysetting/useUserDelete.ts
@@ -4,11 +4,5 @@ import { fetchDeletUser } from '@/src/api/mysetting/inputAPI';
export const useUserDelete = () => {
return useMutation({
mutationFn: fetchDeletUser,
- onSuccess: () => {
- console.log('탈퇴성공~!!');
- },
- onError: (error) => {
- console.error('탈퇴 실패:', error);
- },
});
};
diff --git a/src/hooks/team/useAdminCheck.ts b/src/hooks/team/useAdminCheck.ts
new file mode 100644
index 00000000..51089561
--- /dev/null
+++ b/src/hooks/team/useAdminCheck.ts
@@ -0,0 +1,17 @@
+// useAdminCheck.ts
+import { UserData } from '@components/team/member/ExileUserModal';
+import { useQueryClient } from '@tanstack/react-query';
+import { useTeamStore } from '@/src/stores/useTeamStore';
+
+export const useAdminCheck = () => {
+ const { id } = useTeamStore();
+ const queryClient = useQueryClient();
+ const userData = queryClient.getQueryData(['user']);
+
+ const isAdmin =
+ userData &&
+ userData.memberships.find((m) => m.groupId === Number(id))?.role ===
+ 'ADMIN';
+
+ return isAdmin;
+};
diff --git a/src/hooks/team/useTaskListStorage.ts b/src/hooks/team/useTaskListStorage.ts
new file mode 100644
index 00000000..c14002cd
--- /dev/null
+++ b/src/hooks/team/useTaskListStorage.ts
@@ -0,0 +1,52 @@
+import { useState, useEffect } from 'react';
+
+interface StoredTaskList {
+ name: string;
+ color: string;
+}
+
+export default function useTaskListStorage(groupId: string) {
+ const [taskLists, setTaskLists] = useState([]);
+
+ useEffect(() => {
+ const taskListString = localStorage.getItem(`TaskLists_${groupId}`);
+ if (taskListString) {
+ try {
+ const parsedData = JSON.parse(taskListString);
+ setTaskLists(parsedData);
+ } catch (error) {
+ console.error(
+ '로컬 스토리지에서 TaskLists를 파싱하는 중 오류 발생:',
+ error
+ );
+ }
+ }
+ }, [groupId]);
+
+ const updateTaskList = (newTaskList: StoredTaskList) => {
+ setTaskLists((prev) => {
+ const updatedList = [...prev];
+ const index = updatedList.findIndex(
+ (task) => task.name === newTaskList.name
+ );
+ if (index !== -1) {
+ updatedList[index] = newTaskList;
+ } else {
+ updatedList.push(newTaskList);
+ }
+ localStorage.setItem(`TaskLists_${groupId}`, JSON.stringify(updatedList));
+ return updatedList;
+ });
+ };
+
+ // TaskList 삭제 함수
+ const deleteStoredTaskList = (taskName: string) => {
+ setTaskLists((prev) => {
+ const updatedList = prev.filter((task) => task.name !== taskName);
+ localStorage.setItem(`TaskLists_${groupId}`, JSON.stringify(updatedList));
+ return updatedList;
+ });
+ };
+
+ return { taskLists, updateTaskList, deleteStoredTaskList };
+}
diff --git a/src/hooks/team/useTeamValidation.ts b/src/hooks/team/useTeamValidation.ts
new file mode 100644
index 00000000..b9a8fc13
--- /dev/null
+++ b/src/hooks/team/useTeamValidation.ts
@@ -0,0 +1,46 @@
+// useTeamValidation.ts
+
+import { UserData } from '@components/team/member/ExileUserModal';
+import { useValidation } from '@hooks/useValidation';
+import { useQueryClient } from '@tanstack/react-query';
+import { useState } from 'react';
+
+export const useTeamValidation = (initialTeamName: string) => {
+ const queryClient = useQueryClient();
+ const userData = queryClient.getQueryData(['user']);
+ const { errors, setError, clearError, validateValueOnSubmit } =
+ useValidation();
+ const [localTeamName, setLocalTeamName] = useState(initialTeamName);
+
+ // 팀 이름 변경 핸들러
+ const handleTeamNameChange = (e: React.ChangeEvent) => {
+ const { value } = e.target;
+ if (value.length <= 30) {
+ setLocalTeamName(value);
+ clearError('teamName');
+ } else {
+ setError('teamName', true, '30자 이하로 입력해주세요.');
+ }
+ };
+
+ // 기존 팀 이름 목록 가져오기
+ const existingTeamNames =
+ userData?.memberships.map((membership) => membership.group.name) || [];
+
+ // 팀 이름 중복 체크
+ const validateTeamName = () => {
+ if (!validateValueOnSubmit('teamName', existingTeamNames, localTeamName)) {
+ setError('teamName', true, '이미 존재하는 이름입니다.');
+ return false;
+ }
+ return true;
+ };
+
+ return {
+ localTeamName,
+ setLocalTeamName,
+ handleTeamNameChange,
+ validateTeamName,
+ errors,
+ };
+};
diff --git a/src/hooks/useClipBoardCopy.ts b/src/hooks/useClipBoardCopy.ts
new file mode 100644
index 00000000..3e41815a
--- /dev/null
+++ b/src/hooks/useClipBoardCopy.ts
@@ -0,0 +1,41 @@
+import { useState } from 'react';
+
+export default function useClipboardCopy(
+ defaultMessage = '클립보드에 복사 완료!'
+) {
+ const [isSnackBarOpen, setIsSnackBarOpen] = useState(false);
+ const [snackBarMessage, setSnackBarMessage] = useState(defaultMessage);
+ const [snackBarType, setSnackBarType] = useState<'success' | 'error'>(
+ 'success'
+ );
+
+ const showSnackbar = (message: string, type: 'success' | 'error') => {
+ setSnackBarMessage(message);
+ setSnackBarType(type);
+ setIsSnackBarOpen(true);
+
+ setTimeout(() => {
+ setIsSnackBarOpen(false);
+ }, 2000);
+ };
+
+ const handleCopyClick = (text: string, onSuccess?: () => void) => {
+ navigator.clipboard
+ .writeText(text)
+ .then(() => {
+ showSnackbar(defaultMessage, 'success');
+ if (onSuccess) onSuccess();
+ })
+ .catch(() => {
+ showSnackbar('복사에 실패했습니다', 'error');
+ });
+ };
+
+ return {
+ showSnackbar,
+ isSnackBarOpen,
+ snackBarMessage,
+ snackBarType,
+ handleCopyClick,
+ };
+}
diff --git a/src/hooks/useDragAndDrop.ts b/src/hooks/useDragAndDrop.ts
index 3f0c78d0..6f81360f 100644
--- a/src/hooks/useDragAndDrop.ts
+++ b/src/hooks/useDragAndDrop.ts
@@ -2,7 +2,7 @@ import { useState } from 'react';
import { DragEndEvent } from '@dnd-kit/core';
import { arrayMove } from '@dnd-kit/sortable';
import { QueryClient, UseMutationResult } from '@tanstack/react-query';
-import { TaskListProps, useTeamStore } from '../stores/teamStore';
+import { TaskListProps, useTeamStore } from '../stores/useTeamStore';
import { TaskListUrlParams } from '../api/tasks/taskListAPI';
export default function useDragAndDrop(
diff --git a/src/hooks/useImageUpload.ts b/src/hooks/useImageUpload.ts
new file mode 100644
index 00000000..db5fb9b7
--- /dev/null
+++ b/src/hooks/useImageUpload.ts
@@ -0,0 +1,15 @@
+// useImageUpload.ts
+import { useMutation } from '@tanstack/react-query';
+import { postImage } from '@/src/api/imageAPI';
+
+export const useImageUpload = (onSuccess: (imgUrl: string) => void) => {
+ const uploadImageMutate = useMutation({
+ mutationFn: (file: File) => postImage(file),
+ onSuccess,
+ onError: (error) => {
+ console.error('이미지 업로드 실패:', error);
+ },
+ });
+
+ return uploadImageMutate;
+};
diff --git a/src/pages/[teamid]/index.tsx b/src/pages/[teamid]/index.tsx
index b3242601..02e83e73 100644
--- a/src/pages/[teamid]/index.tsx
+++ b/src/pages/[teamid]/index.tsx
@@ -1,11 +1,12 @@
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { getGroupById } from '@/src/api/team/teamAPI';
+import SlideInMotion from '@components/@shared/animation/SlideInMotion';
import TeamBanner from '@components/team/banner/TeamBanner';
import TaskList from '@components/team/taskList/TaskList';
import Report from '@components/team/Report';
import MemberList from '@components/team/member/MemberList';
-import { useTeamStore } from '@/src/stores/teamStore';
+import { useTeamStore } from '@/src/stores/useTeamStore';
import { useEffect } from 'react';
import Button from '@components/@shared/Button';
import LoadingSpinner from '@components/@shared/LoadingSpinner';
@@ -38,17 +39,19 @@ export default function TeamPage() {
- router.push('myteam')}
- >
- 팀 목록으로 돌아가기
-
+
+ router.push('myteam')}
+ >
+ 팀 목록으로 돌아가기
+
+
);
}
diff --git a/src/pages/[teamid]/tasks.tsx b/src/pages/[teamid]/tasks.tsx
index 0189afe1..b1cb6641 100644
--- a/src/pages/[teamid]/tasks.tsx
+++ b/src/pages/[teamid]/tasks.tsx
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { useDate } from '@/src/contexts/DateContext';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
-import { useTaskListStore } from '@/src/stores/taskListStore';
+import { useTaskListStore } from '@/src/stores/useTaskListStore';
import { getTaskLists, getTaskList } from '@/src/api/tasks/taskListAPI';
import TaskDate from '@components/tasks/TaskDate';
import TaskList from '@components/tasks/TaskList';
diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx
index 342e2669..2290df20 100644
--- a/src/pages/_document.tsx
+++ b/src/pages/_document.tsx
@@ -3,7 +3,10 @@ import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
-
+
+
+
+
diff --git a/src/pages/addteam/index.tsx b/src/pages/addteam/index.tsx
index 0291d613..ddb17d29 100644
--- a/src/pages/addteam/index.tsx
+++ b/src/pages/addteam/index.tsx
@@ -1,14 +1,20 @@
+import SlideInMotion from '@components/@shared/animation/SlideInMotion';
import AddTeamForm from '@components/addteam/AddTeamForm';
export default function AddTeam() {
return (
-
-
-
-
- 팀 이름은 회사명이나 모임 이름 등으로 설정하면 좋아요.
-
-
-
+
+
+
+ 팀 생성하기
+
+
+
+
+ 팀 이름은 회사명이나 모임 이름 등으로 설정하면 좋아요.
+
+
+
+
);
}
diff --git a/src/pages/addteam/participate.tsx b/src/pages/addteam/participate.tsx
index 19e1b91a..11ed80bd 100644
--- a/src/pages/addteam/participate.tsx
+++ b/src/pages/addteam/participate.tsx
@@ -1,14 +1,20 @@
+import SlideInMotion from '@components/@shared/animation/SlideInMotion';
import ParticipateTeamForm from '@components/addteam/ParticipateTeamForm';
export default function ParticipateTeam() {
return (
-
-
-
-
- 공유받은 팀 링크를 입력해 참여할 수 있어요.
-
-
-
+
+
+
+ 팀 참여하기
+
+
+
+
+ 공유받은 팀 링크를 입력해 참여할 수 있어요.
+
+
+
+
);
}
diff --git a/src/pages/article/index.tsx b/src/pages/article/index.tsx
index 39a00a42..c68594b1 100644
--- a/src/pages/article/index.tsx
+++ b/src/pages/article/index.tsx
@@ -1,8 +1,10 @@
// 게시글 메인 페이지
+import SlideInMotion from '@components/@shared/animation/SlideInMotion';
import Button from '@components/@shared/Button';
import { SearchInput } from '@components/@shared/Input';
import ArticleCard from '@components/article/ArticleCard';
import BestCard from '@components/article/BestCard';
+import Image from 'next/image';
import { useRouter } from 'next/router';
import PlusIcon from 'public/icons/plus.svg';
import { useEffect, useState } from 'react';
@@ -11,6 +13,7 @@ export default function ArticlePage() {
const [value, setValue] = useState('');
const [searchTerm, setSearchTerm] = useState('');
const router = useRouter();
+ const [isVisible, setIsVisible] = useState(false);
const handelNewArticle = () => {
router.push('/article/newarticle');
@@ -37,18 +40,53 @@ export default function ArticlePage() {
}
}, [router.query.q]);
+ const handleVisibleClick = () => {
+ if (isVisible === false) {
+ setIsVisible(true);
+ } else {
+ setIsVisible(false);
+ }
+ };
+
return (
* 내용
-
+
+
+
-
+
등록
-
+
-
+
diff --git a/src/pages/myhistory/[id].tsx b/src/pages/myhistory/[id].tsx
index 1d9d4c1f..ee0e4350 100644
--- a/src/pages/myhistory/[id].tsx
+++ b/src/pages/myhistory/[id].tsx
@@ -3,10 +3,9 @@ import MyTask from '@components/myhistory/MyTask';
export default function Myhistory() {
return (
-
+
-
마이 히스토리
diff --git a/src/pages/mysetting/[id].tsx b/src/pages/mysetting/[id].tsx
index 1439fc1f..b8a5c2b9 100644
--- a/src/pages/mysetting/[id].tsx
+++ b/src/pages/mysetting/[id].tsx
@@ -1,3 +1,4 @@
+import SlideInMotion from '@components/@shared/animation/SlideInMotion';
import InputTask from '@components/mysetting/inputTask';
import RemoveAccount from '@components/mysetting/RemoveAccount';
@@ -7,9 +8,9 @@ export default function MySetting() {
-
+
-
+
diff --git a/src/pages/myteam/index.tsx b/src/pages/myteam/index.tsx
index eb225891..05206d93 100644
--- a/src/pages/myteam/index.tsx
+++ b/src/pages/myteam/index.tsx
@@ -1,10 +1,12 @@
import { getMemberships } from '@/src/api/team/memberAPI';
+import ClickMotion from '@components/@shared/animation/ClickMotion';
import Button from '@components/@shared/Button';
import LoadingSpinner from '@components/@shared/LoadingSpinner';
import UserNotFound from '@components/@shared/UserNotFound';
import EmptyTeamPage from '@components/team/myteam/EmptyTeamPage';
import TeamBox from '@components/team/myteam/TeamBox';
import { useQuery } from '@tanstack/react-query';
+import Image from 'next/image';
import Link from 'next/link';
interface Group {
@@ -43,22 +45,25 @@ export default function MyTeamPage() {
참여중인 팀 목록
-
-
- + 새로운 팀 참여하기
-
-
+
+
+
+ + 새로운 팀 참여하기
+
+
+
-
- {memberships.map((membership) => (
+
+ {memberships.map((membership, index) => (
))}
-
-
- + 새로운 팀 생성하기
-
-
+
+
+
+ + 새로운 팀 생성하기
+
+
+
-
diff --git a/src/stores/taskListStore.ts b/src/stores/useTaskListStore.ts
similarity index 100%
rename from src/stores/taskListStore.ts
rename to src/stores/useTaskListStore.ts
diff --git a/src/stores/teamStore.ts b/src/stores/useTeamStore.ts
similarity index 100%
rename from src/stores/teamStore.ts
rename to src/stores/useTeamStore.ts
diff --git a/src/styles/globals.css b/src/styles/globals.css
index 85e437b1..e41160e1 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -167,3 +167,7 @@ div.react-datepicker__day--disabled {
body {
font-family: 'PretendardVariable', sans-serif;
}
+
+.navbar {
+ @apply fixed left-0 top-0 z-50 h-16 w-full bg-background-secondary;
+}
diff --git a/src/styles/progress.module.css b/src/styles/progress.module.css
new file mode 100644
index 00000000..b60e1b63
--- /dev/null
+++ b/src/styles/progress.module.css
@@ -0,0 +1,12 @@
+@keyframes progressAnimation {
+ 0% {
+ stroke-dashoffset: 100%;
+ }
+ 100% {
+ stroke-dashoffset: 0;
+ }
+}
+
+.stroke {
+ animation: progressAnimation 1s ease-out forwards;
+}
diff --git a/src/styles/scroll.module.css b/src/styles/scroll.module.css
index 4fb1f8ce..9d1be168 100644
--- a/src/styles/scroll.module.css
+++ b/src/styles/scroll.module.css
@@ -90,15 +90,3 @@
.ContributionsScroll::-webkit-scrollbar-track {
background: transparent; /* 스크롤바 트랙 색상 */
}
-
-/* 모바일 전용 스크롤바 스타일 */
-@media (max-width: 768px) {
- .taskListScroll::-webkit-scrollbar {
- width: 12px; /* 모바일에서는 스크롤바 숨기기 */
- }
-
- .taskListScroll {
- /* 스크롤이 필요한 요소의 다른 스타일 */
- -webkit-overflow-scrolling: touch; /* 부드러운 스크롤링 */
- }
-}
diff --git a/src/utils/getModalClass.tsx b/src/utils/getModalClass.tsx
index 1da6da3d..a4473950 100644
--- a/src/utils/getModalClass.tsx
+++ b/src/utils/getModalClass.tsx
@@ -10,6 +10,7 @@ export interface ModalClassProps {
fontSize?: '16' | '14' | '12';
fontArray?: 'center' | 'left' | 'right';
gap?: '8' | '24' | '32' | '40';
+ width?: '375' | '500';
}
export const getModalClass = ({
@@ -17,6 +18,7 @@ export const getModalClass = ({
array,
padding,
gap,
+ width = '375',
bgColor,
fontColor,
fontSize,
@@ -43,5 +45,6 @@ export const getModalClass = ({
'text-center': fontArray === 'center',
'text-left': fontArray === 'left',
'text-right': fontArray === 'right',
+ 'md:w-[500px] xl:w-[500px]': width === '500',
});
};
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 2e66ae41..aac6b8d9 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -4,6 +4,15 @@ const config: Config = {
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
theme: {
extend: {
+ keyframes: {
+ fadeInDown: {
+ '0%': { opacity: '0', transform: 'translateY(-10px)' },
+ '100%': { opacity: '1', transform: 'translateY(0)' },
+ },
+ },
+ animation: {
+ fadeInDown: 'fadeInDown 0.2s ease-out',
+ },
screens: {
tablet: '1024px',
},