Skip to content

Commit b2cc965

Browse files
Merge pull request #52 from Team9994/dev
�운동 리스트 페이지 유지보수
2 parents bfd5d42 + 4e1470a commit b2cc965

File tree

9 files changed

+152
-108
lines changed

9 files changed

+152
-108
lines changed

next.config.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ const nextConfig = {
3535
hostname: 'doyg075k8m500.cloudfront.net',
3636
pathname: '/**',
3737
},
38+
{
39+
protocol: 'http',
40+
hostname: 'img1.kakaocdn.net',
41+
pathname: '/**',
42+
},
43+
{
44+
protocol: 'https',
45+
hostname: 'img1.kakaocdn.net',
46+
pathname: '/**',
47+
},
3848
],
3949
},
4050
webpack: (config) => {

src/app/exercise-details/[id]/edit/page.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ import Image from 'next/image';
66
import { Input } from '@/components/ui/input';
77
import useInput from '@/hooks/useInput';
88
import { useCommentPutMutation } from '@/app/api/exercise-details/query';
9-
import { useSession } from 'next-auth/react';
109

1110
const Edit = () => {
1211
const router = useRouter();
1312
const pathname = usePathname();
1413
const searchParams = useSearchParams();
1514
const exerciseCommentId = searchParams.get('exerciseCommentId');
16-
const { data: session } = useSession();
1715
const pathSegments = pathname.split('/');
1816
const exerciseId = pathSegments[pathSegments.length - 2];
1917
const { putCommentMutation } = useCommentPutMutation(exerciseId, 'default');

src/app/exercise-details/[id]/hydrated-page.tsx

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
'use client';
2-
import React, { useState } from 'react';
2+
import React, { useEffect, useRef, useState } from 'react';
33
import ExerciseInfo from '../components/ExerciseInfo';
44
import SelectToggle from '../components/SelectToggle';
55
import Comment from '../components/Comment';
66
import Record from '../components/Record';
7-
import { usePathname, useSearchParams } from 'next/navigation';
7+
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
88
import { useDetailsInfo } from '@/app/api/exercise-details/query';
9-
import { useSession } from 'next-auth/react';
109
import Header from '@/components/layouts/Header';
1110
import Link from 'next/link';
1211
import Back from '@/components/common/Back';
1312
import Image from 'next/image';
14-
import { useDetailLikeRegister } from '@/app/api/exercise/query';
13+
import { useDeleteCustomExerciseMutation, useDetailLikeRegister } from '@/app/api/exercise/query';
1514

1615
const ExerciseDetails = () => {
16+
const router = useRouter();
1717
const searchParams = useSearchParams();
1818
const type = searchParams.get('type');
1919
const [selected, setSelected] = useState<'explain' | 'record'>(
2020
type === 'default' ? 'explain' : 'record'
2121
);
22-
22+
const [isActiveMenu, setIsActiveMenu] = useState<boolean>(false);
2323
const pathname = usePathname();
2424

2525
const pathSegments = pathname.split('/');
2626
const lastSegment = pathSegments[pathSegments.length - 1];
2727

28-
const { data: session } = useSession();
28+
const { deleteCustomExerciseMutation } = useDeleteCustomExerciseMutation();
2929
const { postDetailLikeRegisterMutation } = useDetailLikeRegister();
3030

3131
const { data } = useDetailsInfo({ id: lastSegment, source: 'default' });
@@ -36,14 +36,29 @@ const ExerciseDetails = () => {
3636
source: type as 'custom' | 'default',
3737
});
3838
};
39+
40+
const menuRef = useRef<HTMLDivElement | null>(null);
41+
42+
const handleClickOutside = (e: Event) => {
43+
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
44+
setIsActiveMenu(false);
45+
}
46+
};
47+
48+
useEffect(() => {
49+
document.addEventListener('mousedown', handleClickOutside);
50+
return () => {
51+
document.removeEventListener('mousedown', handleClickOutside);
52+
};
53+
}, []);
3954
console.log(data);
4055
return (
4156
<div>
4257
{lastSegment !== 'edit' && (
4358
<Header
4459
className={'bg-backgrounds-default'}
4560
left={
46-
<Link href="/">
61+
<Link href="/exercise-list/search">
4762
<Back />
4863
</Link>
4964
}
@@ -59,21 +74,50 @@ const ExerciseDetails = () => {
5974
height={24}
6075
/>
6176
{type === 'custom' && (
62-
<Image
63-
onClick={handleHeartChange}
64-
priority
65-
src={'/assets/menu.svg'}
66-
alt={'찜하기'}
67-
width={28}
68-
height={28}
69-
className="ml-4 rotate-90"
70-
/>
77+
<div className="relative">
78+
<Image
79+
priority
80+
src={'/assets/menu.svg'}
81+
alt={'메뉴'}
82+
width={28}
83+
height={28}
84+
className="ml-4 rotate-90"
85+
onClick={() => setIsActiveMenu((pre) => !pre)}
86+
/>
87+
{isActiveMenu && (
88+
<div
89+
ref={menuRef}
90+
className="absolute top-0 z-20 right-0 shadow-main bg-backgrounds-light text-md"
91+
>
92+
<div
93+
onClick={() => {
94+
router.push(
95+
`/custom-exercise?status=edit&exerciseId=${data?.data?.exerciseId}`
96+
);
97+
}}
98+
className="cursor-pointer w-[152px] h-[46px] text-text-main p-3 border-b border-borders-sub"
99+
>
100+
수정
101+
</div>
102+
<div
103+
onClick={() =>
104+
deleteCustomExerciseMutation.mutate({
105+
id: String(data?.data?.exerciseId),
106+
})
107+
}
108+
className="w-[152px] h-[46px] text-text-accent p-3 cursor-pointer"
109+
>
110+
삭제
111+
</div>
112+
</div>
113+
)}
114+
</div>
71115
)}
72116
</div>
73117
}
74118
/>
75119
)}
76-
<ExerciseInfo id={lastSegment} type={type} info={data?.data} />
120+
<ExerciseInfo info={data?.data} />
77121
<SelectToggle type={type} selected={selected} setSelected={setSelected} />
78122
{type === 'default' && selected === 'explain' && (
79123
<div className="w-full px-5 mt-4 mb-10">
@@ -87,7 +131,14 @@ const ExerciseDetails = () => {
87131
})}
88132
<div className="w-[320px] h-[40px] rounded-6 border border-backgrounds-light text-s flex justify-between items-center px-4 mb-10 mt-5">
89133
<div className="leading-[18px] text-text-main ">더 자세한 동작을 알고싶다면?</div>
90-
<div className="leading-[18px] text-blue">동영상 보러 가기 </div>
134+
<Link
135+
href={`https://www.youtube.com/results?search_query=${data?.data?.exerciseName}`}
136+
className="cursor-pointer leading-[18px] text-blue"
137+
target="_blank"
138+
rel="noopener noreferrer"
139+
>
140+
동영상 보러 가기
141+
</Link>
91142
</div>
92143
<Comment />
93144
</div>

src/app/exercise-details/components/Comment.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React from 'react';
44
import useExplain from '../hooks/useExplain';
55
import { CommentList } from '../types';
66
import { usePathname } from 'next/navigation';
7+
import defaultPng from './../../../../public/assets/my.svg';
78

89
const Comment = () => {
910
const {
@@ -20,6 +21,7 @@ const Comment = () => {
2021
contentReset,
2122
router,
2223
id,
24+
session,
2325
} = useExplain();
2426

2527
const pathname = usePathname();
@@ -33,6 +35,12 @@ const Comment = () => {
3335
className="w-full h-[40px] bg-backgrounds-light focus:outline-none border-none placeholder-[#999999]"
3436
placeholder="댓글을 작성해보세요"
3537
onChange={onChange}
38+
onKeyDown={(e) => {
39+
if (e.key === 'Enter') {
40+
postCommentMutation.mutate({ content, exerciseId: id });
41+
contentReset();
42+
}
43+
}}
3644
value={content}
3745
/>
3846
<Image
@@ -60,7 +68,10 @@ const Comment = () => {
6068
?.flatMap((page) => page?.data?.commentList)
6169
?.map((data: CommentList) => (
6270
<section key={data?.exerciseCommentId} className="flex relative w-full mb-7">
63-
<div className="bg-backgrounds-sub w-6 h-6 rounded-full" />
71+
<div className="relative w-6 h-6 rounded-16 overflow-hidden">
72+
<Image src={session?.user?.image || defaultPng} alt="프로필 사진" fill />
73+
</div>
74+
6475
<div className="ml-2">
6576
<div className="text-xs mb-2">
6677
<span className="mr-2 text-text-sub">{data?.nickName}</span>
@@ -85,14 +96,16 @@ const Comment = () => {
8596
<p className={data?.likeStatus ? 'text-blue' : ''}>{data?.likeCount}</p>
8697
</div>
8798
</div>
88-
<Image
89-
className="absolute top-0 right-0 cursor-pointer"
90-
src="/assets/menu.svg"
91-
width={18}
92-
height={18}
93-
alt="menu"
94-
onClick={() => handleMenuClick(data?.exerciseCommentId)}
95-
/>
99+
{data?.writer && (
100+
<Image
101+
className="absolute top-0 right-0 cursor-pointer"
102+
src="/assets/menu.svg"
103+
width={18}
104+
height={18}
105+
alt="menu"
106+
onClick={() => handleMenuClick(data?.exerciseCommentId)}
107+
/>
108+
)}
96109

97110
{data?.writer && activeMenuId === data?.exerciseCommentId && (
98111
<div

src/app/exercise-details/components/ExerciseInfo.tsx

Lines changed: 4 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,13 @@
11
'use client';
2-
import React, { useEffect, useRef, useState } from 'react';
2+
import React from 'react';
33
import Image from 'next/image';
4-
import { useDeleteCustomExerciseMutation } from '@/app/api/exercise/query';
5-
import { useRouter } from 'next/navigation';
64
import { GetDetailsInfoType } from '@/app/api/exercise-details/type';
75

86
interface ExerciseInfoProps {
9-
type: string | null;
10-
id: string;
117
info: GetDetailsInfoType | undefined;
128
}
139

14-
const ExerciseInfo = ({ id, type, info }: ExerciseInfoProps) => {
15-
const router = useRouter();
16-
const menuRef = useRef<HTMLDivElement | null>(null);
17-
const [isActiveMenu, setIsActiveMenu] = useState<boolean>(false);
18-
const handleClickOutside = (e: Event) => {
19-
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
20-
setIsActiveMenu(false);
21-
}
22-
};
23-
24-
useEffect(() => {
25-
document.addEventListener('mousedown', handleClickOutside);
26-
return () => {
27-
document.removeEventListener('mousedown', handleClickOutside);
28-
};
29-
}, []);
30-
const { deleteCustomExerciseMutation } = useDeleteCustomExerciseMutation();
10+
const ExerciseInfo = ({ info }: ExerciseInfoProps) => {
3111
if (!info) return;
3212
return (
3313
<div className="mx-auto px-5">
@@ -36,51 +16,14 @@ const ExerciseInfo = ({ id, type, info }: ExerciseInfoProps) => {
3616
{info?.exerciseName}
3717
</h3>
3818
<div className="flex align-center">
39-
<div className="relative flex align-center">
40-
{type === 'custom' && (
41-
<Image
42-
priority
43-
src={'/assets/menu.svg'}
44-
alt={'메뉴'}
45-
width={28}
46-
height={28}
47-
className="ml-2"
48-
onClick={() => setIsActiveMenu((pre) => !pre)}
49-
/>
50-
)}
51-
{isActiveMenu && (
52-
<div
53-
ref={menuRef}
54-
className="absolute top-0 z-20 right-0 shadow-main bg-backgrounds-light text-md"
55-
>
56-
<div
57-
onClick={() => {
58-
router.push(`/custom-exercise?status=edit&exerciseId=${id}`);
59-
}}
60-
className="w-[152px] h-[46px] text-text-main p-3 cursor-pointer border-b border-borders-sub"
61-
>
62-
수정
63-
</div>
64-
<div
65-
onClick={() =>
66-
deleteCustomExerciseMutation.mutate({
67-
id,
68-
})
69-
}
70-
className="w-[152px] h-[46px] text-text-accent p-3 cursor-pointer"
71-
>
72-
삭제
73-
</div>
74-
</div>
75-
)}
76-
</div>
19+
<div className="relative flex align-center"></div>
7720
</div>
7821
</div>
7922

8023
<p className="text-sm leading-5 text-text-light mb-4">
8124
{info?.exerciseTarget} : {info?.exerciseEquipment}
8225
</p>
83-
<div className="w-full h-[184px] bg-backgrounds-light mb-2 relative">
26+
<div className="w-full h-[250px] bg-backgrounds-light mb-2 relative">
8427
{info?.gifUrl === '등록된 이미지 파일이 없습니다.' ? null : (
8528
<Image src={info.gifUrl} alt={'운동 이미지'} fill />
8629
)}

src/app/exercise-details/components/SelectToggle.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,58 @@
1-
import React from 'react';
1+
import React, { useEffect, useRef, useState } from 'react';
2+
23
interface SelectToggleProps {
34
selected: 'explain' | 'record';
45
setSelected: React.Dispatch<React.SetStateAction<'explain' | 'record'>>;
56
type: string | null;
67
}
78

89
const SelectToggle = ({ selected, setSelected, type }: SelectToggleProps) => {
10+
const explainRef = useRef<HTMLDivElement>(null);
11+
const recordRef = useRef<HTMLDivElement>(null);
12+
const [underlineStyle, setUnderlineStyle] = useState({ width: 0, left: 0 });
13+
14+
useEffect(() => {
15+
const selectedRef = selected === 'explain' ? explainRef.current : recordRef.current;
16+
17+
if (selectedRef) {
18+
const { offsetWidth, offsetLeft } = selectedRef;
19+
setUnderlineStyle({ width: offsetWidth, left: offsetLeft });
20+
}
21+
}, [selected]);
22+
923
return (
10-
<div className="flex w-full text-base text-center">
24+
<div className="relative flex w-full text-base text-center">
25+
{/* 밑줄 */}
26+
<div
27+
className="absolute bottom-0 h-0.5 bg-text-main transition-all duration-300 ease-in-out"
28+
style={{
29+
width: `${underlineStyle.width}px`,
30+
transform: `translateX(${underlineStyle.left}px)`,
31+
}}
32+
></div>
33+
34+
{/* 설명 버튼 */}
1135
{type === 'default' && (
1236
<div
37+
ref={explainRef}
1338
className={`flex-grow basis-0 cursor-pointer py-3 ${
1439
selected === 'explain'
15-
? 'text-text-main font-bold border-b-2 border-text-main'
16-
: 'text-borders-main border-b-2 border-borders-main'
40+
? 'text-text-main font-bold border-b-0'
41+
: 'text-borders-main border-b border-borders-main'
1742
}`}
1843
onClick={() => setSelected('explain')}
1944
>
2045
설명
2146
</div>
2247
)}
2348

49+
{/* 기록/분석 버튼 */}
2450
<div
51+
ref={recordRef}
2552
className={`flex-grow basis-0 cursor-pointer py-3 ${
2653
selected === 'record'
27-
? 'text-text-main font-bold border-b-2 border-text-main'
28-
: 'text-borders-main border-b-2 border-borders-main'
54+
? 'text-text-main font-bold border-b-0'
55+
: 'text-borders-main border-b border-borders-main'
2956
}`}
3057
onClick={() => setSelected('record')}
3158
>

0 commit comments

Comments
 (0)