-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #78 from SWM-METEOR/FIN-367
스마트 드래그 - 질문 글 등록, 조회 기능 구현
- Loading branch information
Showing
17 changed files
with
907 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,3 +34,6 @@ yarn-error.log* | |
*.tsbuildinfo | ||
next-env.d.ts | ||
|
||
|
||
# Sentry Config File | ||
.sentryclirc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
'use client'; | ||
|
||
import Head from 'next/head'; | ||
import * as Sentry from '@sentry/nextjs'; | ||
|
||
export default function Page() { | ||
return ( | ||
<div> | ||
<Head> | ||
<title>Sentry Onboarding</title> | ||
<meta name="description" content="Test Sentry for your Next.js app!" /> | ||
</Head> | ||
|
||
<main | ||
style={{ | ||
minHeight: '100vh', | ||
display: 'flex', | ||
flexDirection: 'column', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
}} | ||
> | ||
<h1 style={{ fontSize: '4rem', margin: '14px 0' }}> | ||
<svg | ||
style={{ | ||
height: '1em', | ||
}} | ||
xmlns="http://www.w3.org/2000/svg" | ||
viewBox="0 0 200 44" | ||
> | ||
<path | ||
fill="currentColor" | ||
d="M124.32,28.28,109.56,9.22h-3.68V34.77h3.73V15.19l15.18,19.58h3.26V9.22h-3.73ZM87.15,23.54h13.23V20.22H87.14V12.53h14.93V9.21H83.34V34.77h18.92V31.45H87.14ZM71.59,20.3h0C66.44,19.06,65,18.08,65,15.7c0-2.14,1.89-3.59,4.71-3.59a12.06,12.06,0,0,1,7.07,2.55l2-2.83a14.1,14.1,0,0,0-9-3c-5.06,0-8.59,3-8.59,7.27,0,4.6,3,6.19,8.46,7.52C74.51,24.74,76,25.78,76,28.11s-2,3.77-5.09,3.77a12.34,12.34,0,0,1-8.3-3.26l-2.25,2.69a15.94,15.94,0,0,0,10.42,3.85c5.48,0,9-2.95,9-7.51C79.75,23.79,77.47,21.72,71.59,20.3ZM195.7,9.22l-7.69,12-7.64-12h-4.46L186,24.67V34.78h3.84V24.55L200,9.22Zm-64.63,3.46h8.37v22.1h3.84V12.68h8.37V9.22H131.08ZM169.41,24.8c3.86-1.07,6-3.77,6-7.63,0-4.91-3.59-8-9.38-8H154.67V34.76h3.8V25.58h6.45l6.48,9.2h4.44l-7-9.82Zm-10.95-2.5V12.6h7.17c3.74,0,5.88,1.77,5.88,4.84s-2.29,4.86-5.84,4.86Z M29,2.26a4.67,4.67,0,0,0-8,0L14.42,13.53A32.21,32.21,0,0,1,32.17,40.19H27.55A27.68,27.68,0,0,0,12.09,17.47L6,28a15.92,15.92,0,0,1,9.23,12.17H4.62A.76.76,0,0,1,4,39.06l2.94-5a10.74,10.74,0,0,0-3.36-1.9l-2.91,5a4.54,4.54,0,0,0,1.69,6.24A4.66,4.66,0,0,0,4.62,44H19.15a19.4,19.4,0,0,0-8-17.31l2.31-4A23.87,23.87,0,0,1,23.76,44H36.07a35.88,35.88,0,0,0-16.41-31.8l4.67-8a.77.77,0,0,1,1.05-.27c.53.29,20.29,34.77,20.66,35.17a.76.76,0,0,1-.68,1.13H40.6q.09,1.91,0,3.81h4.78A4.59,4.59,0,0,0,50,39.43a4.49,4.49,0,0,0-.62-2.28Z" | ||
></path> | ||
</svg> | ||
</h1> | ||
|
||
<p>Get started by sending us a sample error:</p> | ||
<button | ||
type="button" | ||
style={{ | ||
padding: '12px', | ||
cursor: 'pointer', | ||
backgroundColor: '#AD6CAA', | ||
borderRadius: '4px', | ||
border: 'none', | ||
color: 'white', | ||
fontSize: '14px', | ||
margin: '18px', | ||
}} | ||
onClick={async () => { | ||
const transaction = Sentry.startTransaction({ | ||
name: 'Example Frontend Transaction', | ||
}); | ||
|
||
Sentry.configureScope((scope) => { | ||
scope.setSpan(transaction); | ||
}); | ||
|
||
try { | ||
const res = await fetch('/api/sentry-example-api'); | ||
if (!res.ok) { | ||
throw new Error('Sentry Example Frontend Error'); | ||
} | ||
} finally { | ||
transaction.finish(); | ||
} | ||
}} | ||
> | ||
Throw error! | ||
</button> | ||
|
||
<p> | ||
Next, look for the error on the{' '} | ||
<a href="https://sw-maestro.sentry.io/issues/?project=4506229638758400">Issues Page</a>. | ||
</p> | ||
<p style={{ marginTop: '24px' }}> | ||
For more information, see{' '} | ||
<a href="https://docs.sentry.io/platforms/javascript/guides/nextjs/"> | ||
https://docs.sentry.io/platforms/javascript/guides/nextjs/ | ||
</a> | ||
</p> | ||
</main> | ||
</div> | ||
); | ||
} |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
'use client'; | ||
|
||
import { useState, useEffect } from 'react'; | ||
import axiosInstance from '@/utils/axios'; | ||
|
||
import QnAView from '@/components/SmartDrag/QnA/QnAView'; | ||
import { useSelectedTextStore } from '@/store/sidePanel'; | ||
import { QuestionListType } from '@/types/qna'; | ||
|
||
export default function QnAContainer() { | ||
const { selectedText } = useSelectedTextStore(); | ||
const [questionsData, setQuestionsData] = useState<QuestionListType | null>(null); | ||
|
||
async function fetchQuestions() { | ||
try { | ||
const res = await axiosInstance.post(`/qna/inline-question-list`, { | ||
dragText: selectedText, | ||
}); | ||
const questionRes = res.data.data; | ||
setQuestionsData(questionRes); | ||
} catch (error) { | ||
setQuestionsData(null); | ||
} | ||
} | ||
|
||
useEffect(() => { | ||
if (!selectedText) return; | ||
|
||
fetchQuestions(); | ||
}, [selectedText]); | ||
|
||
return <QnAView selectedText={selectedText} questionList={questionsData?.questionList} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
'use client'; | ||
|
||
import React, { useRef, useState } from 'react'; | ||
import { AxiosError } from 'axios'; | ||
import { useMutation, useQueryClient } from '@tanstack/react-query'; | ||
|
||
import axiosInstance from '@/utils/axios'; | ||
import useToast from '@/hooks/toast'; | ||
import { ServerErrorResponse } from '@/types/error'; | ||
|
||
export default function QnAEditor() { | ||
const [showErrorToast] = useToast(); | ||
const queryClient = useQueryClient(); | ||
|
||
const titleRef = useRef<HTMLTextAreaElement | null>(null); | ||
const contentsRef = useRef<HTMLTextAreaElement | null>(null); | ||
|
||
// 글자 수 | ||
const [titleLength, setTitleLength] = useState(0); | ||
const [contentsLength, setContentsLength] = useState(0); | ||
|
||
const handleTitleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { | ||
const textValue = e.target.value; | ||
|
||
// 입력값을 500자로 제한 | ||
if (textValue.length > 500) { | ||
e.target.value = textValue.slice(0, 500); | ||
setTitleLength(500); | ||
return; | ||
} | ||
|
||
setTitleLength(textValue.length); | ||
}; | ||
|
||
const handleContentsChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { | ||
const textValue = e.target.value; | ||
|
||
// 입력값을 50자로 제한 | ||
if (textValue.length > 50) { | ||
e.target.value = textValue.slice(0, 50); | ||
setContentsLength(50); | ||
return; | ||
} | ||
|
||
setContentsLength(textValue.length); | ||
}; | ||
|
||
const registerQuestionMutation = useMutation( | ||
({ titleValue, contentsValue }: { titleValue: string; contentsValue: string }) => { | ||
return axiosInstance.post(`/qna/write`, { | ||
title: titleValue, | ||
body: contentsValue, | ||
}); | ||
}, | ||
{ | ||
onSuccess: () => { | ||
titleRef.current!.value = ''; | ||
contentsRef.current!.value = ''; | ||
|
||
setTitleLength(0); | ||
setContentsLength(0); | ||
|
||
queryClient.invalidateQueries(['question']); | ||
console.log('질문 등록 성공'); | ||
}, | ||
onError: (err: AxiosError<ServerErrorResponse>) => { | ||
if (!err.response) { | ||
return; | ||
} | ||
|
||
const errorStatus = err.response.data.status; | ||
// 비로그인 유저 | ||
if (errorStatus === 401) { | ||
showErrorToast('로그인 후 이용가능합니다.'); | ||
} | ||
}, | ||
} | ||
); | ||
|
||
const registerQuestion = () => { | ||
const titleValue = titleRef.current?.value; | ||
const contentsValue = contentsRef.current?.value; | ||
|
||
if (!titleValue || !contentsValue) { | ||
return; | ||
} | ||
registerQuestionMutation.mutate({ titleValue, contentsValue }); | ||
}; | ||
|
||
return ( | ||
<div className="flex flex-col gap-[15px] w-full"> | ||
{/* 질문 제목 */} | ||
<textarea | ||
ref={titleRef} | ||
onChange={handleTitleChange} | ||
placeholder={'질문 제목을 입력하세요.'} | ||
className="border border-[#DDDDDD] rounded-[12px] h-[50px] w-[320px] px-[14px] pt-[12px] pb-[20px] focus:outline-none active:outline-none text-[15px] font-medium" | ||
></textarea> | ||
{/* 질문 내용 */} | ||
<textarea | ||
ref={contentsRef} | ||
onChange={handleContentsChange} | ||
placeholder={'질문 내용을 입력하세요.'} | ||
className="border border-[#DDDDDD] rounded-[12px] h-[130px] w-[320px] py-[14px] px-[14px] focus:outline-none active:outline-none" | ||
></textarea> | ||
|
||
<button | ||
className="ml-auto bottom-5 right-5 bg-[#00A1FF] h-[50px] w-[320px] rounded-[5px] text-white font-bold text-[16px]" | ||
onClick={registerQuestion} | ||
> | ||
등록 | ||
</button> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import Image from 'next/image'; | ||
|
||
import IntroContainer from '@/components/SmartDrag/Intro/IntroContainer'; | ||
import QnAEditor from '@/components/SmartDrag/QnA/QnAEditor'; | ||
import { QuestionType } from '@/types/qna'; | ||
|
||
interface PropsType { | ||
selectedText: string; | ||
questionList: QuestionType[] | undefined; | ||
} | ||
|
||
export default function QnAView({ selectedText, questionList }: PropsType) { | ||
console.log(questionList); | ||
return ( | ||
<div className="w-[320px] flex flex-col gap-4"> | ||
{selectedText === '' ? ( | ||
<IntroContainer /> | ||
) : ( | ||
<> | ||
{/* 선택한 텍스트 */} | ||
<div className="chat chat-end"> | ||
<div className="chat-bubble bg-[#313a47] whitespace-pre-line text-white"> | ||
{selectedText} | ||
</div> | ||
</div> | ||
{/* QnA 검색 결과 */} | ||
<div className="h-[calc(100vh-655px)] flex flex-col gap-[10px] overflow-y-auto flex-shrink-0"> | ||
{!questionList ? ( | ||
<div></div> | ||
) : ( | ||
<> | ||
{questionList.map((question) => ( | ||
<div | ||
key={question.id} | ||
className="flex flex-col p-[14px] flex-shrink-0 bg-[#F7F7F7] w-[320px] h-[120px] border border-[#EEEEEE] rounded-[15px] " | ||
> | ||
<div className="flex gap-[5px] items-center"> | ||
<div className="w-[20px] h-[20px] rounded-[5px] overflow-hidden flex-shrink-0"> | ||
<Image | ||
src={question.profileImageUrl} | ||
alt={question.authorNickname} | ||
width="20" | ||
height="20" | ||
/> | ||
</div> | ||
<span className="text-[#333333] text-[12px] font-medium"> | ||
{question.authorNickname} | ||
</span> | ||
<span className="ml-[3px] text-[#999999] text-[12px]"> | ||
{question.createdDate} | ||
</span> | ||
</div> | ||
{/* TODO: 질문 title 말고 본문으로 변경해야 함 */} | ||
<div className="text-[#000000] text-[15px] font-medium h-[60px] overflow-y-auto"> | ||
{question.title} | ||
</div> | ||
</div> | ||
))} | ||
</> | ||
)} | ||
</div> | ||
<hr className="w-full text-[#EEEEEE] mt-[12.5px]" /> | ||
{/* 질문 입력 창 */} | ||
<QnAEditor /> | ||
</> | ||
)} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.