Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
f460feb
:bug: [fix] : 오버레이 생길 때 스크롤 사라짐으로 인한 레이아웃 밀림 수정
seorang42 Feb 15, 2025
7eb7bdc
Merge branch 'CLAP-426' of https://github.com/TaskFlow-CLAP/TaskFlow-…
seorang42 Feb 15, 2025
8267da1
:bug: [fix] : 오버레이 생길 때 스크롤 사라짐으로 인한 TopBar 레이아웃 밀림 수정
seorang42 Feb 15, 2025
b354c7d
:bug: [fix] : 닉네임이 10 글자일 경우 공간이 부족한 현상 수정
seorang42 Feb 15, 2025
c8541dc
:lipstick: [design] : 비활성화된 초대 버튼 사유 표시
seorang42 Feb 15, 2025
1699ab6
:bug: [fix] : isValidate 공유로 인해 border 속성 동시 표시 오류 수정
seorang42 Feb 15, 2025
9a82d6d
:lipstick: [design] : TaskDetail min-w 제거
seorang42 Feb 15, 2025
0e3d86e
:bug: [fix] : 잘못 작성된 스크롤바 제거 속성 제거
seorang42 Feb 15, 2025
82e9b35
:bug: [fix] : 알림 상태 표시 안 되는 현상 수정
seorang42 Feb 16, 2025
5a4a3de
:bug: [fix] : 종료 사유 사용자 정보 누락으로 인한 수정
seorang42 Feb 16, 2025
2a86165
:bug: [fix] : ModalView가 TaskDetail 컴포넌트 안에 갇혀 비정상적으로 작동하는 오류 수정
seorang42 Feb 16, 2025
04919bf
:bug: [fix] : div#modal의 mount 순서로 인해 에러 모달 표시되지 않는 현상 수정
seorang42 Feb 16, 2025
9e59dd3
:recycle: [refactor] XSS 공격 방지를 위한 DOMPurify 설치 및 사용
seorang42 Feb 16, 2025
1152396
:bug: [fix] : 1차 카테고리 추가, 2차 카테고리 수정 400 에러 발생 수정
seorang42 Feb 16, 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
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@tanstack/vue-query": "^5.66.0",
"axios": "^1.7.9",
"chart.js": "^4.4.7",
"dompurify": "^3.2.4",
"js-cookie": "^3.0.5",
"pinia": "^2.3.0",
"tailwind-scrollbar-hide": "^2.0.0",
Expand All @@ -27,6 +28,7 @@
},
"devDependencies": {
"@tsconfig/node22": "^22.0.0",
"@types/dompurify": "^3.0.5",
"@types/js-cookie": "^3.0.6",
"@types/node": "^22.10.2",
"@typescript-eslint/eslint-plugin": "^8.20.0",
Expand Down
1 change: 1 addition & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import TheView from './layout/TheView.vue'
</script>

<template>
<div id="modal" />
<TopBar />
<TheView />
</template>
3 changes: 2 additions & 1 deletion src/api/admin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { NewLabelTypes, UserRegistrationApiProps, UserUpdateValue } from '@/types/admin'
import type { LabelDataTypes } from '@/types/common'
import { axiosInstance, formDataAxiosInstance } from '@/utils/axios'
import DOMPurify from 'dompurify'

export const deleteLabelAdmin = async (id: number) => {
const response = await axiosInstance.delete(`/api/managements/labels/${id}`)
Expand All @@ -14,7 +15,7 @@ export const postAddLabelAdmin = async (newLabel: NewLabelTypes) => {

export const patchLabelAdmin = async (editLabel: LabelDataTypes) => {
const response = await axiosInstance.patch(`/api/managements/labels/${editLabel.labelId}`, {
labelName: editLabel.labelName,
labelName: DOMPurify.sanitize(editLabel.labelName),
labelColor: editLabel.labelColor
})
return response.data
Expand Down
10 changes: 4 additions & 6 deletions src/api/user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Status } from '@/types/common'
import type { RequestApprovePostTypes } from '@/types/manager'
import { axiosInstance, formDataAxiosInstance } from '@/utils/axios'
import DOMPurify from 'dompurify'

export const postTaskRequest = async (formdata: FormData) => {
const response = await formDataAxiosInstance.post('/api/tasks', formdata)
Expand Down Expand Up @@ -54,7 +55,9 @@ export const getHistory = async (taskID: number | null) => {
}

export const postComment = async (taskID: number, content: string) => {
const response = await axiosInstance.post(`/api/tasks/${taskID}/comments`, { content })
const response = await axiosInstance.post(`/api/tasks/${taskID}/comments`, {
content: DOMPurify.sanitize(content)
})
return response.data
}

Expand All @@ -66,11 +69,6 @@ export const postCommentAttachment = async (taskID: number, formdata: FormData)
return response.data
}

export const patchComment = async (commentId: number, content: string) => {
const response = await axiosInstance.patch(`/api/comments/${commentId}`, { content })
return response.data
}

export const deleteComment = async (commentId: number) => {
const response = await axiosInstance.delete(`/api/comments/${commentId}`)
return response.data
Expand Down
4 changes: 3 additions & 1 deletion src/components/common/EditInformation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ import FormButtonContainer from './FormButtonContainer.vue'
import FormCheckbox from './FormCheckbox.vue'
import ImageContainer from './ImageContainer.vue'
import ModalView from './ModalView.vue'
import DOMPurify from 'dompurify'

const router = useRouter()

const memberStore = useMemberStore()
Expand Down Expand Up @@ -276,7 +278,7 @@ const handleSubmit = async () => {
if (isInvalid.value == false && isFull.value == false) {
const formData = new FormData()
const memberInfo = {
name: name.value,
name: DOMPurify.sanitize(name.value),
isProfileImageDeleted: imageDelete.value,
emailNotification: emailCheck.value,
kakaoWorkNotification: kakaoWorkCheck.value
Expand Down
140 changes: 70 additions & 70 deletions src/components/common/ModalView.vue
Original file line number Diff line number Diff line change
@@ -1,90 +1,92 @@
<template>
<div
v-if="isOpen"
class="fixed inset-0 bg-black bg-opacity-15 flex justify-center items-center z-[99]"
@click.self="closeModal" />
<Transition name="modal">
<Teleport to="#modal">
<div
v-if="isOpen"
class="bg-white rounded-lg shadow-lg px-8 py-8 fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-[99]">
<div class="flex flex-col gap-8 w-[300px]">
<div class="flex flex-col gap-6">
<div class="flex flex-col items-center gap-2">
<CommonIcons
v-if="type == 'successType'"
:name="successIcon" />
<CommonIcons
v-if="type == 'failType' || type == 'inputType' || type === 'terminate'"
:name="failIcon" />
<CommonIcons
v-if="type == 'warningType'"
:name="warningIcon" />
<LoadingIcon v-if="type == 'loadingType'" />
<div
v-if="$slots.header"
class="flex text-2xl font-semibold justify-center whitespace-pre-wrap text-center">
<slot name="header"></slot>
</div>
class="fixed inset-0 bg-black bg-opacity-15 flex justify-center items-center z-[99]"
@click.self="closeModal" />
<Transition name="modal">
<div
v-if="isOpen"
class="bg-white rounded-lg shadow-lg px-8 py-8 fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-[99]">
<div class="flex flex-col gap-8 w-[300px]">
<div class="flex flex-col gap-6">
<div class="flex flex-col items-center gap-2">
<CommonIcons
v-if="type == 'successType'"
:name="successIcon" />
<CommonIcons
v-if="type == 'failType' || type == 'inputType' || type === 'terminate'"
:name="failIcon" />
<CommonIcons
v-if="type == 'warningType'"
:name="warningIcon" />
<LoadingIcon v-if="type == 'loadingType'" />
<div
v-if="$slots.header"
class="flex text-2xl font-semibold justify-center whitespace-pre-wrap text-center">
<slot name="header"></slot>
</div>

<div
v-if="type != 'inputType' && $slots.body"
class="flex text-sm font-semibold text-body justify-center whitespace-pre-wrap text-center">
<slot name="body"></slot>
<div
v-if="type != 'inputType' && $slots.body"
class="flex text-sm font-semibold text-body justify-center whitespace-pre-wrap text-center">
<slot name="body"></slot>
</div>
</div>
<textarea
v-if="type == 'inputType' || type === 'terminate'"
v-model="textValue"
:placeholder="
type === 'terminate' ? '종료 사유를 입력해주세요' : '반려 사유를 입력해주세요'
"
:class="{ 'border border-red-1 placeholder-red-500': isEmpty }"
class="flex border w-full border-border-1 px-4 py-3 focus:outline-none resize-none h-[120px]" />
</div>
<textarea
v-if="type == 'inputType' || type === 'terminate'"
v-model="textValue"
:placeholder="
type === 'terminate' ? '종료 사유를 입력해주세요' : '반려 사유를 입력해주세요'
"
:class="{ 'border border-red-1 placeholder-red-500': isEmpty }"
class="flex border w-full border-border-1 px-4 py-3 focus:outline-none resize-none h-[120px]" />
</div>

<button
type="button"
class="button-large-primary"
v-if="type == 'successType'"
@click="closeModal">
확인
</button>

<button
type="button"
class="button-large-default"
v-if="type == 'failType'"
@click="closeModal">
확인
</button>

<div
class="flex items-center gap-6"
v-if="type == 'warningType' || type == 'inputType' || type === 'terminate'">
<button
type="button"
class="button-large-default"
class="button-large-primary"
v-if="type == 'successType'"
@click="closeModal">
취소
확인
</button>

<button
type="button"
class="button-large-red"
@click="confirmModal">
{{ type === 'inputType' ? '반려' : type === 'terminate' ? '종료' : '삭제' }}
class="button-large-default"
v-if="type == 'failType'"
@click="closeModal">
확인
</button>

<div
class="flex items-center gap-6"
v-if="type == 'warningType' || type == 'inputType' || type === 'terminate'">
<button
type="button"
class="button-large-default"
@click="closeModal">
취소
</button>
<button
type="button"
class="button-large-red"
@click="confirmModal">
{{ type === 'inputType' ? '반려' : type === 'terminate' ? '종료' : '삭제' }}
</button>
</div>
</div>
</div>
</div>
</Transition>
</Transition>
</Teleport>
</template>

<script setup lang="ts">
import { failIcon, successIcon, warningIcon } from '@/constants/iconPath'
import { preventEnter } from '@/utils/preventEnter'
import { onUnmounted, ref, watch } from 'vue'
import CommonIcons from './CommonIcons.vue'
import LoadingIcon from './LoadingIcon.vue'
import { useIsOverlayOpenStore } from '@/stores/isOverlayOpen'

const { isOpen, type, modelValue, isEmpty } = defineProps<{
isOpen: boolean
Expand Down Expand Up @@ -114,22 +116,20 @@ const confirmModal = () => {
emit('click')
}

const { setIsOverlayOpen } = useIsOverlayOpenStore()
watch(
() => isOpen,
() => {
if (isOpen) {
textValue.value = ''
document.body.style.overflow = 'hidden'
window.addEventListener('keydown', preventEnter)
setIsOverlayOpen(true)
} else {
document.body.style.overflow = ''
window.removeEventListener('keydown', preventEnter)
setIsOverlayOpen(false)
}
}
)

onUnmounted(() => {
document.body.style.overflow = ''
window.removeEventListener('keydown', preventEnter)
setIsOverlayOpen(false)
})
</script>
4 changes: 3 additions & 1 deletion src/components/common/TaskCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import TaskDetail from '../task-detail/TaskDetail.vue'
import CommonIcons from './CommonIcons.vue'
import ImageContainer from './ImageContainer.vue'
import TaskLabel from './TaskLabel.vue'
import { useIsOverlayOpenStore } from '@/stores/isOverlayOpen'

const { data } = defineProps<{ data: TaskCardProps; draggable?: boolean }>()
const emit = defineEmits(['toggleModal'])
Expand All @@ -62,6 +63,7 @@ const borderLeft = computed(() => {
return `border-${statusAsColor(data.taskStatus as Status)}-1`
})

const { setIsOverlayOpen } = useIsOverlayOpenStore()
const handleModal = (id: number | null) => {
if (!id) {
queryClient.invalidateQueries({
Expand All @@ -70,7 +72,7 @@ const handleModal = (id: number | null) => {
queryClient.invalidateQueries({
queryKey: ['teamStatus', params]
})
document.body.style.overflow = ''
setIsOverlayOpen(false)
}
emit('toggleModal')
selectedID.value = id
Expand Down
3 changes: 2 additions & 1 deletion src/components/filters/FilterInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@

<script setup lang="ts">
import type { Filter } from '@/types/common'
import DOMPurify from 'dompurify'

const { title, width = '120' } = defineProps<Filter>()
const emit = defineEmits(['update:value'])

const onValueChange = (event: Event) => {
const target = event.target as HTMLInputElement
setTimeout(() => {
emit('update:value', target.value)
emit('update:value', DOMPurify.sanitize(target.value))
}, 500)
}
</script>
Loading
Loading