Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 2 additions & 1 deletion apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-error-boundary": "^6.0.0",
"react-router-dom": "^7.8.2"
"react-router-dom": "^7.8.2",
"lottie-react": "^2.4.1"
},
"devDependencies": {
"@eslint/js": "^9.33.0",
Expand Down
1 change: 1 addition & 0 deletions apps/client/src/assets/5_chippiface.json

Large diffs are not rendered by default.

42 changes: 31 additions & 11 deletions apps/client/src/pages/level/Level.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Icon } from '@pinback/design-system/icons';
import { cn } from '@pinback/design-system/utils';
import LevelScene from '@pages/level/components/LevelScene';
import TreeStatusCard from '@pages/level/components/TreeStatusCard';
import { getTreeLevel } from '@shared/utils/treeLevel';
import { TreeLevel } from '@pages/level/types/treeLevelType';
import { Badge } from '@pinback/design-system/ui';
import { useGetArcons } from '@shared/apis/queries';
import { lazy, Suspense } from 'react';
import { Balloon } from '@shared/components/balloon/Balloon';

const LevelInfoCard = lazy(
() => import('@pages/level/components/LevelInfoCard')
);
Expand All @@ -15,8 +16,8 @@ const NextAcornTime = lazy(() => import('./components/NextAcornTime'));
export default function Level() {
const { data, isPending, isError } = useGetArcons();

if (isPending) return <div></div>;
if (isError) return <div></div>;
if (isPending) return <div />;
if (isError) return <div />;

const acornCount = data.acornCount;
const nextAcornTime = data.nextRemind;
Expand All @@ -27,22 +28,35 @@ export default function Level() {
const defaultLevel: TreeLevel = 1;
const level = isPending ? defaultLevel : (info.level as TreeLevel);

const balloonText =
acornCount >= 7
? '도토리를 모두 모았어요!'
: `다음 레벨까지 저장한 북마크 ${
acornCount === 0 || acornCount % 2 === 0 ? 1 : 2
}개 다시 읽기`;

return (
<div className={cn('bg-subcolor mx-auto h-dvh w-full overflow-hidden')}>
<div className="relative h-full w-full overflow-hidden rounded-[1.2rem]">
<LevelScene level={level} />

<div className="absolute inset-0">
<div className="flex flex-col items-start gap-[2rem] px-[8rem] py-[5.2rem]">
<div className="flex flex-row items-center gap-[0.8rem]">
<h1 className="head3 text-font-black-1">치삐의 지식나무 숲</h1>

<div className="relative items-center">
<button
type="button"
className="peer flex items-center justify-center p-[0.4rem]"
className="peer flex items-center justify-center gap-[4px] p-[0.4rem]"
aria-describedby="level-info-card"
>
<Icon name="ic_info" width={20} height={20} />
<span className="body3-r text-font-gray-3">
치삐의 지식나무 숲, 어떻게하면 성장하나요?
</span>
</button>

<div
id="level-info-card"
className={cn(
Expand All @@ -59,15 +73,21 @@ export default function Level() {
</div>
</div>

<Badge
text="오늘 모은 도토리 개수"
countNum={acornCount}
isActive={true}
/>
<div className="flex">
<TreeStatusCard acorns={acornCount} />
<div className="relative inline-block">
<Badge
text="오늘 모은 도토리 개수"
countNum={acornCount}
isActive={true}
/>

<div className="absolute left-1/2 top-full z-[3] mt-[0.8rem] -translate-x-[53px]">
<Balloon variant="gray" side="top">
<span className="caption1-m text-white">{balloonText}</span>
</Balloon>
</div>
</div>
</div>

{isLevel5 && (
<Suspense fallback={null}>
<NextAcornTime
Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/shared/components/balloon/Balloon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function Balloon({
<div className="relative inline-block">
<div
className={cn(
'relative flex items-start gap-3 rounded-[4px] p-[1.2rem]',
'relative flex w-full items-start gap-3 whitespace-nowrap rounded-[5.5px] px-[1.2rem] py-[0.8rem]',
variantStyle[variant]
)}
>
Expand Down
75 changes: 63 additions & 12 deletions apps/client/src/shared/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import Lottie from 'lottie-react';
import Chippiface from '@assets/5_chippiface.json';

import { Icon } from '@pinback/design-system/icons';
import SideItem from './SideItem';
import AccordionItem from './AccordionItem';
Expand All @@ -19,16 +22,21 @@ import {
useGetGoogleProfile,
useGetMyProfile,
} from '@shared/apis/queries';
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import ProfilePopupPortal from '../profilePopup/ProfilePopupPortal';
import { AutoDismissToast } from '@pinback/design-system/ui';
import { Balloon } from '@shared/components/balloon/Balloon';

export function Sidebar() {
const [newCategoryName, setNewCategoryName] = useState('');
const [toastIsOpen, setToastIsOpen] = useState(false);
const [profileOpen, setProfileOpen] = useState(false);

const [acornToastOpen, setAcornToastOpen] = useState(false);
const [acornToastKey, setAcornToastKey] = useState(0);
const prevAcornRef = useRef<number | null>(null);
const queryClient = useQueryClient();
const navigate = useNavigate();

Expand Down Expand Up @@ -133,6 +141,22 @@ export function Sidebar() {
const categoryCount = categories?.categories?.length ?? 0;
const canCreateMore = categoryCount < MAX_CATEGORIES;

useEffect(() => {
if (isPending) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isPending이 무엇에 대한 것인지 몰라서, 위에서 rename 해주는 것이 좋을 것 같아요!


if (prevAcornRef.current === null) {
prevAcornRef.current = acornCount;
return;
}

if (acornCount > prevAcornRef.current) {
setAcornToastOpen(true);
setAcornToastKey((k) => k + 1);
}

prevAcornRef.current = acornCount;
}, [acornCount, isPending]);

return (
<aside className="bg-white-bg sticky top-0 h-screen w-[24rem] border-r border-gray-300">
<div className="flex h-full flex-col px-[0.8rem]">
Expand Down Expand Up @@ -238,25 +262,52 @@ export function Sidebar() {
/>
</div>

<footer className="pb-[2.8rem] pt-[1.2rem]">
<footer className="relative pb-[2.8rem] pt-[1.2rem]">
{isPending ? (
<div className="h-[6.2rem] w-full animate-pulse rounded-[0.4rem] border bg-gray-100 p-[0.8rem]" />
) : (
<MyLevelItem
acorns={acornCount}
isActive={activeTab === 'level'}
onClick={() => {
closeMenu();
setSelectedCategoryId(null);
goLevel();
}}
/>
<>
{acornToastOpen && (
<div className="absolute bottom-[10.2rem] left-1/2 z-[50] -translate-x-1/2">
<AutoDismissToast
key={acornToastKey}
duration={3000}
fadeMs={200}
onClose={() => setAcornToastOpen(false)}
>
<Balloon variant="main" side="bottom">
<div className="flex w-[20rem] items-center gap-[1.2rem]">
<Lottie
animationData={Chippiface}
loop
autoplay
className="h-[4rem] w-[4rem] shrink-0"
/>
<div className="caption2-m flex flex-col text-white">
<span>치삐가 방금</span>
<span>도토리 1개를 모았어요!</span>
</div>
</div>
</Balloon>
</AutoDismissToast>
</div>
)}

<MyLevelItem
acorns={acornCount}
isActive={activeTab === 'level'}
onClick={() => {
closeMenu();
setSelectedCategoryId(null);
goLevel();
}}
/>
</>
)}
</footer>
</div>

{/* 팝업 영역 */}

<ProfilePopupPortal
open={profileOpen}
onClose={() => setProfileOpen(false)}
Expand Down
Loading