Skip to content

Commit 0f89fa1

Browse files
committed
Merge branch 'dev' into design/main#11
2 parents a89d685 + 1dbeb90 commit 0f89fa1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1092
-123
lines changed

src/app/layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import KaKaoScript from './api/kakao/KaKaoScript';
88
import 'swiper/css';
99
import 'swiper/css/navigation';
1010
import 'swiper/css/pagination';
11-
import Provider from '@/shared/api/Provider';
11+
1212
import ClientInitHook from '@/domains/login/components/ClientInitHook';
13+
import Provider from '@/shared/provider/Provider';
1314

1415
export const metadata: Metadata = {
1516
title: { default: 'SSOUL', template: 'SSOUL | %s' },

src/app/page.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import Landing from '@/domains/shared/components/3d/Landing';
22

3+
import MainSlide from '@/domains/main/components/mainSlide/components/MainSlide';
4+
35
export default function Home() {
4-
return <Landing />;
6+
return (
7+
<div className="page-layout max-w-full">
8+
<Landing />
9+
<MainSlide />
10+
</div>
11+
);
512
}

src/app/recipe/[id]/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getApi } from '@/app/api/config/appConfig';
2-
import DetailMain from '@/domains/recipe/details/DetailMain';
2+
import DetailMain from '@/domains/recipe/components/details/DetailMain';
3+
34
import StarBg from '@/domains/shared/components/star-bg/StarBg';
45
import { Metadata } from 'next';
56

src/domains/community/api/fetchComment.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ export const postComments = async (postId: number | ParamValue, content: string)
3232
credentials: 'include',
3333
body: JSON.stringify({ content }),
3434
});
35-
3635
const text = await res.text();
3736

3837
if (!res.ok) {

src/domains/login/components/ClientInitHook.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,29 @@
22

33
import { useFetchInterceptor } from '@/shared/hook/useFetchInterceptor';
44
import { useIdleLogout } from '../hook/useIdleLogout';
5-
import { useEffect } from 'react';
5+
import { useEffect, useRef } from 'react';
66
import { useAuthStore } from '@/domains/shared/store/auth';
77

88
function ClientInitHook() {
9-
const checkAuth = useAuthStore((state) => state.checkAuth);
9+
const { isAuthChecked } = useAuthStore();
10+
const checkAuthRef = useRef(useAuthStore.getState().checkAuth);
1011

1112
useIdleLogout();
13+
1214
useFetchInterceptor();
1315

1416
useEffect(() => {
15-
checkAuth();
16-
}, [checkAuth]);
17+
// ref를 최신 함수로 업데이트
18+
checkAuthRef.current = useAuthStore.getState().checkAuth;
19+
});
20+
21+
useEffect(() => {
22+
if (!isAuthChecked) {
23+
checkAuthRef.current();
24+
}
25+
}, [isAuthChecked]);
26+
1727
return null;
1828
}
29+
1930
export default ClientInitHook;

src/domains/login/hook/useLoginRedirect.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const useLoginRedirect = () => {
3333

3434
if (user && preLoginPath === '/login') {
3535
router.replace('/');
36-
removeCookie('preLoginPath');
36+
setTimeout(() => removeCookie('preLoginPath'), 500);
3737
return;
3838
}
3939

@@ -42,14 +42,14 @@ export const useLoginRedirect = () => {
4242
} else if (pathname.startsWith('/login/user/success')) {
4343
toastSuccess(`${user.nickname}님 \n 로그인 성공 🎉`);
4444
router.replace(preLoginPath);
45-
removeCookie('preLoginPath');
45+
setTimeout(() => removeCookie('preLoginPath'), 500);
4646
}
4747
}, [pathname, user, loading, router, toastSuccess]);
4848

