Skip to content

Commit

Permalink
feat: 마이페이지 디자인 수정 및 설정 페이지 퍼블리싱 (#82)
Browse files Browse the repository at this point in the history
* feat: 마이페이지 디자인 수정 및 추가

* feat: 설정 페이지 퍼블리싱

* feat: 로그아웃 Dialog
  • Loading branch information
Youjiiin authored Feb 3, 2025
1 parent 4dc70cc commit 49d513d
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 78 deletions.
9 changes: 9 additions & 0 deletions packages/design-system/Caption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ReactNode } from "react";

export const Caption = ({ children }: { children: ReactNode }) => {
return (
<div className="rounded-full w-fit px-[10px] py-[2px] bg-gradient-to-r from-main4-gradient-from to-main4-gradient-to">
{children}
</div>
);
};
26 changes: 0 additions & 26 deletions src/entities/user/components/ExpProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,11 @@
import * as ProgressPrimitives from "@radix-ui/react-progress";
import { textVariants } from "@repo/design-system/Text";
import { cn } from "@repo/design-system/cn";
import { Flex } from "@repo/ui/Flex";
import { motion } from "motion/react";
import type { ComponentProps } from "react";
import useAnimationCounter from "~/shared/hooks/useAnimationCounter";

export const ExpProgressBar = (
props: ComponentProps<typeof ProgressPrimitives.Root> & { totalExp: number; currentExp: number },
) => {
const { children, value, totalExp, currentExp, style, ...rest } = props;
const currentExpText = useAnimationCounter({
to: currentExp,
start: 0,
duration: 1,
delay: 0.3,
});

const totalExpText = useAnimationCounter({
to: totalExp,
start: 0,
duration: 1,
delay: 0.3,
});

return (
<ProgressPrimitives.Root
Expand All @@ -45,15 +28,6 @@ export const ExpProgressBar = (
transition={{ duration: 0.5, ease: "easeInOut" }}
/>
</ProgressPrimitives.Indicator>

<Flex className=" absolute gap-x-[8px] items-center top-0 left-0 z-10 px-[12px] w-full h-full">
<motion.span className={cn(textVariants({ variant: "caption/12_m" }), "text-white")}>
{currentExpText}
</motion.span>
<motion.span className={cn(" text-[10px] font-light text-white")}>
{totalExpText}
</motion.span>
</Flex>
{children}
</ProgressPrimitives.Root>
);
Expand Down
115 changes: 115 additions & 0 deletions src/features/settingsMenu/settingsMenuItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { Button } from "@repo/design-system/Button";
import { Dialog } from "@repo/design-system/Dialog";
import { MaxWidthBox } from "@repo/design-system/MaxWidthBox";
import { Text, textVariants } from "@repo/design-system/Text";
import { cn } from "@repo/design-system/cn";
import { Flex } from "@repo/ui/Flex";
import { JustifyBetween } from "@repo/ui/JustifyBetween";
import { Stack } from "@repo/ui/Stack";
import { overlay } from "overlay-kit";
import { useState } from "react";
import { openExternalUrl } from "~/shared/utils/openExternalUrl";

interface SettingMenuItemProps {
type: "toggle" | "exit" | "link";
menuName: string;
onClick?: () => void;
link?: string;
defaultValue?: boolean;
}

export const SettingMenuItem = ({
type,
menuName,
onClick,
link,
defaultValue,
}: SettingMenuItemProps) => {
const [isOn, setIsOn] = useState(defaultValue || false);

const handleClick = () => {
if (type === "toggle") {
setIsOn(!isOn);
} else if (type === "exit") {
_LogoutOverlay.open();
} else if (type === "link" && link) {
openExternalUrl(link);
}
};

return (
<Flex className="py-4 flex justify-between items-center cursor-pointer" onClick={handleClick}>
<Text
variant="body/16_sb"
className={cn(type === "exit" ? "text-gray-400" : "text-gray-800")}
>
{menuName}
</Text>
{type === "toggle" && (
<button
onClick={handleClick}
className={cn(
textVariants({ variant: "body/16_sb" }),
isOn ? "text-green" : "text-gray-500",
)}
>
{isOn ? "ON" : "OFF"}
</button>
)}
</Flex>
);
};

const _LogoutOverlay = {
open: () => {
return overlay.open(({ isOpen, close, unmount }) => {
return (
<LogoutDialog
isOpen={isOpen}
onOpenChange={() => {
close();
setTimeout(unmount, 2000);
}}
/>
);
});
},
};

const LogoutDialog = (props: {
isOpen?: boolean;
onOpenChange?: (bool: boolean) => void;
}) => {
const { isOpen, onOpenChange } = props;
return (
<Dialog.Root open={isOpen} onOpenChange={onOpenChange}>
<Dialog.Portal>
<Dialog.Overlay className="z-[6]" />
<Dialog.Content>
<Dialog.Title className=" sr-only">로그아웃</Dialog.Title>
<Dialog.Description className=" sr-only">로그아웃을 합니다.</Dialog.Description>
<MaxWidthBox className="flex z-[7] justify-center items-center fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] px-6">
<Stack className=" bg-white rounded-[14px] pt-10 pb-4 px-4 justify-center items-center">
<Text className=" whitespace-pre-wrap text-gray-800" variant={"title/20_sb"}>
정말 로그아웃할까요?
</Text>
<JustifyBetween className=" w-full mt-9 gap-2">
<Button
size={"small"}
className=" w-full rounded-[14px]"
variant={"gray"}
onClick={() => onOpenChange?.(false)}
>
아니요
</Button>
<Button size={"small"} className=" w-full rounded-[14px]" variant={"warning"}>
로그아웃
</Button>
</JustifyBetween>
</Stack>
</MaxWidthBox>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
};
26 changes: 1 addition & 25 deletions src/features/userExp/HabitSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,7 @@ import { useSuspenseQuery } from "@tanstack/react-query";
import { isAfter, isSameDay } from "date-fns";
import { useMemo } from "react";
import { useTestUserQueryOptions } from "~/entities/user/api/getTestUser";
import { ExpProgressBar } from "~/entities/user/components/ExpProgressBar";
import { HabitCalendar } from "~/entities/user/components/HabitCalendar";
import { calculateUserLevel, getExpPercentage, getGoalExp } from "~/entities/user/model/user.model";

const ExpSection = (props: { exp: number; goalExp: number; percentage: number; level: string }) => {
const { exp, goalExp, percentage, level } = props;
return (
<Stack className=" w-full px-[20px]">
<Flex className=" gap-x-[16px]">
<Text variant={"body/18_sb"} className=" text-nowrap text-[#393F46]">
{level}
</Text>

<ExpProgressBar value={percentage} totalExp={goalExp} currentExp={exp} />
</Flex>
</Stack>
);
};

const HabitBar = (props: { normalText: string; boldText: string }) => {
const { normalText, boldText } = props;
Expand Down Expand Up @@ -99,7 +82,6 @@ const getDayName = (date: Date) => {
const Fallback = () => {
return (
<>
<ExpSection exp={0} goalExp={0} percentage={0} level={`레벨 1`} />
<Spacing className=" h-[12px]" />
<Stack className=" px-[20px] w-full animate-pulse">
<Text variant={"body/18_sb"} className=" mb-[10px] text-[#393F46]">
Expand Down Expand Up @@ -137,17 +119,11 @@ export const HabitSection = wrap
const boldText =
successCount > 0 ? `${successCount}일 연속 습관 쌓는 중!` : "습관 쌓을 준비 중";

const level = calculateUserLevel(user.exp);
const goalExp = getGoalExp(level);
const exp = user.exp + 50;
const percentage = getExpPercentage(exp, goalExp);

const weekDates = useMemo(() => getWeekDates(new Date()), []);

return (
<>
<ExpSection exp={exp} goalExp={goalExp} percentage={percentage} level={`레벨 ${level}`} />
<Spacing className=" h-[12px]" />
<Spacing className=" h-[17px]" />
<Stack className=" px-[20px]">
<Text variant={"body/18_sb"} className=" mb-[10px] text-[#393F46]">
습관 쌓기
Expand Down
114 changes: 114 additions & 0 deletions src/features/userInfo/UserInfoSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { AspectRatio } from "@repo/design-system/AspectRatio";
import { Caption } from "@repo/design-system/Caption";
import { Image } from "@repo/design-system/Image";
import { Text } from "@repo/design-system/Text";
import { Flex } from "@repo/ui/Flex";
import { Stack } from "@repo/ui/Stack";
import { wrap } from "@suspensive/react";
import { useSuspenseQuery } from "@tanstack/react-query";
import { motion } from "motion/react";
import { useTestUserQueryOptions } from "~/entities/user/api/getTestUser";
import { ExpProgressBar } from "~/entities/user/components/ExpProgressBar";
import { calculateUserLevel, getExpPercentage, getGoalExp } from "~/entities/user/model/user.model";

const ExpSection = (props: { exp: number; goalExp: number; percentage: number; level: string }) => {
const { exp, goalExp, percentage, level } = props;
const formatExp = percentage.toFixed(2);
return (
<Stack className=" w-full px-[20px]">
<Flex className=" gap-x-[6px] items-center">
<Text variant={"body/18_sb"} className=" text-nowrap text-gray-100">
{level}
</Text>

<ExpProgressBar value={percentage} totalExp={goalExp} currentExp={exp} />
<Text variant={"body/14_sb"} className="text-white-violet ml-[1px]">
{formatExp}%
</Text>
</Flex>
</Stack>
);
};

const Fallback = () => {
return (
<Stack className=" w-full bg-navy">
<motion.div
className=" w-full h-full"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5, delay: 0.3 }}
>
<AspectRatio ratio={375 / 343} className=" flex justify-center items-center">
<Stack>
<Stack>
<Text>머큐리와 함께한 지 000일</Text>
<Text></Text>
</Stack>
<Image src={HOME_ASSETS.HOME_MERCURY} alt="mercury character" objectfit={"fill"} />
<ExpSection exp={0} goalExp={0} percentage={0} level={`레벨 1`} />
</Stack>
</AspectRatio>
</motion.div>
</Stack>
);
};

export const MainSection = wrap
.Suspense({
fallback: <Fallback />,
})
.on(() => {
const { data: user } = useSuspenseQuery(useTestUserQueryOptions());

const nickname = `테스터${user.nickname.slice(10, 14)}`;

const level = calculateUserLevel(user.exp);
const goalExp = getGoalExp(level);
const exp = user.exp + 50;
const percentage = getExpPercentage(exp, goalExp);

return (
<Stack className=" w-full bg-navy justify-between">
<motion.div
className=" w-full h-full"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5, delay: 0.3 }}
>
<AspectRatio ratio={375 / 392} className=" flex justify-center items-center">
<Stack className="gap-[25px]">
<Stack className="px-5 gap-[6px]">
<Caption>
<Text className="text-white" variant={"caption/12_m"}>
머큐리와 함께한 지 000일
</Text>
</Caption>
<Text variant={"title/25_b"} className="text-gray-100">
{nickname}
</Text>
</Stack>

<Image
src={HOME_ASSETS.HOME_MERCURY}
alt="mercury character"
objectfit={"fill"}
className="px-[36px]"
/>

<ExpSection
exp={exp}
goalExp={goalExp}
percentage={percentage}
level={`레벨 ${level}`}
/>
</Stack>
</AspectRatio>
</motion.div>
</Stack>
);
});

const HOME_ASSETS = {
HOME_MERCURY: "/images/home/home_mercury.webp",
};
2 changes: 2 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import HomePage from "./pages/HomePage.tsx";
import Notification from "./pages/Notification.tsx";
import OnBoardingPage from "./pages/OnBoardingPage.tsx";
import ProfilePage from "./pages/ProfilePage.tsx";
import SettingPage from "./pages/SettingPage.tsx";
import TimerPage from "./pages/TimerPage.tsx";

createRoot(document.getElementById("root")!).render(
Expand Down Expand Up @@ -47,6 +48,7 @@ createRoot(document.getElementById("root")!).render(
<Route path="add-memo/:recordId" element={<BookRecordMemoAddPage />} />

<Route path="notification" element={<Notification />} />
<Route path="settings" element={<SettingPage />} />
<Route path="" element={<OnBoardingPage />} />
</Routes>
</OverlayProvider>
Expand Down
Loading

0 comments on commit 49d513d

Please sign in to comment.