Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default function AdviceInputArea({
<p className="button-md">키워드 입력</p>
<Spacing size={8} />

<div className="custom-scrollbar max-w-[760px] rotate-x-180 scroll-smooth">
<div className="custom-scrollbar-x max-w-[760px] rotate-x-180 scroll-smooth">
<div className="mb-3 flex w-max rotate-x-180 flex-nowrap gap-2">
{tags.map((tag) => (
<InputTag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default function RecommendTemplates() {
더 찾아보기
</Link>
</aside>
<section className="custom-scrollbar z-20 ml-4 flex w-[648px] flex-1 rotate-x-180 overflow-x-scroll scroll-smooth">
<section className="custom-scrollbar-x z-20 ml-4 flex w-[648px] flex-1 rotate-x-180 overflow-x-scroll scroll-smooth">
<section className="mb-2 flex max-h-full rotate-x-180 gap-2">
{templates.map((template) => (
<Card
Expand Down
100 changes: 1 addition & 99 deletions src/app/(layout)/explore/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import React, { useState, useCallback, useEffect } from 'react';
import { Spacing } from '@/components/ui/Spacing';
import TagSearchBar from '@/components/ui/TagSearchBar';
import Card from '@/components/ui/Card';
import CardTag from '@/components/ui/CardTag';
import Arrow from '@/components/ui/Arrow';
import WandIcon from '@/assets/icons/icon-lucide-wand.svg';
import MagnifyIcon from '@/assets/icons/icon-magnify.svg';
Expand All @@ -25,7 +24,7 @@ export default function ExplorePage() {
const [recommendTemplates, setRecommendTemplates] = useState<TemplateType[]>([]);
const [allTemplates, setAllTemplates] = useState<TemplateType[]>([]);
const [totalPages, setTotalPages] = useState(0);
const { openModal, closeModal, currentModal } = useModalStore();
const { openModal, currentModal } = useModalStore();
const [userName, setUserName] = useState<string>('사용자');

const tagOptions: TagType[] = [
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -381,79 +356,6 @@ export default function ExplorePage() {
</section>
</section>
</div>

{selectedTemplate && (
<div
className="fixed inset-0 z-50"
style={{
backgroundColor: 'rgba(0,0,0,0.05)',
backdropFilter: 'blur(3px)',
}}
onClick={clearSelectedTemplate}
>
<div className="flex h-full w-full items-center justify-center p-4">
<div
className="max-h-[90vh] w-full max-w-2xl overflow-y-auto rounded-lg bg-white p-6 shadow-lg"
onClick={(e) => e.stopPropagation()}
>
<div className="mb-4 flex items-center justify-between">
<h3 className="title-md text-layout-grey7">
{selectedTemplate.title || '제목 없음'}
</h3>
<button
onClick={clearSelectedTemplate}
className="text-layout-grey6 hover:text-layout-grey7"
>
닫기
</button>
</div>

<div className="mb-6">
<p className="body-md text-layout-grey7 mb-4">
{selectedTemplate.description || '설명이 없는 템플릿입니다.'}
</p>
<div className="mt-3 flex flex-wrap gap-2">
{selectedTemplate.tags && selectedTemplate.tags.length > 0 ? (
selectedTemplate.tags.map((tag, idx) => <CardTag key={idx} text={tag} />)
) : (
<p className="text-layout-grey5 text-sm">태그 정보가 없습니다.</p>
)}
</div>
</div>

{selectedTemplate.content ? (
<div className="bg-layout-grey1 mt-4 mb-6 rounded-md p-4">
<p className="text-layout-grey7 text-sm whitespace-pre-line">
{selectedTemplate.content}
</p>
</div>
) : (
<div className="bg-layout-grey1 mt-4 mb-6 flex items-center justify-center rounded-md p-4">
<p className="text-layout-grey6 py-8 text-sm">
현재 템플릿 내용을 불러올 수 없습니다.
</p>
</div>
)}

<div className="mt-6 flex justify-end gap-3">
<button
className="border-layout-grey3 hover:bg-layout-grey1 rounded-md border px-4 py-2 transition-colors"
onClick={clearSelectedTemplate}
>
취소
</button>
<button
className="bg-primary-navy4 hover:bg-primary-navy5 rounded-md px-4 py-2 text-white transition-colors"
onClick={handleUseTemplate}
disabled={!selectedTemplate.content}
>
템플릿 사용하기
</button>
</div>
</div>
</div>
</div>
)}
</>
);
}
2 changes: 1 addition & 1 deletion src/components/ui/Modal/EditModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export default function EditModal({
<section className="bg-layout-white flex h-[700px] w-[1204px] flex-col rounded-[10px] p-9">
{/* 태그 */}
<section className="flex justify-between">
<div className="custom-scrollbar max-w-[760px] rotate-x-180 scroll-smooth">
<div className="custom-scrollbar-x max-w-[760px] rotate-x-180 scroll-smooth">
<div className="mb-3 flex w-max rotate-x-180 flex-nowrap gap-2">
{tags.map((tag) => (
<InputTag
Expand Down
2 changes: 2 additions & 0 deletions src/components/ui/Modal/ModalContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useUnsaveModalStore } from '@/stores/useUnsaveModalStore';
import ViewModal from './ViewModal';
import EditModal from './EditModal';
import UsingModal from './UsingModal';
import ProfileModal from './ProfileModal';
import UnSaveModal from './UnsaveModal';
import { useEffect } from 'react';

Expand Down Expand Up @@ -34,6 +35,7 @@ export default function ModalContainer() {
<div onClick={(e) => e.stopPropagation()}>
{currentModal === 'view' && <ViewModal />}
{currentModal === 'create' && <EditModal mode="create" draftContent={draftContent} />}
{currentModal === 'profile' && <ProfileModal />}
{currentModal === 'edit' && selectedTemplateId != null && (
<EditModal
mode="edit"
Expand Down
105 changes: 105 additions & 0 deletions src/components/ui/Modal/ProfileModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { useState, useMemo } from 'react';
import Image from 'next/image';
import Card from '@/components/ui/Card';
import { Spacing } from '@/components/ui/Spacing';
import { useAuthorDetail } from '@/hooks/template/useTemplateAuthor';
import { useModalStore } from '@/stores/useModalStore';
import IconClose from '@/assets/icons/icon-close.svg';
import IconGlowScore from '@/assets/icons/icon-glow-score.svg';
import IconArrowUpDown from '@/assets/icons/icon-arrow-up-down.svg';

export default function ProfileModal() {
const [sortType, setSortType] = useState<'latest' | 'popular'>('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 (
<section className="bg-layout-white flex h-[700px] w-[1204px] flex-col rounded-[10px] p-9">
<section className="flex items-start justify-between">
<section className="flex">
<Image
src={
authorData?.author.profileImageUrl ||
'https://github.com/user-attachments/assets/9c948b08-a78b-44cb-b572-f2a934b70c45'
}
alt="작성자 프로필"
width={100}
height={100}
className="rounded-sm"
/>
<Spacing size={16} horizontal={true} />
<section className="flex flex-col gap-5">
<div className="title-lg mt-1">{authorData?.author.name}</div>
<div className="title-sm flex gap-2">
<IconGlowScore />
{authorData?.author.score}
</div>
</section>
<Spacing size={36} horizontal={true} />
<div className="border-l-layout-grey3 h-[100px] border-l-1" />
<Spacing size={36} horizontal={true} />
<div className="body-md flex flex-col items-center justify-center gap-2">
<div className="title-lg text-layout-grey6">{authorData?.templateTotalCount}</div>
작성한 템플릿
</div>
</section>
<button onClick={closeModal}>
<IconClose />
</button>
</section>

<Spacing size={52} />

<div className="w-full justify-items-end">
<button
onClick={templateSort}
className="bg-layout-grey1 hover:bg-layout-grey2 flex h-9 w-22 items-center justify-center gap-1 rounded-md transition-colors"
>
<IconArrowUpDown />
{sortType === 'popular' ? '인기순' : '최신순'}
</button>
</div>

<Spacing size={20} />

<section className="custom-scrollbar-y grid h-[420px] grid-cols-4 gap-3 overflow-auto">
{sortedTemplates.map((item) => (
<Card
key={`${item.templateId}-${sortType}`}
variant="medium"
title={item.title}
description={item.content}
tags={item.tags}
likes={item.likeCount}
onClick={() => handleClickCard(item.templateId as number)}
/>
))}
</section>
</section>
);
}
38 changes: 25 additions & 13 deletions src/components/ui/Modal/UsingModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>();
const [template, setTemplate] = useState<TemplateDetail | null>(null);
const [loading, setLoading] = useState(true);
const [content, setContent] = useState('');
Expand All @@ -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);
Expand Down Expand Up @@ -93,6 +95,11 @@ export default function UsingModal() {
setToastVisible(true);
};

const handleAuthor = () => {
closeModal();
openModal('profile', { templateId: templateId });
};

return (
<section className="bg-layout-white flex h-[700px] w-[1204px] flex-col rounded-[10px] p-9">
{/* 태그 */}
Expand Down Expand Up @@ -157,25 +164,30 @@ export default function UsingModal() {
<section className="flex h-[80px] w-full items-end justify-between">
{/* 작성자 정보 */}
<section className="flex items-center gap-3">
<Image
src={
template.author.profileImageUrl ||
'https://github.com/user-attachments/assets/9c948b08-a78b-44cb-b572-f2a934b70c45'
}
alt="작성자 프로필"
width={80}
height={80}
className="rounded-sm"
/>
<button onClick={handleAuthor}>
<Image
src={
template.author.profileImageUrl ||
'https://github.com/user-attachments/assets/9c948b08-a78b-44cb-b572-f2a934b70c45'
}
alt="작성자 프로필"
width={80}
height={80}
className="rounded-sm"
/>
</button>

<section className="flex gap-6">
<div className="flex flex-col items-center justify-center gap-[18px]">
<button
onClick={handleAuthor}
className="flex flex-col items-center justify-center gap-[18px]"
>
<div className="title-sm flex">{template.author.name}</div>
<div className="body-lg flex gap-1">
<IconGlowScore />
{template.author.score}
</div>
</div>
</button>

<div className="border-layout-grey5 flex h-16 w-0 border" />

Expand Down
Loading