diff --git a/src/app/(layout)/advice/_components/AdviceInputArea/index.tsx b/src/app/(layout)/advice/_components/AdviceInputArea/index.tsx index fce6cb0..f495d92 100644 --- a/src/app/(layout)/advice/_components/AdviceInputArea/index.tsx +++ b/src/app/(layout)/advice/_components/AdviceInputArea/index.tsx @@ -81,7 +81,7 @@ export default function AdviceInputArea({

키워드 입력

-
+
{tags.map((tag) => ( -
+
{templates.map((template) => ( ([]); const [allTemplates, setAllTemplates] = useState([]); const [totalPages, setTotalPages] = useState(0); - const { openModal, closeModal, currentModal } = useModalStore(); + const { openModal, currentModal } = useModalStore(); const [userName, setUserName] = useState('사용자'); const tagOptions: TagType[] = [ @@ -204,36 +203,12 @@ export default function ExplorePage() { openModal('view', { templateId }); }; - // 모달 닫기 핸들러 (useModalStore의 closeModal을 사용하므로 이 함수는 선택 해제만 담당) - const clearSelectedTemplate = () => { - setSelectedTemplate(null); - }; - // 화면에 표시할 추천 템플릿 (3개씩) const visibleRecommendTemplates = recommendTemplates.slice( currentRecommendIndex, currentRecommendIndex + 3, ); - // 템플릿 사용 핸들러 - const handleUseTemplate = () => { - if (selectedTemplate?.content) { - navigator.clipboard - .writeText(selectedTemplate.content) - .then(() => { - alert('템플릿 내용이 클립보드에 복사되었습니다.'); - console.log('템플릿 사용하기:', selectedTemplate.title); - closeModal(); - }) - .catch((err) => { - console.error('클립보드 복사 실패:', err); - alert('템플릿 복사에 실패했습니다. 다시 시도해주세요.'); - }); - } else { - alert('템플릿 내용이 없습니다.'); - } - }; - // 모달 상태 변경 감지하여 카드 선택 해제 useEffect(() => { if (currentModal === null && selectedTemplate) { @@ -381,79 +356,6 @@ export default function ExplorePage() {
- - {selectedTemplate && ( -
-
-
e.stopPropagation()} - > -
-

- {selectedTemplate.title || '제목 없음'} -

- -
- -
-

- {selectedTemplate.description || '설명이 없는 템플릿입니다.'} -

-
- {selectedTemplate.tags && selectedTemplate.tags.length > 0 ? ( - selectedTemplate.tags.map((tag, idx) => ) - ) : ( -

태그 정보가 없습니다.

- )} -
-
- - {selectedTemplate.content ? ( -
-

- {selectedTemplate.content} -

-
- ) : ( -
-

- 현재 템플릿 내용을 불러올 수 없습니다. -

-
- )} - -
- - -
-
-
-
- )} ); } diff --git a/src/components/ui/Modal/EditModal/index.tsx b/src/components/ui/Modal/EditModal/index.tsx index 80bb357..ee7f87c 100644 --- a/src/components/ui/Modal/EditModal/index.tsx +++ b/src/components/ui/Modal/EditModal/index.tsx @@ -181,7 +181,7 @@ export default function EditModal({
{/* 태그 */}
-
+
{tags.map((tag) => ( e.stopPropagation()}> {currentModal === 'view' && } {currentModal === 'create' && } + {currentModal === 'profile' && } {currentModal === 'edit' && selectedTemplateId != null && ( ('popular'); + + const { selectedTemplateId, openModal, closeModal } = useModalStore(); + const { data: authorData } = useAuthorDetail(selectedTemplateId as number); + + const templates = authorData?.templates || []; + + const templateSort = () => { + setSortType((prev) => (prev === 'latest' ? 'popular' : 'latest')); + }; + + const sortedTemplates = useMemo(() => { + if (sortType === 'popular') { + const sorted = [...templates].sort((a, b) => { + const aLikes = a.likeCount || 0; + const bLikes = b.likeCount || 0; + return bLikes - aLikes; + }); + return sorted; + } + + return templates; + }, [templates, sortType]); + + const handleClickCard = (templateId: number) => { + closeModal(); + openModal('view', { templateId }); + }; + + return ( +
+
+
+ 작성자 프로필 + +
+
{authorData?.author.name}
+
+ + {authorData?.author.score} +
+
+ +
+ +
+
{authorData?.templateTotalCount}
+ 작성한 템플릿 +
+
+ +
+ + + +
+ +
+ + + +
+ {sortedTemplates.map((item) => ( + handleClickCard(item.templateId as number)} + /> + ))} +
+
+ ); +} diff --git a/src/components/ui/Modal/UsingModal/index.tsx b/src/components/ui/Modal/UsingModal/index.tsx index c719fc3..582b9da 100644 --- a/src/components/ui/Modal/UsingModal/index.tsx +++ b/src/components/ui/Modal/UsingModal/index.tsx @@ -19,8 +19,9 @@ import Toast from '../../Toast'; export default function UsingModal() { const router = useRouter(); - const { selectedTemplateId, closeModal } = useModalStore(); + const { selectedTemplateId, openModal, closeModal } = useModalStore(); const { openUnsaveModal } = useUnsaveModalStore(); + const [templateId, setTemplateId] = useState(); const [template, setTemplate] = useState(null); const [loading, setLoading] = useState(true); const [content, setContent] = useState(''); @@ -40,6 +41,7 @@ export default function UsingModal() { const fetchTemplate = async () => { try { const data = await getTemplateDetail(selectedTemplateId); + setTemplateId(data?.templateId ?? 0); setTemplate(data); setContent(data?.content ?? ''); setFolderId(data?.savedFolder?.folderId ?? 0); @@ -93,6 +95,11 @@ export default function UsingModal() { setToastVisible(true); }; + const handleAuthor = () => { + closeModal(); + openModal('profile', { templateId: templateId }); + }; + return (
{/* 태그 */} @@ -157,25 +164,30 @@ export default function UsingModal() {
{/* 작성자 정보 */}
- 작성자 프로필 +
-
+
+
diff --git a/src/components/ui/Modal/ViewModal/index.tsx b/src/components/ui/Modal/ViewModal/index.tsx index 3cad65f..7b854db 100644 --- a/src/components/ui/Modal/ViewModal/index.tsx +++ b/src/components/ui/Modal/ViewModal/index.tsx @@ -97,6 +97,13 @@ export default function ViewModal() { setToastVisible(true); }; + const handleAuthor = () => { + closeModal(); + openModal('profile', { + templateId: template.templateId, + }); + }; + return (
{/* 태그 */} @@ -167,25 +174,30 @@ export default function ViewModal() {
{/* 작성자 정보 */}
- 작성자 프로필 +
-
+
+
diff --git a/src/hooks/template/useCreateTemplate.ts b/src/hooks/template/useCreateTemplate.ts index 4984c43..1b9de61 100644 --- a/src/hooks/template/useCreateTemplate.ts +++ b/src/hooks/template/useCreateTemplate.ts @@ -1,8 +1,8 @@ import { createTemplate } from '@/services/template/postCreateTemplate'; -import { TemplateRequset } from '@/types/templateRequest'; +import { TemplateRequest } from '@/types/templateRequest'; import { useMutation } from '@tanstack/react-query'; export const useCreateTemplate = () => - useMutation({ + useMutation({ mutationFn: (data) => createTemplate(data), }); diff --git a/src/hooks/template/useEditTemplate.ts b/src/hooks/template/useEditTemplate.ts index 1a7449a..2d40cbf 100644 --- a/src/hooks/template/useEditTemplate.ts +++ b/src/hooks/template/useEditTemplate.ts @@ -1,10 +1,10 @@ import { editTemplate } from '@/services/template/putEditTemplate'; -import { TemplateRequset } from '@/types/templateRequest'; +import { TemplateRequest } from '@/types/templateRequest'; import { useMutation } from '@tanstack/react-query'; export const useEditTemplate = () => { return useMutation({ - mutationFn: ({ templateId, payload }: { templateId: number; payload: TemplateRequset }) => + mutationFn: ({ templateId, payload }: { templateId: number; payload: TemplateRequest }) => editTemplate(templateId, payload), }); }; diff --git a/src/hooks/template/useTemplateAuthor.ts b/src/hooks/template/useTemplateAuthor.ts new file mode 100644 index 0000000..e84a509 --- /dev/null +++ b/src/hooks/template/useTemplateAuthor.ts @@ -0,0 +1,10 @@ +import { getTemplateAuthor } from '@/services/template/getTemplateAuthor'; +import { useQuery } from '@tanstack/react-query'; + +export const useAuthorDetail = (templateId: number) => { + return useQuery({ + queryKey: ['author', templateId], + queryFn: () => getTemplateAuthor(templateId), + enabled: !!templateId, + }); +}; diff --git a/src/services/template/getTemplateAuthor.ts b/src/services/template/getTemplateAuthor.ts new file mode 100644 index 0000000..de8c0eb --- /dev/null +++ b/src/services/template/getTemplateAuthor.ts @@ -0,0 +1,26 @@ +import { customFetch } from '@/utils/customFetch'; + +export interface AuthorRequest { + templateId?: number; + title: string; + content: string; + tags: string[]; + isPrivate: boolean; + likeCount: number; +} + +export interface AuthorDetail { + author: { + id: number; + name: string; + score: number; + profileImageUrl: string; + }; + templateTotalCount: number; + templates: AuthorRequest[]; +} + +export const getTemplateAuthor = async (templateId: number): Promise => { + const data = await customFetch(`/templates/${templateId}/authors`); + return data ?? null; +}; diff --git a/src/services/template/postCreateTemplate.ts b/src/services/template/postCreateTemplate.ts index 1f214fc..fe37530 100644 --- a/src/services/template/postCreateTemplate.ts +++ b/src/services/template/postCreateTemplate.ts @@ -1,7 +1,7 @@ -import { TemplateRequset } from '@/types/templateRequest'; +import { TemplateRequest } from '@/types/templateRequest'; import { customFetch } from '@/utils/customFetch'; -export const createTemplate = async (payload: TemplateRequset): Promise => { +export const createTemplate = async (payload: TemplateRequest): Promise => { return customFetch('/templates', { method: 'POST', body: JSON.stringify(payload), diff --git a/src/services/template/putEditTemplate.ts b/src/services/template/putEditTemplate.ts index 6888c3c..6ddef6e 100644 --- a/src/services/template/putEditTemplate.ts +++ b/src/services/template/putEditTemplate.ts @@ -1,7 +1,7 @@ -import { TemplateRequset } from '@/types/templateRequest'; +import { TemplateRequest } from '@/types/templateRequest'; import { customFetch } from '@/utils/customFetch'; -export const editTemplate = async (templateId: number, payload: TemplateRequset): Promise => { +export const editTemplate = async (templateId: number, payload: TemplateRequest): Promise => { return customFetch(`/templates/${templateId}`, { method: 'PUT', body: JSON.stringify(payload), diff --git a/src/styles/scrollbar.css b/src/styles/scrollbar.css index 2708ada..7a33a58 100644 --- a/src/styles/scrollbar.css +++ b/src/styles/scrollbar.css @@ -1,21 +1,43 @@ -.custom-scrollbar { +.custom-scrollbar-x { overflow-x: scroll; scroll-behavior: smooth; } -.custom-scrollbar::-webkit-scrollbar { +.custom-scrollbar-x::-webkit-scrollbar { height: 4px; } -.custom-scrollbar::-webkit-scrollbar-thumb { +.custom-scrollbar-x::-webkit-scrollbar-thumb { background-color: #e0e0e0; /* grey2 */ border-radius: 2px; } -.custom-scrollbar::-webkit-scrollbar-track { +.custom-scrollbar-x::-webkit-scrollbar-track { background: transparent; } -.custom-scrollbar::-webkit-scrollbar-button { +.custom-scrollbar-x::-webkit-scrollbar-button { + display: none; +} + +.custom-scrollbar-y { + overflow-y: scroll; + scroll-behavior: smooth; +} + +.custom-scrollbar-y::-webkit-scrollbar { + width: 4px; +} + +.custom-scrollbar-y::-webkit-scrollbar-thumb { + background-color: #e0e0e0; /* grey2 */ + border-radius: 2px; +} + +.custom-scrollbar-y::-webkit-scrollbar-track { + background: transparent; +} + +.custom-scrollbar-y::-webkit-scrollbar-button { display: none; } diff --git a/src/types/templateRequest.ts b/src/types/templateRequest.ts index 9271b92..2e5493f 100644 --- a/src/types/templateRequest.ts +++ b/src/types/templateRequest.ts @@ -1,4 +1,5 @@ -export interface TemplateRequset { +export interface TemplateRequest { + templateId?: number; title: string; content: string; tags: string[];