4949
const handleCloseWelcomeModal = () => {
5050
setWelcomeModalOpen(false);
5151
const preLoginPath = getCookie('preLoginPath') || '/';
52-
removeCookie('preLoginPath');
52+
setTimeout(() => removeCookie('preLoginPath'), 500);
5353
router.replace(preLoginPath);
5454
};
5555

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
'use client';
2+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
3+
import MainSlideAbv from './MainSlideAbv';
4+
5+
import gsap from 'gsap';
6+
import { ScrollTrigger } from 'gsap/ScrollTrigger';
7+
import MobileSlide from './mobile/MobileSlide';
8+
import MainSlideIntro from './MainSlideIntro';
9+
import MainSlideTest from './MainSlideTest';
10+
import MainSlideCommunity from './MainSlideCommunity';
11+
import StarBg from '@/domains/shared/components/star-bg/StarBg';
12+
13+
gsap.registerPlugin(ScrollTrigger);
14+
15+
function MainSlide() {
16+
const root = useRef<HTMLDivElement>(null);
17+
const [isMobile, setIsMobile] = useState(false);
18+
const [mounted, setMounted] = useState(false);
19+
const cleanupFnRef = useRef<(() => void) | null>(null);
20+
const resizeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
21+
22+
useEffect(() => {
23+
setIsMobile(window.innerWidth < 1024);
24+
setMounted(true);
25+
26+
const handleResize = () => {
27+
// 디바운스: resize 이벤트를 200ms 지연
28+
if (resizeTimeoutRef.current) {
29+
clearTimeout(resizeTimeoutRef.current);
30+
}
31+
32+
resizeTimeoutRef.current = setTimeout(() => {
33+
const newIsMobile = window.innerWidth < 1024;
34+
35+
// 모바일 ↔ 데스크탑 전환 시에만 cleanup 실행
36+
if (newIsMobile !== isMobile) {
37+
// GSAP을 먼저 완전히 정리
38+
if (cleanupFnRef.current) {
39+
cleanupFnRef.current();
40+
cleanupFnRef.current = null;
41+
}
42+
43+
// 상태 업데이트
44+
setIsMobile(newIsMobile);
45+
} else if (!newIsMobile) {
46+
// 데스크탑 내에서의 리사이즈 - ScrollTrigger refresh
47+
ScrollTrigger.refresh(true);
48+
}
49+
}, 200);
50+
};
51+
52+
window.addEventListener('resize', handleResize);
53+
return () => {
54+
window.removeEventListener('resize', handleResize);
55+
if (resizeTimeoutRef.current) {
56+
clearTimeout(resizeTimeoutRef.current);
57+
}
58+
if (cleanupFnRef.current) {
59+
cleanupFnRef.current();
60+
}
61+
};
62+
}, [isMobile]);
63+
64+
// GSAP 초기화 - 데스크탑에서만
65+
useLayoutEffect(() => {
66+
if (!mounted) return;
67+
if (isMobile) return;
68+
if (!root.current) return;
69+
70+
const el = root.current;
71+
const stage = el.querySelector('.stage') as HTMLElement;
72+
if (!stage) return;
73+
74+
const timer = setTimeout(() => {
75+
if (!root.current) return;
76+
77+
const ctx = gsap.context(() => {
78+
const panels = Array.from(el.querySelectorAll<HTMLElement>('.panel'));
79+
const tl = gsap.timeline({ paused: true, defaults: { ease: 'power3.inOut' } });
80+
81+
panels.forEach((panel, i) => {
82+
const c = panel.querySelector<HTMLElement>('.slide-content');
83+
if (!c) return;
84+
const stageW = () => stage.clientWidth;
85+
const contentW = () => c.getBoundingClientRect().width;
86+
87+
gsap.set(c, {
88+
x: () => stageW(),
89+
immediateRender: false,
90+
});
91+
92+
tl.to(
93+
c,
94+
{
95+
x: () => stageW() - contentW(),
96+
duration: 2,
97+
immediateRender: false,
98+
onStart: () => c.classList.remove('invisible'),
99+
},
100+
i
101+
);
102+
});
103+
104+
ScrollTrigger.create({
105+
trigger: el,
106+
start: 'top top',
107+
end: `+=${panels.length * 100}%`,
108+
pin: true,
109+
scrub: true,
110+
animation: tl,
111+
invalidateOnRefresh: true,
112+
});
113+
114+
ScrollTrigger.refresh();
115+
}, root);
116+
117+
cleanupFnRef.current = () => {
118+
const allTriggers = ScrollTrigger.getAll();
119+
allTriggers.forEach((st) => {
120+
if (st.trigger === el || el.contains(st.trigger as Node)) {
121+
st.kill(true);
122+
}
123+
});
124+
125+
try {
126+
ctx.revert();
127+
} catch {}
128+
129+
const pinSpacers = document.querySelectorAll('.pin-spacer');
130+
pinSpacers.forEach((spacer) => {
131+
if (spacer.contains(el) || el.contains(spacer)) {
132+
const child = spacer.querySelector('section');
133+
if (child && spacer.parentElement) {
134+
spacer.parentElement.appendChild(child);
135+
}
136+
spacer.remove();
137+
}
138+
});
139+
};
140+
}, 50);
141+
142+
return () => {
143+
clearTimeout(timer);
144+
if (cleanupFnRef.current) {
145+
cleanupFnRef.current();
146+
cleanupFnRef.current = null;
147+
}
148+
};
149+
}, [isMobile, mounted]);
150+
// SSR 방지
151+
if (!mounted) {
152+
return null;
153+
}
154+
155+
return (
156+
<>
157+
{isMobile ? (
158+
<StarBg className="">
159+
<MobileSlide key="mobile" />
160+
</StarBg>
161+
) : (
162+
<StarBg className="bg-fixed">
163+
<section key="desktop" ref={root} className="stage h-screen">
164+
<div className="stage relative w-full h-full overflow-hidden">
165+
<div className="panel absolute inset-0">
166+
<MainSlideIntro />
167+
</div>
168+
<div className="panel absolute inset-0">
169+
<MainSlideTest />
170+
</div>
171+
<div className="panel absolute inset-0">
172+
<MainSlideCommunity />
173+
</div>
174+
<div className="panel absolute inset-0">
175+
<MainSlideAbv />
176+
</div>
177+
</div>
178+
</section>
179+
</StarBg>
180+
)}
181+
</>
182+
);
183+
}
184+
185+
export default MainSlide;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import Ssury1 from '@/shared/assets/ssury/ssury_level1.webp';
2+
import Ssury2 from '@/shared/assets/ssury/ssury_level2.webp';
3+
import Ssury3 from '@/shared/assets/ssury/ssury_level3.webp';
4+
import Ssury4 from '@/shared/assets/ssury/ssury_level4.webp';
5+
import Ssury5 from '@/shared/assets/ssury/ssury_level5.webp';
6+
import Ssury6 from '@/shared/assets/ssury/ssury_level6.webp';
7+
import MainSsuryDrunk from './MainSsuryDrunk';
8+
9+
function MainSlideAbv() {
10+
const SSURY_DRUNK = [
11+
{
12+
id: 1,
13+
src: Ssury1,
14+
abv: 5,
15+
},
16+
{
17+
id: 2,
18+
src: Ssury2,
19+
abv: 11,
20+
},
21+
{
22+
id: 3,
23+
src: Ssury3,
24+
abv: 26,
25+
},
26+
{
27+
id: 4,
28+
src: Ssury4,
29+
abv: 46,
30+
},
31+
{
32+
id: 5,
33+
src: Ssury5,
34+
abv: 66,
35+
},
36+
{
37+
id: 6,
38+
src: Ssury6,
39+
abv: 86,
40+
},
41+
];
42+
43+
return (
44+
<section className="slide-content invisible w-1/2 h-full bg-[#84739e] rounded-tl-[30px] rounded-bl-[30px] p-15 flex flex-col justify-center">
45+
<div className="flex flex-col gap-15">
46+
<span className="font-black text-[32px]">3</span>
47+
<div className="flex flex-col gap-5">
48+
<h2 className="text-5xl font-black text-shadow-[0_4px_6px_rgb(255_255_255_/0.25)]">
49+
내 알콜도수 UP
50+
</h2>
51+
<p className="text-xl xl:text-2xl font-normal leading-[1.5]">
52+
5도 부터 시작하는 내 알콜도수 <br />글 작성,댓글,좋아요 / 킵으로 알콜도수 UP! <br />
53+
알콜도수에 따라 변하는 쑤리(SSURY)를 보는 재미도 있어요.
54+
</p>
55+
</div>
56+
<div className=" h-[190px] flex flex-col gap-2">
57+
<ul className="flex gap-[5%] xl:gap-[10%]">
58+
{SSURY_DRUNK.map(({ id, src, abv }) => (
59+
<li key={id}>
60+
<MainSsuryDrunk src={src} abv={abv} />
61+
</li>
62+
))}
63+
</ul>
64+
<div className="w-full h-3 border border-gray rounded-full relative">
65+
<span className="absolute top-0 left-0 h-full rounded-full bg-gradient-to-r from-[#FFCA8D] to-[#FA2424] w-1/2"></span>
66+
</div>
67+
</div>
68+
</div>
69+
</section>
70+
);
71+
}
72+
export default MainSlideAbv;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
function MainSlideCommunity() {
2+
return (
3+
<section className="slide-content invisible w-3/5 h-full p-15 bg-[#77688d] rounded-tl-[30px] rounded-bl-[30px] flex flex-col justify-center">
4+
<div className="flex flex-col justify-center">
5+
<div className="flex flex-col gap-15">
6+
<span className="text-[32px] font-black">2</span>
7+
<header className="flex flex-col gap-5">
8+
<h2 className="text-5xl text-secondary font-black text-shadow-[0_4px_6px_rgb(255_255_255_/0.25)]">
9+
술술 즐기는, 커뮤니티
10+
</h2>
11+
<p className="text-xl pb-[250px] xl:text-2xl text-secondary font-normal leading-[1.5]">
12+
칵테일에 대해 물어볼 곳이 없어 목이 마른 당신! <br />
13+
초보자부터 애호가까지, Ssoul에서는 누구나 칵테일 이야기를 나눌 수 있어요.
14+
<br />
15+
회원들과 소통하면 내 칵테일 솜씨를 뽐내보세요.
16+
</p>
17+
</header>
18+
</div>
19+
</div>
20+
</section>
21+
);
22+
}
23+
export default MainSlideCommunity;

0 commit comments

Comments
 (0)