Skip to content

Commit b7490d8

Browse files
authored
Merge pull request #196 from TaskFlow-CLAP/CLAP-436
CLAP-436 QA 사항 반영 Tony.tsx
2 parents 7e6dccb + 1152396 commit b7490d8

26 files changed

+300
-151
lines changed

package-lock.json

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@tanstack/vue-query": "^5.66.0",
1717
"axios": "^1.7.9",
1818
"chart.js": "^4.4.7",
19+
"dompurify": "^3.2.4",
1920
"js-cookie": "^3.0.5",
2021
"pinia": "^2.3.0",
2122
"tailwind-scrollbar-hide": "^2.0.0",
@@ -27,6 +28,7 @@
2728
},
2829
"devDependencies": {
2930
"@tsconfig/node22": "^22.0.0",
31+
"@types/dompurify": "^3.0.5",
3032
"@types/js-cookie": "^3.0.6",
3133
"@types/node": "^22.10.2",
3234
"@typescript-eslint/eslint-plugin": "^8.20.0",

src/App.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import TheView from './layout/TheView.vue'
44
</script>
55

66
<template>
7+
<div id="modal" />
78
<TopBar />
89
<TheView />
910
</template>

src/api/admin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { NewLabelTypes, UserRegistrationApiProps, UserUpdateValue } from '@/types/admin'
22
import type { LabelDataTypes } from '@/types/common'
33
import { axiosInstance, formDataAxiosInstance } from '@/utils/axios'
4+
import DOMPurify from 'dompurify'
45

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

1516
export const patchLabelAdmin = async (editLabel: LabelDataTypes) => {
1617
const response = await axiosInstance.patch(`/api/managements/labels/${editLabel.labelId}`, {
17-
labelName: editLabel.labelName,
18+
labelName: DOMPurify.sanitize(editLabel.labelName),
1819
labelColor: editLabel.labelColor
1920
})
2021
return response.data

src/api/user.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Status } from '@/types/common'
22
import type { RequestApprovePostTypes } from '@/types/manager'
33
import { axiosInstance, formDataAxiosInstance } from '@/utils/axios'
4+
import DOMPurify from 'dompurify'
45

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

5657
export const postComment = async (taskID: number, content: string) => {
57-
const response = await axiosInstance.post(`/api/tasks/${taskID}/comments`, { content })
58+
const response = await axiosInstance.post(`/api/tasks/${taskID}/comments`, {
59+
content: DOMPurify.sanitize(content)
60+
})
5861
return response.data
5962
}
6063

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

69-
export const patchComment = async (commentId: number, content: string) => {
70-
const response = await axiosInstance.patch(`/api/comments/${commentId}`, { content })
71-
return response.data
72-
}
73-
7472
export const deleteComment = async (commentId: number) => {
7573
const response = await axiosInstance.delete(`/api/comments/${commentId}`)
7674
return response.data

src/components/common/EditInformation.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ import FormButtonContainer from './FormButtonContainer.vue'
139139
import FormCheckbox from './FormCheckbox.vue'
140140
import ImageContainer from './ImageContainer.vue'
141141
import ModalView from './ModalView.vue'
142+
import DOMPurify from 'dompurify'
143+
142144
const router = useRouter()
143145
144146
const memberStore = useMemberStore()
@@ -276,7 +278,7 @@ const handleSubmit = async () => {
276278
if (isInvalid.value == false && isFull.value == false) {
277279
const formData = new FormData()
278280
const memberInfo = {
279-
name: name.value,
281+
name: DOMPurify.sanitize(name.value),
280282
isProfileImageDeleted: imageDelete.value,
281283
emailNotification: emailCheck.value,
282284
kakaoWorkNotification: kakaoWorkCheck.value
Lines changed: 70 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,92 @@
11
<template>
2-
<div
3-
v-if="isOpen"
4-
class="fixed inset-0 bg-black bg-opacity-15 flex justify-center items-center z-[99]"
5-
@click.self="closeModal" />
6-
<Transition name="modal">
2+
<Teleport to="#modal">
73
<div
84
v-if="isOpen"
9-
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]">
10-
<div class="flex flex-col gap-8 w-[300px]">
11-
<div class="flex flex-col gap-6">
12-
<div class="flex flex-col items-center gap-2">
13-
<CommonIcons
14-
v-if="type == 'successType'"
15-
:name="successIcon" />
16-
<CommonIcons
17-
v-if="type == 'failType' || type == 'inputType' || type === 'terminate'"
18-
:name="failIcon" />
19-
<CommonIcons
20-
v-if="type == 'warningType'"
21-
:name="warningIcon" />
22-
<LoadingIcon v-if="type == 'loadingType'" />
23-
<div
24-
v-if="$slots.header"
25-
class="flex text-2xl font-semibold justify-center whitespace-pre-wrap text-center">
26-
<slot name="header"></slot>
27-
</div>
5+
class="fixed inset-0 bg-black bg-opacity-15 flex justify-center items-center z-[99]"
6+
@click.self="closeModal" />
7+
<Transition name="modal">
8+
<div
9+
v-if="isOpen"
10+
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]">
11+
<div class="flex flex-col gap-8 w-[300px]">
12+
<div class="flex flex-col gap-6">
13+
<div class="flex flex-col items-center gap-2">
14+
<CommonIcons
15+
v-if="type == 'successType'"
16+
:name="successIcon" />
17+
<CommonIcons
18+
v-if="type == 'failType' || type == 'inputType' || type === 'terminate'"
19+
:name="failIcon" />
20+
<CommonIcons
21+
v-if="type == 'warningType'"
22+
:name="warningIcon" />
23+
<LoadingIcon v-if="type == 'loadingType'" />
24+
<div
25+
v-if="$slots.header"
26+
class="flex text-2xl font-semibold justify-center whitespace-pre-wrap text-center">
27+
<slot name="header"></slot>
28+
</div>
2829

29-
<div
30-
v-if="type != 'inputType' && $slots.body"
31-
class="flex text-sm font-semibold text-body justify-center whitespace-pre-wrap text-center">
32-
<slot name="body"></slot>
30+
<div
31+
v-if="type != 'inputType' && $slots.body"
32+
class="flex text-sm font-semibold text-body justify-center whitespace-pre-wrap text-center">
33+
<slot name="body"></slot>
34+
</div>
3335
</div>
36+
<textarea
37+
v-if="type == 'inputType' || type === 'terminate'"
38+
v-model="textValue"
39+
:placeholder="
40+
type === 'terminate' ? '종료 사유를 입력해주세요' : '반려 사유를 입력해주세요'
41+
"
42+
:class="{ 'border border-red-1 placeholder-red-500': isEmpty }"
43+
class="flex border w-full border-border-1 px-4 py-3 focus:outline-none resize-none h-[120px]" />
3444
</div>
35-
<textarea
36-
v-if="type == 'inputType' || type === 'terminate'"
37-
v-model="textValue"
38-
:placeholder="
39-
type === 'terminate' ? '종료 사유를 입력해주세요' : '반려 사유를 입력해주세요'
40-
"
41-
:class="{ 'border border-red-1 placeholder-red-500': isEmpty }"
42-
class="flex border w-full border-border-1 px-4 py-3 focus:outline-none resize-none h-[120px]" />
43-
</div>
44-
45-
<button
46-
type="button"
47-
class="button-large-primary"
48-
v-if="type == 'successType'"
49-
@click="closeModal">
50-
확인
51-
</button>
5245

53-
<button
54-
type="button"
55-
class="button-large-default"
56-
v-if="type == 'failType'"
57-
@click="closeModal">
58-
확인
59-
</button>
60-
61-
<div
62-
class="flex items-center gap-6"
63-
v-if="type == 'warningType' || type == 'inputType' || type === 'terminate'">
6446
<button
6547
type="button"
66-
class="button-large-default"
48+
class="button-large-primary"
49+
v-if="type == 'successType'"
6750
@click="closeModal">
68-
취소
51+
확인
6952
</button>
53+
7054
<button
7155
type="button"
72-
class="button-large-red"
73-
@click="confirmModal">
74-
{{ type === 'inputType' ? '반려' : type === 'terminate' ? '종료' : '삭제' }}
56+
class="button-large-default"
57+
v-if="type == 'failType'"
58+
@click="closeModal">
59+
확인
7560
</button>
61+
62+
<div
63+
class="flex items-center gap-6"
64+
v-if="type == 'warningType' || type == 'inputType' || type === 'terminate'">
65+
<button
66+
type="button"
67+
class="button-large-default"
68+
@click="closeModal">
69+
취소
70+
</button>
71+
<button
72+
type="button"
73+
class="button-large-red"
74+
@click="confirmModal">
75+
{{ type === 'inputType' ? '반려' : type === 'terminate' ? '종료' : '삭제' }}
76+
</button>
77+
</div>
7678
</div>
7779
</div>
78-
</div>
79-
</Transition>
80+
</Transition>
81+
</Teleport>
8082
</template>
8183

8284
<script setup lang="ts">
8385
import { failIcon, successIcon, warningIcon } from '@/constants/iconPath'
84-
import { preventEnter } from '@/utils/preventEnter'
8586
import { onUnmounted, ref, watch } from 'vue'
8687
import CommonIcons from './CommonIcons.vue'
8788
import LoadingIcon from './LoadingIcon.vue'
89+
import { useIsOverlayOpenStore } from '@/stores/isOverlayOpen'
8890
8991
const { isOpen, type, modelValue, isEmpty } = defineProps<{
9092
isOpen: boolean
@@ -114,22 +116,20 @@ const confirmModal = () => {
114116
emit('click')
115117
}
116118
119+
const { setIsOverlayOpen } = useIsOverlayOpenStore()
117120
watch(
118121
() => isOpen,
119122
() => {
120123
if (isOpen) {
121124
textValue.value = ''
122-
document.body.style.overflow = 'hidden'
123-
window.addEventListener('keydown', preventEnter)
125+
setIsOverlayOpen(true)
124126
} else {
125-
document.body.style.overflow = ''
126-
window.removeEventListener('keydown', preventEnter)
127+
setIsOverlayOpen(false)
127128
}
128129
}
129130
)
130131
131132
onUnmounted(() => {
132-
document.body.style.overflow = ''
133-
window.removeEventListener('keydown', preventEnter)
133+
setIsOverlayOpen(false)
134134
})
135135
</script>

src/components/common/TaskCard.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import TaskDetail from '../task-detail/TaskDetail.vue'
5151
import CommonIcons from './CommonIcons.vue'
5252
import ImageContainer from './ImageContainer.vue'
5353
import TaskLabel from './TaskLabel.vue'
54+
import { useIsOverlayOpenStore } from '@/stores/isOverlayOpen'
5455
5556
const { data } = defineProps<{ data: TaskCardProps; draggable?: boolean }>()
5657
const emit = defineEmits(['toggleModal'])
@@ -62,6 +63,7 @@ const borderLeft = computed(() => {
6263
return `border-${statusAsColor(data.taskStatus as Status)}-1`
6364
})
6465
66+
const { setIsOverlayOpen } = useIsOverlayOpenStore()
6567
const handleModal = (id: number | null) => {
6668
if (!id) {
6769
queryClient.invalidateQueries({
@@ -70,7 +72,7 @@ const handleModal = (id: number | null) => {
7072
queryClient.invalidateQueries({
7173
queryKey: ['teamStatus', params]
7274
})
73-
document.body.style.overflow = ''
75+
setIsOverlayOpen(false)
7476
}
7577
emit('toggleModal')
7678
selectedID.value = id

src/components/filters/FilterInput.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@
1212

1313
<script setup lang="ts">
1414
import type { Filter } from '@/types/common'
15+
import DOMPurify from 'dompurify'
1516
1617
const { title, width = '120' } = defineProps<Filter>()
1718
const emit = defineEmits(['update:value'])
1819
1920
const onValueChange = (event: Event) => {
2021
const target = event.target as HTMLInputElement
2122
setTimeout(() => {
22-
emit('update:value', target.value)
23+
emit('update:value', DOMPurify.sanitize(target.value))
2324
}, 500)
2425
}
2526
</script>

0 commit comments

Comments
 (0)