Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a47fb14
[fix] 챗봇 추천 페이지 수정 및 기능추가 (#130)
ahk0413 Oct 13, 2025
61bd59c
Design/main#11 (#131)
EunbinJung Oct 13, 2025
f071906
Style/main page 2#122 (#132)
mtm-git1018 Oct 13, 2025
bd767b0
Style/메인페이지 슬라이드영역 (#135)
mtm-git1018 Oct 14, 2025
1dbeb90
[fix] 로그아웃 시 auth/me api 자동호출로 401 에러 (#136)
ahk0413 Oct 14, 2025
993820d
Design/main#11 (#138)
EunbinJung Oct 15, 2025
d6035f4
[fix] 레이아웃 분리 (#139)
ahk0413 Oct 15, 2025
0a71cd0
Refactor/recipe fetch (#140)
mtm-git1018 Oct 15, 2025
8ee022c
Merge remote-tracking branch 'origin/main' into dev
mtm-git1018 Oct 15, 2025
95248c2
[fix] 경로 오류 수정
mtm-git1018 Oct 15, 2025
a160b2a
경로 수정
mtm-git1018 Oct 15, 2025
9c64186
[fix] 경로수정
mtm-git1018 Oct 15, 2025
52e82c3
Feat/write#19 (#142)
EunbinJung Oct 15, 2025
3f0e6f3
[fix] MainSlide 수정 (#143)
ahk0413 Oct 15, 2025
1d0e476
Refactor/칵테일 정렬 기능 수정 (#144)
mtm-git1018 Oct 15, 2025
0d6c6be
Merge remote-tracking branch 'origin/main' into dev
mtm-git1018 Oct 15, 2025
c3c3644
[fix]충돌에러수정
mtm-git1018 Oct 15, 2025
d28a1f4
[style] 폰트 추가
mtm-git1018 Oct 15, 2025
94a65e0
[fix]파일 내 코드중복 수정
mtm-git1018 Oct 15, 2025
9e976a5
[chore]포매팅
mtm-git1018 Oct 15, 2025
06e809c
[fix]타입중복 수정
mtm-git1018 Oct 15, 2025
fcef37f
[chore]포매팅
mtm-git1018 Oct 15, 2025
ca5539a
docs/ 폰트 추가 및 삭제 (#146)
mtm-git1018 Oct 15, 2025
ad53efa
Feat/write#19 (#147)
EunbinJung Oct 15, 2025
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
4 changes: 4 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
experimental: {
scrollRestoration: false,
},
images: {
domains: ['team2-app-s3-bucket.s3.ap-northeast-2.amazonaws.com'],
remotePatterns: [
Expand All @@ -9,6 +12,7 @@ const nextConfig: NextConfig = {
hostname: 'www.thecocktaildb.com',
},
],
qualities: [25, 50, 75, 90, 100],
},
env: {
NPUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
Expand Down
2,705 changes: 108 additions & 2,597 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"dependencies": {
"@react-three/drei": "^10.7.6",
"@react-three/fiber": "^9.3.0",
"@react-three/gltfjsx": "^4.3.4",
"@react-three/postprocessing": "^3.0.4",
"@tanstack/react-query": "^5.90.2",
"@tanstack/react-virtual": "^3.13.12",
Expand Down
Binary file removed public/1Stars.png
Binary file not shown.
Binary file added public/1star.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/2Stars.png
Binary file not shown.
Binary file added public/2star.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/CocktailDrop.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/fonts/Hahmlet-Bold.ttf
Binary file not shown.
Binary file added public/fonts/Hahmlet-Light.ttf
Binary file not shown.
Binary file added public/fonts/Hahmlet-Medium.ttf
Binary file not shown.
Binary file added public/fonts/Hahmlet-Regular.ttf
Binary file not shown.
Binary file added public/fonts/NanumSquareNeo-Variable.ttf
Binary file not shown.
Binary file added public/mobileCocktail.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions src/app/(main)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import FooterWrapper from '@/shared/components/footer/FooterWrapper';
import Header from '@/shared/components/header/Header';

function NoLayout({ children }: { children: React.ReactNode }) {
return (
<>
<Header className="bg-transparent w-full h-[44px] md:h-[60px] flex items-center justify-between px-[12px] fixed top-0 left-0 z-50 transition-transform duration-200 ease-in-ou" />
<main className="flex flex-1">{children}</main>
<FooterWrapper />
</>
);
}
export default NoLayout;
2 changes: 1 addition & 1 deletion src/app/(no-layout)/page.tsx → src/app/(main)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import FinalLanding from '@/domains/main/components/FinalLanding';

export default function Home() {
return (
<div className="page-layout max-w-full">
<div className="max-w-full flex-1">
<FinalLanding />
</div>
);
Expand Down
11 changes: 10 additions & 1 deletion src/app/(no-layout)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import FooterWrapper from '@/shared/components/footer/FooterWrapper';
import Header from '@/shared/components/header/Header';

function NoLayout({ children }: { children: React.ReactNode }) {
return <main className="flex flex-1">{children}</main>;
return (
<>
<Header className="bg-transparent w-full h-[44px] md:h-[60px] flex items-center justify-between px-[12px] fixed top-0 left-0 z-50 transition-transform duration-200 ease-in-ou" />
<main className="flex flex-1">{children}</main>
<FooterWrapper />
</>
);
}
export default NoLayout;
1 change: 0 additions & 1 deletion src/app/(with-layout)/community/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export async function generateMetadata({
cache: 'no-store',
});
const post = await res.json();
console.log(post);
return {
title: post.title,
description: post.content?.slice(0, 80),
Expand Down
1 change: 0 additions & 1 deletion src/app/(with-layout)/community/edit/[postId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { useParams } from 'next/navigation';

function Page() {
const params = useParams();
console.log(params);

return (
<div className="w-full mb-20 flex relative">
Expand Down
9 changes: 9 additions & 0 deletions src/domains/community/api/fetchPost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,12 @@ export async function likePost(postId: number | ParamValue) {
});
if (!res.ok) throw new Error('좋아요 실패');
}

export async function getLikePost(postId: number | ParamValue) {
const res = await fetch(`${getApi}/posts/${postId}/like`, {
method: 'GET',
});
if (!res.ok) throw new Error('좋아요 실패');
const data = await res.json();
return data.data;
}
4 changes: 2 additions & 2 deletions src/domains/community/components/like/LikeBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useState } from 'react';
type Props = {
size: 'sm' | 'md';
onClick?: () => void;
isClick?: boolean; // 외부에서 제어
isClick?: boolean | null; // 외부에서 제어
};

function LikeBtn({ size, onClick, isClick = false }: Props) {
Expand All @@ -13,7 +13,7 @@ function LikeBtn({ size, onClick, isClick = false }: Props) {
type="button"
className={`${size === 'md' ? 'w-13.75 h-13.75 flex-center border-1 border-white rounded-full' : ''} bg-primary`}
aria-label="좋아요 버튼"
aria-pressed={isClick}
aria-pressed={isClick ? isClick : false}
onClick={() => {
if (onClick) onClick();
}}
Expand Down
18 changes: 16 additions & 2 deletions src/domains/community/components/tag/TagList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,26 @@ function TagList({ hasDelete, tags, setTags }: Props) {

if (!tags) return;
return (
<ul className="flex text-sm gap-2 items-center text-primary font-light flex-wrap">
<ul
className="flex
flex-nowrap
md:flex-wrap
overflow-x-scroll
md:overflow-visible
text-sm
gap-2
items-center
text-primary
font-light
no-scrollbar
max-w-full
"
>
{tags?.length > 0 &&
tags.map((tag) => (
<li
key={tag}
className={`bg-[#FFE4E6] px-2 py-[1px] rounded-md flex gap-2 ${hasDelete && 'hover:opacity-90 pl-2 pr-1'}`}
className={`bg-[#FFE4E6] px-2 py-[1px] rounded-md flex gap-2 w-fit whitespace-nowrap ${hasDelete && 'hover:opacity-90 pl-2 pr-1'}`}
>
<p>{tag}</p>
{hasDelete && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { Virtualizer } from '@tanstack/react-virtual';
import gsap from 'gsap';
import { useEffect, useRef } from 'react';

type Props = {
value: string;
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
rowVirtualize: Virtualizer<HTMLElement, Element>;
};

function AutoGrowingTextarea({ value, onChange, rowVirtualize }: Props) {
function AutoGrowingTextarea({ value, onChange }: Props) {
const textareaRef = useRef<HTMLTextAreaElement | null>(null);

useEffect(() => {
Expand All @@ -29,17 +27,6 @@ function AutoGrowingTextarea({ value, onChange, rowVirtualize }: Props) {
};
}, []);

useEffect(() => {
if (textareaRef.current) {
requestAnimationFrame(() => {
const li = textareaRef.current?.closest('li') as HTMLElement | null;
if (li) {
rowVirtualize.measureElement(li);
}
});
}
}, [value]);

useEffect(() => {
if (!textareaRef.current) return;
gsap.fromTo(
Expand Down
2 changes: 1 addition & 1 deletion src/domains/community/detail/DetailContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Props = {
content: string;
prevLikeCount: number;
commentCount: number;
like: boolean;
like: boolean | null;
onLikeToggle: () => void;
imageUrls: string[];
title: string;
Expand Down
30 changes: 21 additions & 9 deletions src/domains/community/detail/DetailPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { fetchPostById, likePost } from '@/domains/community/api/fetchPost';
import { fetchPostById, getLikePost, likePost } from '@/domains/community/api/fetchPost';
import DetailContent from '@/domains/community/detail/DetailContent';
import DetailHeader from '@/domains/community/detail/DetailHeader';
import DetailTitle from '@/domains/community/detail/DetailTitle';
Expand All @@ -24,7 +24,7 @@ function DetailPage() {

const [postDetail, setPostDetail] = useState<Post | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [like, setLike] = useState(false);
const [like, setLike] = useState<boolean | null>(null);
const [prevLikeCount, setPrevLikeCount] = useState<number | undefined>(0);

const isLoggedIn = useAuthStore((state) => state.isLoggedIn);
Expand All @@ -46,6 +46,18 @@ function DetailPage() {
fetchData();
}, [postId, setPostDetail]);

useEffect(() => {
const fetchLikeStatus = async () => {
try {
const liked = await getLikePost(postId);
setLike(liked);
} catch (err) {
console.error('좋아요 상태 불러오기 실패', err);
}
};
fetchLikeStatus();
}, [postId]);

useEffect(() => {
if (postDetail) {
setPrevLikeCount(postDetail.likeCount);
Expand All @@ -68,17 +80,17 @@ function DetailPage() {
} = postDetail;

const handleLike = async () => {
setLike((prev) => !prev);
setPrevLikeCount((prev) => {
return like ? prev! - 1 : prev! + 1;
});
const newLike = !like; // 현재 상태 기준으로 먼저 계산
setLike(newLike); // 좋아요 상태 먼저 반영
setPrevLikeCount((count) => (newLike ? (count ?? 0) + 1 : (count ?? 0) - 1)); // count도 바로 계산

try {
await likePost(postId); // POST 요청 한 번으로 토글 처리
} catch (err) {
console.error('좋아요 토글 실패', err);
setLike((prev) => !prev);
setPrevLikeCount((prev) => (like ? prev! + 1 : prev! - 1));
// 롤백
setLike(!newLike);
setPrevLikeCount((count) => (newLike ? (count ?? 0) - 1 : (count ?? 0) + 1));
}
};

Expand Down Expand Up @@ -126,7 +138,7 @@ function DetailPage() {
{isLoggedIn && (
<div className="hidden lg:block">
<DetailTabDesktop
likeCount={prevLikeCount ?? 0}
likeCount={prevLikeCount}
commentCount={commentCount}
commentRef={commentRef}
like={like}
Expand Down
40 changes: 31 additions & 9 deletions src/domains/community/detail/ImageSlide.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,49 @@
import Image from 'next/image';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation, Pagination } from 'swiper/modules';
import { useState } from 'react';

function ImageSlide({ imageUrls }: { imageUrls: string[] }) {
const shouldLoop = imageUrls.length > 1;
const [loadedImages, setLoadedImages] = useState<Set<string>>(new Set());

const handleImageLoad = (imgUrl: string) => {
setLoadedImages((prev) => new Set(prev).add(imgUrl));
};

return (
<Swiper
spaceBetween={20}
modules={[Navigation, Pagination]}
navigation
navigation={shouldLoop}
pagination={{ clickable: true, type: 'bullets' }}
loop
loop={shouldLoop}
className="w-full max-h-100 flex justify-center items-center"
>
{imageUrls.length > 0 &&
imageUrls.map((img) => (
<SwiperSlide className="w-full flex justify-center items-center" key={img}>
<Image
src={encodeURI(img)}
alt="이미지"
width={150}
height={150}
className="object-contain w-full max-h-[400px]"
/>
<div className="relative w-full h-[400px] flex items-center justify-center">
{!loadedImages.has(img) && (
<div className="absolute inset-0 flex items-center justify-center bg-gray/80 rounded-lg">
<div className="w-8 h-8 border-4 border-secondary border-t-tertiary rounded-full animate-spin"></div>
</div>
)}
<Image
src={img}
alt="이미지"
width={800}
height={600}
quality={90}
priority
onLoad={() => handleImageLoad(img)}
onError={() => handleImageLoad(img)}
className={`object-contain w-full max-h-[400px] transition-opacity duration-300 ${
loadedImages.has(img) ? 'opacity-100' : 'opacity-0'
}`}
style={{ width: 'auto', height: 'auto' }}
/>
</div>
</SwiperSlide>
))}
</Swiper>
Expand Down
5 changes: 2 additions & 3 deletions src/domains/community/detail/tab/DetailTabDesktop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { useParams } from 'next/navigation';
import { CommentType } from '../../types/post';

type Props = {
likeCount: number;
likeCount: number | undefined;
commentCount: number;
commentRef: RefObject<HTMLElement | null>;
like: boolean;
like: boolean | null;
onLikeToggle: () => void;
title: string;
imageUrls: string[];
Expand All @@ -27,7 +27,6 @@ interface Meta {

function DetailTabDesktop({
likeCount,
commentCount,
commentRef,
like,
onLikeToggle,
Expand Down
2 changes: 1 addition & 1 deletion src/domains/community/detail/tab/DetailTabMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useParams } from 'next/navigation';

type Props = {
likeCount: number;
like: boolean;
like: boolean | null;
onLikeToggle: () => void;
title: string;
imageUrls: string[];
Expand Down
16 changes: 11 additions & 5 deletions src/domains/community/main/Community.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ function Community() {
const newPosts = await fetchPostByTab({
category,
filter,
lastLikeCount,
lastCommentCount,
});

if (!newPosts || newPosts.length === 0) {
Expand All @@ -55,6 +53,9 @@ function Community() {
} else {
setPosts(newPosts);
}
} catch (error) {
console.error('게시글 로딩 실패:', error);
setPosts([]);
} finally {
setIsLoading(false);
}
Expand All @@ -63,7 +64,6 @@ function Community() {
const loadMorePosts = async (lastPostId: number) => {
if (isEnd || isLoading) return;
if (!posts || posts.length === 0) return;
console.log('시작', lastPostId);

if (lastLoadedId === lastPostId) return;
setLastLoadedId(lastPostId);
Expand All @@ -81,8 +81,14 @@ function Community() {
if (!newPosts || newPosts?.length === 0) {
setIsEnd(true);
} else {
setPosts((prev) => [...(prev ?? []), ...(newPosts ?? [])]);
setPosts((prev) => {
const existingIds = new Set(prev?.map((p) => p.postId));
const filtered = newPosts.filter((p) => !existingIds.has(p.postId));
return [...(prev || []), ...filtered];
});
}
} catch (error) {
console.error('추가 게시글 로딩 실패:', error);
} finally {
setIsLoading(false);
}
Expand All @@ -94,7 +100,7 @@ function Community() {
aria-label="탭과 글쓰기"
className="flex justify-between item-center sm:flex-row flex-col gap-4 mt-1"
>
<CommunityTab setPosts={setPosts} setIsLoading={setIsLoading} setIsEnd={setIsEnd} />
<CommunityTab />
<WriteBtn />
</section>

Expand Down
Loading