From 745d669e87bbfd57102b1df43cffa582efc70db3 Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:47:39 +0900 Subject: [PATCH 01/18] =?UTF-8?q?style:=20=EB=9E=9C=EB=94=A9=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20UI=20=EB=B3=80=EA=B2=BD=20+=20=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EC=83=89=EC=83=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(public)/_components/PublicHeader.tsx | 6 +-- src/app/(public)/landing-page/LadingPage.tsx | 2 - .../_components/SectionWrapper.tsx | 11 ++--- .../_components/banner/Banner.tsx | 31 ++----------- .../_components/feature/Feature.tsx | 46 ++++++++++++------- .../landing-page/_components/info/Info.tsx | 1 - src/app/globals.css | 27 ++++++++--- src/components/button/Button.tsx | 40 ++++++++++++---- 8 files changed, 91 insertions(+), 73 deletions(-) diff --git a/src/app/(public)/_components/PublicHeader.tsx b/src/app/(public)/_components/PublicHeader.tsx index 8f99fab..a0497bd 100644 --- a/src/app/(public)/_components/PublicHeader.tsx +++ b/src/app/(public)/_components/PublicHeader.tsx @@ -7,17 +7,17 @@ export default function PublicHeader() {
STACK - + PLUS
diff --git a/src/app/(public)/landing-page/LadingPage.tsx b/src/app/(public)/landing-page/LadingPage.tsx index d0e79bc..302c85c 100644 --- a/src/app/(public)/landing-page/LadingPage.tsx +++ b/src/app/(public)/landing-page/LadingPage.tsx @@ -1,11 +1,9 @@ -import InfoPage from "./_components/info/Info"; import BannerPage from "./_components/banner/Banner"; import { FeaturePage } from "./_components/feature/Feature"; export default function LandingPage() { return ( <> - diff --git a/src/app/(public)/landing-page/_components/SectionWrapper.tsx b/src/app/(public)/landing-page/_components/SectionWrapper.tsx index c131ee9..b66ff53 100644 --- a/src/app/(public)/landing-page/_components/SectionWrapper.tsx +++ b/src/app/(public)/landing-page/_components/SectionWrapper.tsx @@ -1,27 +1,22 @@ type SectionWrapperProps = { - bg?: string; - padding?: string; - title: React.ReactNode; desc: React.ReactNode; children: React.ReactNode; }; export default function SectionWrapper({ - bg, - padding, title, desc, children, }: SectionWrapperProps) { return (
-
+

{title}

-

+

{desc}

{children} diff --git a/src/app/(public)/landing-page/_components/banner/Banner.tsx b/src/app/(public)/landing-page/_components/banner/Banner.tsx index 1336f73..b879015 100644 --- a/src/app/(public)/landing-page/_components/banner/Banner.tsx +++ b/src/app/(public)/landing-page/_components/banner/Banner.tsx @@ -1,37 +1,12 @@ -"use client"; -import Button from "@/components/button/Button"; -import { useUserStore } from "@/store/useUserStore"; - export default function BannerPage() { - const { id } = useUserStore(); - return ( -
+

- 오늘 배운 영어 표현부터 + 오늘 배운 표현부터
- Stack+에 차곡차곡 쌓아보세요. + StackPlus 에 차곡차곡 쌓아보세요.

- {id ? ( - - ) : ( - - )}
); diff --git a/src/app/(public)/landing-page/_components/feature/Feature.tsx b/src/app/(public)/landing-page/_components/feature/Feature.tsx index fac0928..e8b23ed 100644 --- a/src/app/(public)/landing-page/_components/feature/Feature.tsx +++ b/src/app/(public)/landing-page/_components/feature/Feature.tsx @@ -1,29 +1,26 @@ import SectionWrapper from "@/app/(public)/landing-page/_components/SectionWrapper"; import { GoBook } from "react-icons/go"; -import { LuPencil } from "react-icons/lu"; -import { LuChartNoAxesCombined } from "react-icons/lu"; +import { LuPencil, LuChartNoAxesCombined } from "react-icons/lu"; import { FaRegBookmark } from "react-icons/fa"; import { IoSearchOutline } from "react-icons/io5"; import { FiTag } from "react-icons/fi"; +import Button from "@/components/button/Button"; const features = [ { icon: GoBook, title: "표현 등록", - description: - "영어 표현과 한국어 뜻을 간편하게 등록하세요. 예문까지 함께 저장할 수 있어요.", + description: "표현과 뜻을 간편하게 등록하세요.", }, { icon: LuPencil, title: "메모 기능", - description: - "표현마다 메모를 추가해 헷갈리는 포인트나 사용법을 기록하세요.", + description: "메모를 추가해 헷갈리는 포인트나 사용법을 기록하세요.", }, { icon: LuChartNoAxesCombined, title: "학습 기록", - description: - "일간, 월간, 연간 학습 기록을 차트로 확인하고 꾸준히 성장하세요.", + description: "일간, 월간, 연간 학습 기록을 차트로 확인하세요.", }, { icon: FaRegBookmark, @@ -45,26 +42,41 @@ const features = [ export function FeaturePage() { return ( + StackPlus가 제공하는 + 강력한 기능들을 소개합니다. + + } > -
+
{features.map((feature) => (
-
- +
+
+ +
+

+ {feature.title} +

-

{feature.title}

-

+ +

{feature.description}

))}
+ ); } diff --git a/src/app/(public)/landing-page/_components/info/Info.tsx b/src/app/(public)/landing-page/_components/info/Info.tsx index 335c32a..3e998a9 100644 --- a/src/app/(public)/landing-page/_components/info/Info.tsx +++ b/src/app/(public)/landing-page/_components/info/Info.tsx @@ -4,7 +4,6 @@ import Button from "@/components/button/Button"; export default function InfoPage() { return ( 나만의 표현장으로
diff --git a/src/app/globals.css b/src/app/globals.css index 358623f..df85993 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,13 +1,26 @@ @import "tailwindcss"; @theme { - --color-bg: #f8f8f8; - --color-point: #ceec46; - --color-text-black: #1a1a1a; - --color-text-600: #4a5565; - --color-text-700: #364153; - --font-pretendard: var(--font-pretendard); --font-sekuya: var(--font-sekuya); + + --color-background: rgb(var(--background)); + --color-foreground: rgb(var(--foreground)); + --color-border: rgb(var(--border)); + --color-point: rgb(var(--point)); +} + +:root { + --background: 248 249 250; + --foreground: 34 34 34; + --border: 229 231 235; + --point: 100 116 189; +} + +.dark { + --background: 18 18 18; + --foreground: 229 231 235; + --border: 55 65 81; + --point: 124 58 237; } *, @@ -28,6 +41,8 @@ html { } body { + @apply bg-background text-foreground; + font-family: var(--font-pretendard), sans-serif; line-height: 1.5; color: var(--color-text-black); diff --git a/src/components/button/Button.tsx b/src/components/button/Button.tsx index 00b0c58..639ed48 100644 --- a/src/components/button/Button.tsx +++ b/src/components/button/Button.tsx @@ -18,18 +18,42 @@ type ButtonProps = }); const baseStyles = - "transition-all inline-flex items-center justify-center gap-1 rounded-full text-sm text-black font-medium focus:outline-none cursor-pointer hover:-translate-y-1 group-hover:-translate-y-1"; - + "inline-flex items-center justify-center gap-1 rounded-full text-sm font-medium transition-all focus:outline-none cursor-pointer hover:-translate-y-1 group-hover:-translate-y-1"; const variantStyles: Record = { - default: "bg-point px-4 py-3.5", - outline: - "border border-point bg-white px-4 py-3.5 hover:bg-point group-hover:bg-point", - text_underline: - "p-1 hover:translate-y-0 group-hover:translate-y-0 relative after:absolute after:left-0 after:bottom-0 after:h-[2px] after:w-0 after:bg-current after:transition-all after:duration-300 hover:after:w-full", - text: "p-1 hover:translate-y-0 group-hover:translate-y-0 relative", + default: ` + bg-point text-white + px-4 py-3.5 +`, + + outline: ` + border border-point text-point + bg-transparent + px-4 py-3.5 + hover:bg-point + dark:hover:bg-point +`, + + text: ` + p-1 text-foreground + hover:translate-y-0 group-hover:translate-y-0 + `, + + text_underline: ` + relative inline-block p-1 text-foreground + hover:translate-y-0 group-hover:translate-y-0 + + after:absolute after:left-0 after:-bottom-1 + after:h-[2px] after:w-0 + after:bg-point + after:rounded-full + + after:transition-[width] after:duration-300 after:ease-out + hover:after:w-full +`, }; export default function Button(props: ButtonProps) { const { children, variant = "default", className = "" } = props; + const styles = clsx(baseStyles, variantStyles[variant], className); if ("href" in props) { From 2a98343656121725973dd38c9b65a30b1f0011ae Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:49:10 +0900 Subject: [PATCH 02/18] =?UTF-8?q?style:=20=EA=B8=80=EA=BC=B4=20=EB=B0=98?= =?UTF-8?q?=EC=9D=91=ED=98=95=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(public)/landing-page/_components/SectionWrapper.tsx | 4 ++-- src/app/(public)/landing-page/_components/banner/Banner.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/(public)/landing-page/_components/SectionWrapper.tsx b/src/app/(public)/landing-page/_components/SectionWrapper.tsx index b66ff53..f270dda 100644 --- a/src/app/(public)/landing-page/_components/SectionWrapper.tsx +++ b/src/app/(public)/landing-page/_components/SectionWrapper.tsx @@ -13,10 +13,10 @@ export default function SectionWrapper({ className={`mx-auto w-full text-center bg-background text-foreground py-30`} >
-

+

{title}

-

+

{desc}

{children} diff --git a/src/app/(public)/landing-page/_components/banner/Banner.tsx b/src/app/(public)/landing-page/_components/banner/Banner.tsx index b879015..25e1438 100644 --- a/src/app/(public)/landing-page/_components/banner/Banner.tsx +++ b/src/app/(public)/landing-page/_components/banner/Banner.tsx @@ -2,7 +2,7 @@ export default function BannerPage() { return (
-

+

오늘 배운 표현부터
StackPlus 에 차곡차곡 쌓아보세요. From 2e9ee0025bfcd470004edb8da32d7103622410a4 Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:02:06 +0900 Subject: [PATCH 03/18] =?UTF-8?q?chore:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8/?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20import=20=EC=88=9C?= =?UTF-8?q?=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../list/list-actions/ListActionsView.tsx | 6 ++-- .../_components/modal/ModalTop.tsx | 2 +- .../RecordLineChart/RecordChartToggle.tsx | 6 ++-- .../record/_components/FilterCount.tsx | 2 +- src/app/(public)/_components/title/Title.tsx | 6 ++-- src/app/(public)/join/page.tsx | 29 ++++++++++--------- .../landing-page/_components/info/Info.tsx | 26 ----------------- src/app/(public)/login/page.tsx | 13 ++++++--- src/components/empty-state/EmptyState.tsx | 2 +- src/components/layout/Footer.tsx | 2 +- 10 files changed, 39 insertions(+), 55 deletions(-) delete mode 100644 src/app/(public)/landing-page/_components/info/Info.tsx diff --git a/src/app/(protected)/_components/list/list-actions/ListActionsView.tsx b/src/app/(protected)/_components/list/list-actions/ListActionsView.tsx index 01d762a..115f71a 100644 --- a/src/app/(protected)/_components/list/list-actions/ListActionsView.tsx +++ b/src/app/(protected)/_components/list/list-actions/ListActionsView.tsx @@ -57,7 +57,7 @@ export default function ListActionsView({ className={`${buttonStyles} pointer-events-none`} disabled > - + )}
@@ -84,7 +84,7 @@ export default function ListActionsView({ className={`${buttonStyles}`} onClick={toggleDropdown} > - + {isDropdownOpen && (
    diff --git a/src/app/(protected)/dashboard/_components/record/RecordLineChart/RecordChartToggle.tsx b/src/app/(protected)/dashboard/_components/record/RecordLineChart/RecordChartToggle.tsx index 7e76bc9..e699ef6 100644 --- a/src/app/(protected)/dashboard/_components/record/RecordLineChart/RecordChartToggle.tsx +++ b/src/app/(protected)/dashboard/_components/record/RecordLineChart/RecordChartToggle.tsx @@ -14,7 +14,7 @@ export function RecordChartToggle({ period, onChange }: Props) { className={ period === "daily" ? "font-medium text-black after:w-full" - : "text-gray-500 hover:text-black" + : "text-gray-600 hover:text-black" } > 일간 @@ -26,7 +26,7 @@ export function RecordChartToggle({ period, onChange }: Props) { className={ period === "monthly" ? "font-medium text-black after:w-full" - : "text-gray-500 hover:text-black" + : "text-gray-600 hover:text-black" } > 월간 @@ -38,7 +38,7 @@ export function RecordChartToggle({ period, onChange }: Props) { className={ period === "yearly" ? "font-medium text-black after:w-full" - : "text-gray-500 hover:text-black" + : "text-gray-600 hover:text-black" } > 연간 diff --git a/src/app/(protected)/record/_components/FilterCount.tsx b/src/app/(protected)/record/_components/FilterCount.tsx index b9d61bf..6bd6362 100644 --- a/src/app/(protected)/record/_components/FilterCount.tsx +++ b/src/app/(protected)/record/_components/FilterCount.tsx @@ -7,7 +7,7 @@ type FilterCountItem = { export function FilterCount({ total, filterKey }: FilterCountItem) { return ( -

    +

    {FILTER_LABELS[filterKey]} {total}개

    ); diff --git a/src/app/(public)/_components/title/Title.tsx b/src/app/(public)/_components/title/Title.tsx index ad81e9c..daa128d 100644 --- a/src/app/(public)/_components/title/Title.tsx +++ b/src/app/(public)/_components/title/Title.tsx @@ -1,12 +1,14 @@ type TitleProps = { title: string; - desc: string; + desc: React.ReactNode; }; export default function Title({ title, desc }: TitleProps) { return (
    -

    {title}

    +

    + {title} +

    {desc}

    diff --git a/src/app/(public)/join/page.tsx b/src/app/(public)/join/page.tsx index 45287ea..6043e14 100644 --- a/src/app/(public)/join/page.tsx +++ b/src/app/(public)/join/page.tsx @@ -19,6 +19,11 @@ type JoinInputs = { export default function JoinPage() { const router = useRouter(); + const [passwordShow, setPasswordShow] = useState({ + password: false, + confirmPassword: false, + }); + const { register, handleSubmit, @@ -26,11 +31,6 @@ export default function JoinPage() { getValues, } = useForm(); - const [passwordShow, setPasswordShow] = useState({ - password: false, - confirmPassword: false, - }); - const onSubmit: SubmitHandler = async (formData) => { try { await signUp({ @@ -38,14 +38,13 @@ export default function JoinPage() { nickname: formData.nickname, password: formData.password, }); - router.push("/login"); toast.success("로그인이 되었습니다."); + router.push("/login"); } catch (error) { if (error instanceof Error) { toast.error(error.message); - } else { - toast.error("회원가입 중 알 수 없는 오류가 발생했습니다."); } + toast.error("회원가입 중 오류가 발생했습니다."); } }; @@ -53,10 +52,14 @@ export default function JoinPage() {
    + <span className="font-sekuya">stack plus</span>함께 표현을 + 쌓아가세요. + </> + } /> - <form className="mt-8 grid gap-5 font-pretendard" onSubmit={handleSubmit(onSubmit)} @@ -111,7 +114,7 @@ export default function JoinPage() { "비밀번호가 일치하지 않습니다", })} placeholder="비밀번호를 다시 입력하세요." - errors={errors.passwordConfirm?.message} // 여기 주목 + errors={errors.passwordConfirm?.message} > <FaRegEyeSlash onClick={() => { @@ -128,7 +131,7 @@ export default function JoinPage() { </Button> </form> - <div className="mt-6 text-center text-sm text-gray-500"> + <div className="mt-6 text-center text-sm text-gray-600"> 이미 계정이 있으신가요? <Button variant="text_underline" href="/login" className="ml-1"> 로그인 diff --git a/src/app/(public)/landing-page/_components/info/Info.tsx b/src/app/(public)/landing-page/_components/info/Info.tsx deleted file mode 100644 index 3e998a9..0000000 --- a/src/app/(public)/landing-page/_components/info/Info.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import SectionWrapper from "@/app/(public)/landing-page/_components/SectionWrapper"; -import Button from "@/components/button/Button"; - -export default function InfoPage() { - return ( - <SectionWrapper - title={ - <> - 나만의 표현장으로 <br /> - <span className="text-point">영어 실력</span>을 쌓아보세요. - </> - } - desc={ - <> - 표현를 등록하고, 예문과 메모를 추가하고, 학습 기록을 추적하세요. - <br /> - Stack Plus와 함께 효율적인 영어 학습을 시작하세요. - </> - } - > - <Button href="./dashboard" type="button" className="mt-10 w-40 mx-auto"> - 시작하기 - </Button> - </SectionWrapper> - ); -} diff --git a/src/app/(public)/login/page.tsx b/src/app/(public)/login/page.tsx index 2d0d518..5d5af81 100644 --- a/src/app/(public)/login/page.tsx +++ b/src/app/(public)/login/page.tsx @@ -37,7 +37,7 @@ export default function LoginPage() { if (error instanceof Error) { toast.error(error.message); } - toast.error("알 수 없는 오류가 발생했습니다."); + toast.error("로그인 중 오류가 발생했습니다."); } }; @@ -45,8 +45,13 @@ export default function LoginPage() { <div className="flex min-h-screen items-center justify-center bg-gray-50 px-6"> <div className="w-full max-w-lg rounded-2xl bg-white p-10 shadow-sm"> <Title - title="Welcome!" - desc="stack plus를 이용하기 위해서 로그인을 해주세요." + title="환영합니다" + desc={ + <> + <span className="font-sekuya">stack plus</span>를 이용하기 위해서 + 로그인을 해주세요. + </> + } /> <form @@ -89,7 +94,7 @@ export default function LoginPage() { </Button> </form> - <div className="mt-6 text-center text-sm text-gray-500"> + <div className="mt-6 text-center text-sm text-gray-600"> 아직 계정이 없으신가요? <Button variant="text_underline" href="/join" className="ml-1"> 회원가입 diff --git a/src/components/empty-state/EmptyState.tsx b/src/components/empty-state/EmptyState.tsx index e9c9b1f..bda76ca 100644 --- a/src/components/empty-state/EmptyState.tsx +++ b/src/components/empty-state/EmptyState.tsx @@ -11,7 +11,7 @@ export default function EmptyState({ <div className={`flex w-full items-center justify-center px-4 py-12 ${className}`} > - <p className="max-w-sm text-center text-sm text-gray-500 leading-relaxed"> + <p className="max-w-sm text-center text-sm text-gray-600 leading-relaxed"> {children} </p> </div> diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 397c37f..e6b1ec1 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -1,7 +1,7 @@ export default function Footer() { return ( <footer className="border-t border-gray-200 py-6"> - <p className="text-center text-xs text-gray-500"> + <p className="text-center text-xs text-gray-600"> © 2025 <span className="font-medium text-gray-700">Stack Plus</span>{" "} Build your growth </p> From 1c3a188d263d613bb8b26e3f2ff677bef8c5ee7d Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:43:28 +0900 Subject: [PATCH 04/18] =?UTF-8?q?refactor:=20AuthProvider=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth.ts | 2 +- src/api/profile.ts | 2 +- .../_components/common/banner/Banner.tsx | 2 +- src/app/(public)/join/page.tsx | 11 ++- src/app/(public)/login/page.tsx | 7 +- src/providers/AuthProvider.tsx | 75 +++++++------------ src/store/useUserStore.ts | 8 +- 7 files changed, 48 insertions(+), 59 deletions(-) diff --git a/src/api/auth.ts b/src/api/auth.ts index adedea7..f23285d 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -19,7 +19,7 @@ export async function signUp(payload: SignUpResponse) { if (profileError) throw profileError; - return user; + return data; } export async function signIn(payload: SignInResponse) { diff --git a/src/api/profile.ts b/src/api/profile.ts index ac08cd6..fde2a96 100644 --- a/src/api/profile.ts +++ b/src/api/profile.ts @@ -10,7 +10,7 @@ export async function getMyProfile(userId: string) { .from("profiles") .select("id, nickname") .eq("id", userId) - .single(); + .maybeSingle(); if (error) throw error; return data as Profile; diff --git a/src/app/(protected)/dashboard/_components/common/banner/Banner.tsx b/src/app/(protected)/dashboard/_components/common/banner/Banner.tsx index 6dcbfd2..8983923 100644 --- a/src/app/(protected)/dashboard/_components/common/banner/Banner.tsx +++ b/src/app/(protected)/dashboard/_components/common/banner/Banner.tsx @@ -9,7 +9,7 @@ export function Banner({ children, className = "" }: BannerProps) { rounded-xl bg-white px-6 py-5 border border-gray-200 transition-colors - hover:bg-gray-50 + hover:bg-background focus:outline-none focus:ring-2 focus:ring-gray-900/10 flex flex-col justify-between group h-full diff --git a/src/app/(public)/join/page.tsx b/src/app/(public)/join/page.tsx index 6043e14..aedf888 100644 --- a/src/app/(public)/join/page.tsx +++ b/src/app/(public)/join/page.tsx @@ -33,13 +33,18 @@ export default function JoinPage() { const onSubmit: SubmitHandler<JoinInputs> = async (formData) => { try { - await signUp({ + const result = await signUp({ email: formData.email, nickname: formData.nickname, password: formData.password, }); + toast.success("로그인이 되었습니다."); - router.push("/login"); + if (result.session) { + router.push("/dashboard"); + } else { + router.push("/login"); + } } catch (error) { if (error instanceof Error) { toast.error(error.message); @@ -49,7 +54,7 @@ export default function JoinPage() { }; return ( - <div className="flex min-h-screen items-center justify-center bg-gray-50 px-6"> + <div className="flex min-h-screen items-center justify-center bg-background px-6"> <div className="w-full max-w-lg rounded-2xl bg-white p-10 shadow-sm"> <Title title="회원가입" diff --git a/src/app/(public)/login/page.tsx b/src/app/(public)/login/page.tsx index 5d5af81..292bc59 100644 --- a/src/app/(public)/login/page.tsx +++ b/src/app/(public)/login/page.tsx @@ -5,10 +5,11 @@ import Input from "../../../components/input/Input"; import Button from "@/components/button/Button"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { FaRegEyeSlash } from "react-icons/fa"; import { SubmitHandler, useForm } from "react-hook-form"; -import { signIn } from "@/api/auth"; +import { signIn, signUp } from "@/api/auth"; +import { useUserStore } from "@/store/useUserStore"; type LoginInputs = { email: string; @@ -42,7 +43,7 @@ export default function LoginPage() { }; return ( - <div className="flex min-h-screen items-center justify-center bg-gray-50 px-6"> + <div className="flex min-h-screen items-center justify-center bg-background px-6"> <div className="w-full max-w-lg rounded-2xl bg-white p-10 shadow-sm"> <Title title="환영합니다" diff --git a/src/providers/AuthProvider.tsx b/src/providers/AuthProvider.tsx index 977d4e1..a7a6834 100644 --- a/src/providers/AuthProvider.tsx +++ b/src/providers/AuthProvider.tsx @@ -7,18 +7,6 @@ import { getMyProfile } from "@/api/profile"; import { Session } from "@supabase/supabase-js"; import { supabase } from "@/lib/supabase"; -async function getSession() { - const { data, error } = await supabase.auth.getSession(); - if (error) throw error; - return data.session; -} - -function subscribeAuthChange(callback: (session: Session | null) => void) { - return supabase.auth.onAuthStateChange((_, session) => { - callback(session); - }); -} - export default function AuthProvider({ children, }: { @@ -26,47 +14,42 @@ export default function AuthProvider({ }) { const { setUser, clearUser, setInitialized } = useUserStore(); - useEffect(() => { - getSession().then(async (session) => { - if (!session) { - setInitialized(); - return; - } - - setUser({ id: session.user.id }); - - try { - const profile = await getMyProfile(session.user.id); - setUser({ nickname: profile.nickname }); - } catch (error) { - console.error("profile 불러오기 실패", error); - } finally { - setInitialized(); - } - }); - - const { data } = subscribeAuthChange(async (session) => { - if (!session) { - clearUser(); + const handleSession = async (session: Session | null) => { + if (!session) { + clearUser(); + setInitialized(); + return; + } + + try { + const profile = await getMyProfile(session.user.id); + if (!profile) { + setUser({ + id: session.user.id, + nickname: null, + }); return; } - - setUser({ id: session.user.id }); - - try { - const profile = await getMyProfile(session.user.id); - setUser({ nickname: profile.nickname }); - } catch (error) { - console.error("profile 불러오기 실패", error); - } - + setUser({ + id: session.user.id, + nickname: profile.nickname, + }); + } catch (error) { + console.error("프로필 불러오기 실패", JSON.stringify(error)); + setUser({ id: session.user.id, nickname: null }); + } finally { setInitialized(); - }); + } + }; + useEffect(() => { + const { data } = supabase.auth.onAuthStateChange((event, sesseion) => { + handleSession(sesseion); + }); return () => { data.subscription.unsubscribe(); }; - }, [setUser, clearUser, setInitialized]); + }, []); return <>{children}</>; } diff --git a/src/store/useUserStore.ts b/src/store/useUserStore.ts index 040f1a5..01895d8 100644 --- a/src/store/useUserStore.ts +++ b/src/store/useUserStore.ts @@ -4,24 +4,24 @@ interface UserState { id: string | null; nickname: string | null; isInitialized: boolean; + setInitialized: () => void; setUser: ( user: Partial<Omit<UserState, "setUser" | "clearUser" | "setInitialized">>, ) => void; clearUser: () => void; - setInitialized: () => void; } export const useUserStore = create<UserState>((set) => ({ id: null, nickname: null, isInitialized: false, + setInitialized: () => { + set({ isInitialized: true }); + }, setUser: (user) => { set((state) => ({ ...state, ...user })); }, clearUser: () => { set({ id: null, nickname: null, isInitialized: true }); }, - setInitialized: () => { - set({ isInitialized: true }); - }, })); From 840189ee0bcef700ea4567cffb0201d50763096a Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:45:19 +0900 Subject: [PATCH 05/18] =?UTF-8?q?chore:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20import=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(public)/join/page.tsx | 5 +---- src/app/(public)/login/page.tsx | 10 +++------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/app/(public)/join/page.tsx b/src/app/(public)/join/page.tsx index aedf888..439fc81 100644 --- a/src/app/(public)/join/page.tsx +++ b/src/app/(public)/join/page.tsx @@ -65,10 +65,7 @@ export default function JoinPage() { </> } /> - <form - className="mt-8 grid gap-5 font-pretendard" - onSubmit={handleSubmit(onSubmit)} - > + <form className="mt-8 grid gap-5" onSubmit={handleSubmit(onSubmit)}> <Input type="email" {...register("email", { diff --git a/src/app/(public)/login/page.tsx b/src/app/(public)/login/page.tsx index 292bc59..d8e7ffb 100644 --- a/src/app/(public)/login/page.tsx +++ b/src/app/(public)/login/page.tsx @@ -5,11 +5,10 @@ import Input from "../../../components/input/Input"; import Button from "@/components/button/Button"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { FaRegEyeSlash } from "react-icons/fa"; import { SubmitHandler, useForm } from "react-hook-form"; -import { signIn, signUp } from "@/api/auth"; -import { useUserStore } from "@/store/useUserStore"; +import { signIn } from "@/api/auth"; type LoginInputs = { email: string; @@ -55,10 +54,7 @@ export default function LoginPage() { } /> - <form - className="mt-8 grid gap-5 font-pretendard" - onSubmit={handleSubmit(onSubmit)} - > + <form className="mt-8 grid gap-5" onSubmit={handleSubmit(onSubmit)}> <Input type="email" {...register("email", { From 44d6b0226ca3cf6102090598a55c80ad3bffbb35 Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:03:45 +0900 Subject: [PATCH 06/18] =?UTF-8?q?feature:=20=EB=8B=A4=ED=81=AC=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20public=20=ED=8F=B4=EB=8D=94=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 947 ++++++++++++++++-- package.json | 1 + .../(protected)/_components/layout/Header.tsx | 5 +- src/app/(public)/_components/PublicHeader.tsx | 2 + src/app/(public)/_components/title/Title.tsx | 8 +- src/app/(public)/join/page.tsx | 10 +- .../_components/SectionWrapper.tsx | 4 +- .../_components/feature/Feature.tsx | 2 +- src/app/(public)/login/page.tsx | 7 +- src/app/globals.css | 33 +- src/app/layout.tsx | 27 +- src/components/button/ThemeButton.tsx | 21 + src/components/input/Input.tsx | 2 +- src/providers/ThemeProvider.tsx | 16 + src/store/useUserStore.ts | 4 +- 15 files changed, 972 insertions(+), 117 deletions(-) create mode 100644 src/components/button/ThemeButton.tsx create mode 100644 src/providers/ThemeProvider.tsx diff --git a/package-lock.json b/package-lock.json index 16bfc6f..f5b9d09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,11 +13,15 @@ "@tanstack/react-query": "^5.90.12", "clsx": "^2.1.1", "framer-motion": "^12.23.25", - "next": "16.0.4", + "next": "16.1.3", + "next-themes": "^0.4.6", "react": "19.2.0", "react-dom": "19.2.0", + "react-hook-form": "^7.72.0", "react-icons": "^5.5.0", "recharts": "^3.5.1", + "sonner": "^2.0.7", + "sooner": "^1.1.4", "zustand": "^5.0.10" }, "devDependencies": { @@ -46,6 +50,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@ava/babel-plugin-throws-helper": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ava/babel-plugin-throws-helper/-/babel-plugin-throws-helper-2.0.0.tgz", + "integrity": "sha512-pX8AjCJPlthNUGcvD4g/sgjIaZgOUN0CIBf5IOoLA5YROxuoHOXGvDiJeNZpEUeh62WWen6XfNTHh8Hmp/Oulw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@ava/babel-preset-stage-4": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ava/babel-preset-stage-4/-/babel-preset-stage-4-1.1.0.tgz", + "integrity": "sha512-oWqTnIGXW3k72UFidXzW0ONlO7hnO9x02S/QReJ7NBGeiBH9cUHY9+EfV6C8PXC6YJH++WrliEq03wMSJGNZFg==", + "license": "MIT", + "dependencies": { + "babel-plugin-check-es2015-constants": "^6.8.0", + "babel-plugin-syntax-trailing-function-commas": "^6.20.0", + "babel-plugin-transform-async-to-generator": "^6.16.0", + "babel-plugin-transform-es2015-destructuring": "^6.19.0", + "babel-plugin-transform-es2015-function-name": "^6.9.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", + "babel-plugin-transform-es2015-parameters": "^6.21.0", + "babel-plugin-transform-es2015-spread": "^6.8.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.8.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.11.0", + "babel-plugin-transform-exponentiation-operator": "^6.8.0", + "package-hash": "^1.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@ava/babel-preset-transform-test-files": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@ava/babel-preset-transform-test-files/-/babel-preset-transform-test-files-3.0.0.tgz", + "integrity": "sha512-GjO+HUIBzHuVjNAbsWtFzDPblvMAaYa7JYUy9qZeD6VCJlwfH3AdJArb3mAevZ+hUcgNTNimtwY1jGByAXl5ag==", + "license": "MIT", + "dependencies": { + "@ava/babel-plugin-throws-helper": "^2.0.0", + "babel-plugin-espower": "^2.3.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -1045,9 +1094,9 @@ } }, "node_modules/@next/env": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.4.tgz", - "integrity": "sha512-FDPaVoB1kYhtOz6Le0Jn2QV7RZJ3Ngxzqri7YX4yu3Ini+l5lciR7nA9eNDpKTmDm7LWZtxSju+/CQnwRBn2pA==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.3.tgz", + "integrity": "sha512-BLP14oBOvZWXgfdJf9ao+VD8O30uE+x7PaV++QtACLX329WcRSJRO5YJ+Bcvu0Q+c/lei41TjSiFf6pXqnpbQA==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1061,9 +1110,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.4.tgz", - "integrity": "sha512-TN0cfB4HT2YyEio9fLwZY33J+s+vMIgC84gQCOLZOYusW7ptgjIn8RwxQt0BUpoo9XRRVVWEHLld0uhyux1ZcA==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.3.tgz", + "integrity": "sha512-CpOD3lmig6VflihVoGxiR/l5Jkjfi4uLaOR4ziriMv0YMDoF6cclI+p5t2nstM8TmaFiY6PCTBgRWB57/+LiBA==", "cpu": [ "arm64" ], @@ -1077,9 +1126,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.4.tgz", - "integrity": "sha512-XsfI23jvimCaA7e+9f3yMCoVjrny2D11G6H8NCcgv+Ina/TQhKPXB9P4q0WjTuEoyZmcNvPdrZ+XtTh3uPfH7Q==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.3.tgz", + "integrity": "sha512-aF4us2JXh0zn3hNxvL1Bx3BOuh8Lcw3p3Xnurlvca/iptrDH1BrpObwkw9WZra7L7/0qB9kjlREq3hN/4x4x+Q==", "cpu": [ "x64" ], @@ -1093,9 +1142,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.4.tgz", - "integrity": "sha512-uo8X7qHDy4YdJUhaoJDMAbL8VT5Ed3lijip2DdBHIB4tfKAvB1XBih6INH2L4qIi4jA0Qq1J0ErxcOocBmUSwg==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.3.tgz", + "integrity": "sha512-8VRkcpcfBtYvhGgXAF7U3MBx6+G1lACM1XCo1JyaUr4KmAkTNP8Dv2wdMq7BI+jqRBw3zQE7c57+lmp7jCFfKA==", "cpu": [ "arm64" ], @@ -1109,9 +1158,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.4.tgz", - "integrity": "sha512-pvR/AjNIAxsIz0PCNcZYpH+WmNIKNLcL4XYEfo+ArDi7GsxKWFO5BvVBLXbhti8Coyv3DE983NsitzUsGH5yTw==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.3.tgz", + "integrity": "sha512-UbFx69E2UP7MhzogJRMFvV9KdEn4sLGPicClwgqnLht2TEi204B71HuVfps3ymGAh0c44QRAF+ZmvZZhLLmhNg==", "cpu": [ "arm64" ], @@ -1125,9 +1174,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.4.tgz", - "integrity": "sha512-2hebpsd5MRRtgqmT7Jj/Wze+wG+ZEXUK2KFFL4IlZ0amEEFADo4ywsifJNeFTQGsamH3/aXkKWymDvgEi+pc2Q==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.3.tgz", + "integrity": "sha512-SzGTfTjR5e9T+sZh5zXqG/oeRQufExxBF6MssXS7HPeZFE98JDhCRZXpSyCfWrWrYrzmnw/RVhlP2AxQm+wkRQ==", "cpu": [ "x64" ], @@ -1141,9 +1190,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.4.tgz", - "integrity": "sha512-pzRXf0LZZ8zMljH78j8SeLncg9ifIOp3ugAFka+Bq8qMzw6hPXOc7wydY7ardIELlczzzreahyTpwsim/WL3Sg==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.3.tgz", + "integrity": "sha512-HlrDpj0v+JBIvQex1mXHq93Mht5qQmfyci+ZNwGClnAQldSfxI6h0Vupte1dSR4ueNv4q7qp5kTnmLOBIQnGow==", "cpu": [ "x64" ], @@ -1157,9 +1206,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.4.tgz", - "integrity": "sha512-7G/yJVzum52B5HOqqbQYX9bJHkN+c4YyZ2AIvEssMHQlbAWOn3iIJjD4sM6ihWsBxuljiTKJovEYlD1K8lCUHw==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.3.tgz", + "integrity": "sha512-3gFCp83/LSduZMSIa+lBREP7+5e7FxpdBoc9QrCdmp+dapmTK9I+SLpY60Z39GDmTXSZA4huGg9WwmYbr6+WRw==", "cpu": [ "arm64" ], @@ -1173,9 +1222,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.4.tgz", - "integrity": "sha512-0Vy4g8SSeVkuU89g2OFHqGKM4rxsQtihGfenjx2tRckPrge5+gtFnRWGAAwvGXr0ty3twQvcnYjEyOrLHJ4JWA==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.3.tgz", + "integrity": "sha512-1SZVfFT8zmMB+Oblrh5OKDvUo5mYQOkX2We6VGzpg7JUVZlqe4DYOFGKYZKTweSx1gbMixyO1jnFT4thU+nNHQ==", "cpu": [ "x64" ], @@ -2442,6 +2491,15 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2688,6 +2746,436 @@ "node": ">= 0.4" } }, + "node_modules/babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", + "license": "MIT", + "dependencies": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "node_modules/babel-code-frame/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/babel-code-frame/node_modules/js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", + "license": "MIT" + }, + "node_modules/babel-code-frame/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "license": "MIT", + "dependencies": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "node_modules/babel-generator/node_modules/jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha512-gCtfYORSG1fUMX4kKraymq607FWgMWg+j42IFPc18kFQEsmtaibP4UrqsXt8FlEJle25HUd4tsoDR7H2wDhe9Q==", + "license": "MIT", + "dependencies": { + "babel-helper-explode-assignable-expression": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha512-RL8n2NiEj+kKztlrVJM9JT1cXzzAdvWFh76xh/H1I4nKwunzE4INBXn8ieCZ+wh4zWszZk7NBS1s/8HR5jDkzQ==", + "license": "MIT", + "dependencies": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha512-qe5csbhbvq6ccry9G7tkXbzNtcDiH4r51rrPUbwwoTzZ18AqxWYRZT6AOmxrpxKnQBW0pYlBI/8vh73Z//78nQ==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha512-Oo6+e2iX+o9eVvJ9Y5eKL5iryeRdsIkwRYheCuhYdVHsdEQysbc2z2QkqCLIYnNxkT5Ss3ggrHdXiDI7Dhrn4Q==", + "license": "MIT", + "dependencies": { + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha512-WfgKFX6swFB1jS2vo+DwivRN4NB8XUdM3ij0Y1gnC21y1tdBoe6xjVnd7NSI6alv+gZXCtJqvrTeMW3fR/c0ng==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha512-zAYl3tqerLItvG5cKYw7f1SpvIxS9zi7ohyGHaI9cgDUjAT6YcY9jIEH5CstetP5wHIVSceXwNS7Z5BpJg+rOw==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha512-VlPiWmqmGJp0x0oK27Out1D+71nVVCTSdlbhIVoaBAj2lUgrNjBCRR9+llO4lTSb2O4r7PJg+RobRkhBrf6ofg==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha512-RYqaPD0mQyQIFRu7Ho5wE2yvA/5jxqCIj/Lv4BXNq23mHYu/vxikOy2JueLiBxQknwapwrJeNCesvY0ZcfnlHg==", + "license": "MIT", + "dependencies": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha512-B1M5KBP29248dViEo1owyY32lk1ZSH2DaNNrXLGt8lyjjHm7pBqAdQ7VKUPR6EEDO323+OvT3MQXbCin8ooWdA==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-espower": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-espower/-/babel-plugin-espower-2.4.0.tgz", + "integrity": "sha512-/+SRpy7pKgTI28oEHfn1wkuM5QFAdRq8WNsOOih1dVrdV6A/WbNbRZyl0eX5eyDgtb0lOE27PeDFuCX2j8OxVg==", + "license": "MIT", + "dependencies": { + "babel-generator": "^6.1.0", + "babylon": "^6.1.0", + "call-matcher": "^1.0.0", + "core-js": "^2.0.0", + "espower-location-detector": "^1.0.0", + "espurify": "^1.6.0", + "estraverse": "^4.1.1" + } + }, + "node_modules/babel-plugin-espower/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha512-4Zp4unmHgw30A1eWI5EpACji2qMocisdXhAftfhXoSV9j0Tvj6nRFE3tOmRY912E0FMRm/L5xWE7MGVT2FoLnw==", + "license": "MIT" + }, + "node_modules/babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha512-Z/flU+T9ta0aIEKl1tGEmN/pZiI1uXmCiGFRegKacQfEJzp7iNsKloZmyJlQr+75FCJtiFfGIK03SiCvCt9cPQ==", + "license": "MIT" + }, + "node_modules/babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha512-Gx9CH3Q/3GKbhs07Bszw5fPTlU+ygrOGfAhEt7W2JICwufpC4SuO0mG0+4NykPBSYPMJhqvVlDBU17qB1D+hMQ==", + "license": "MIT" + }, + "node_modules/babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha512-7BgYJujNCg0Ti3x0c/DL3tStvnKS6ktIYOmo9wginv/dfZOrbSZ+qG4IRRHMBOzZ5Awb1skTiAsQXg/+IWkZYw==", + "license": "MIT", + "dependencies": { + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-functions": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha512-aNv/GDAW0j/f4Uy1OEPZn1mqD+Nfy9viFGBfQ5bZyT35YqOiqx7/tXdyfZkJ1sC21NyEsBdfDY6PYmLHF4r5iA==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha512-iFp5KIcorf11iBqu/y/a7DK3MN5di3pNCzto61FqCNnUX4qeBwcV1SLqe10oXNnCaxBUImX3SckX2/o1nsrTcg==", + "license": "MIT", + "dependencies": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", + "license": "MIT", + "dependencies": { + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + } + }, + "node_modules/babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha512-8HxlW+BB5HqniD+nLkQ4xSAVq3bR/pcYW9IigY+2y0dI+Y7INFeTbfAQr+63T3E4UDsZGjyb+l9txUnABWxlOQ==", + "license": "MIT", + "dependencies": { + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha512-3Ghhi26r4l3d0Js933E5+IhHwk0A1yiutj9gwvzmFbVV0sPMYk2lekhOufHBswX7NCoSeF4Xrl3sCIuSIa+zOg==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha512-CYP359ADryTo3pCsH0oxRo/0yn6UsEZLqYohHmvLQdfS9xkf+MbCzE3/Kolw9OYIY4ZMilH25z/5CbQbwDD+lQ==", + "license": "MIT", + "dependencies": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha512-v61Dbbihf5XxnYjtBN04B/JBvsScY37R1cZT5r9permN1cp+b70DY3Ib3fIkgn1DI9U3tGgBJZVD8p/mE/4JbQ==", + "license": "MIT", + "dependencies": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + } + }, + "node_modules/babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha512-LzXDmbMkklvNhprr20//RStKVcT8Cu+SQtX18eMHLhjHf2yFzwtQ0S2f0jQ+89rokoNdmwoSqYzAhq86FxlLSQ==", + "license": "MIT", + "dependencies": { + "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", + "babel-plugin-syntax-exponentiation-operator": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha512-j3KtSpjyLSJxNoCDrhwiJad8kw0gJ9REGj8/CqL0HeRyLnvUNYV9zcqluL6QJSXh3nfsLEmSLvwRfGzrgR96Pw==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "license": "MIT", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", + "license": "MIT", + "dependencies": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/babel-traverse/node_modules/globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-traverse/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "license": "MIT", + "bin": { + "babylon": "bin/babylon.js" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2699,7 +3187,6 @@ "version": "2.8.31", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", - "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -2767,7 +3254,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -2786,7 +3272,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2800,7 +3285,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2813,6 +3297,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-matcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.1.0.tgz", + "integrity": "sha512-IoQLeNwwf9KTNbtSA7aEBb1yfDbdnzwjCetjkC8io5oGeOmK2CBNdg0xr+tadRYKO0p7uQyZzvon0kXlZbvGrw==", + "license": "MIT", + "dependencies": { + "core-js": "^2.0.0", + "deep-equal": "^1.0.0", + "espurify": "^1.6.0", + "estraverse": "^4.0.0" + } + }, + "node_modules/call-matcher/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2922,6 +3427,14 @@ "url": "https://opencollective.com/express" } }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3150,6 +3663,26 @@ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, + "node_modules/deep-equal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", + "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", + "license": "MIT", + "dependencies": { + "is-arguments": "^1.1.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.5.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3161,7 +3694,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -3179,7 +3711,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", @@ -3193,6 +3724,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==", + "license": "MIT", + "dependencies": { + "repeating": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -3220,7 +3763,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3332,7 +3874,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3342,7 +3883,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3380,7 +3920,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3829,6 +4368,18 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/espower-location-detector": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/espower-location-detector/-/espower-location-detector-1.0.0.tgz", + "integrity": "sha512-Y/3H6ytYwqC3YcOc0gOU22Lp3eI5GAFGOymTdzFyfaiglKgtsw2dePOgXY3yrV+QcLPMPiVYwBU9RKaDoh2bbQ==", + "license": "MIT", + "dependencies": { + "is-url": "^1.2.1", + "path-is-absolute": "^1.0.0", + "source-map": "^0.5.0", + "xtend": "^4.0.0" + } + }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -3847,6 +4398,15 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/espurify": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/espurify/-/espurify-1.8.1.tgz", + "integrity": "sha512-ZDko6eY/o+D/gHCWyHTU85mKDgYcS4FJj7S+YD6WIInm7GQ6AnOjmcL4+buFV/JOztVLELi/7MmuGU5NHta0Mg==", + "license": "MIT", + "dependencies": { + "core-js": "^2.0.0" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -3887,7 +4447,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -4071,7 +4630,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4102,7 +4660,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4132,7 +4689,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4157,7 +4713,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -4245,7 +4800,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4268,6 +4822,18 @@ "dev": true, "license": "MIT" }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -4295,7 +4861,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -4324,7 +4889,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4337,7 +4901,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -4353,7 +4916,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4459,6 +5021,31 @@ "node": ">=12" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -4604,7 +5191,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -4643,6 +5229,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -4733,7 +5331,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -4828,6 +5425,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -4920,7 +5523,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -5320,6 +5922,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5331,7 +5939,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -5364,12 +5971,28 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/md5-hex": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-1.3.0.tgz", + "integrity": "sha512-lJEPhRxivsaliY4C6REebtP1Lo8yoQsq2bLVP8mJ6Vvzwu3fXQShzHcWnAqdDm1Y42jhZFg0XRpnrKfZ5mYP6w==", + "license": "MIT", + "dependencies": { + "md5-o-matic": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/md5-o-matic": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz", + "integrity": "sha512-QBJSFpsedXUl/Lgs4ySdB2XCzUEcJ3ujpbagdZCkRaYIaC0kFnID8jhc84KEiVv6dNFtIrmW7bqow0lDxgJi6A==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5481,13 +6104,14 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/next/-/next-16.0.4.tgz", - "integrity": "sha512-vICcxKusY8qW7QFOzTvnRL1ejz2ClTqDKtm1AcUjm2mPv/lVAdgpGNsftsPRIDJOXOjRQO68i1dM8Lp8GZnqoA==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.3.tgz", + "integrity": "sha512-gthG3TRD+E3/mA0uDQb9lqBmx1zVosq5kIwxNN6+MRNd085GzD+9VXMPUs+GGZCbZ+GDZdODUq4Pm7CTXK6ipw==", "license": "MIT", "dependencies": { - "@next/env": "16.0.4", + "@next/env": "16.1.3", "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -5499,14 +6123,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.0.4", - "@next/swc-darwin-x64": "16.0.4", - "@next/swc-linux-arm64-gnu": "16.0.4", - "@next/swc-linux-arm64-musl": "16.0.4", - "@next/swc-linux-x64-gnu": "16.0.4", - "@next/swc-linux-x64-musl": "16.0.4", - "@next/swc-win32-arm64-msvc": "16.0.4", - "@next/swc-win32-x64-msvc": "16.0.4", + "@next/swc-darwin-arm64": "16.1.3", + "@next/swc-darwin-x64": "16.1.3", + "@next/swc-linux-arm64-gnu": "16.1.3", + "@next/swc-linux-arm64-musl": "16.1.3", + "@next/swc-linux-x64-gnu": "16.1.3", + "@next/swc-linux-x64-musl": "16.1.3", + "@next/swc-win32-arm64-msvc": "16.1.3", + "@next/swc-win32-x64-msvc": "16.1.3", "sharp": "^0.34.4" }, "peerDependencies": { @@ -5532,6 +6156,16 @@ } } }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -5590,11 +6224,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5758,6 +6407,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-1.2.0.tgz", + "integrity": "sha512-W5ILqaI3G6bXDuYb7TrQ95TFHfFdjiunpp61PAXj7z32TgJ5NIBaoqZVI6AXUQy/qcqPoFnz0hAZY9KyKd4xNA==", + "license": "ISC", + "dependencies": { + "md5-hex": "^1.3.0" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5781,6 +6439,15 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -5930,6 +6597,22 @@ "react": "^19.2.0" } }, + "node_modules/react-hook-form": { + "version": "7.72.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.72.0.tgz", + "integrity": "sha512-V4v6jubaf6JAurEaVnT9aUPKFbNtDgohj5CIgVGyPHvT9wRx5OZHVjz31GsxnPNI278XMu+ruFz+wGOscHaLKw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -6036,11 +6719,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -6057,6 +6751,55 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha512-tJ9+S4oKjxY8IZ9jmjnp/mtytu1u3iyIQAfmI51IKWH6bFf7XR1ybtaO6j7INhZKXOTYADk7V5qxaqLkmNxiZQ==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "node_modules/regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha512-x+Y3yA24uF68m5GA+tBjbGYo64xXVJpbToBaWCoSNSc1hdk6dfctaRWrNFTVJZIIhL5GxW8zwjoixbnifnK59g==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha512-jlQ9gYLfk2p3V5Ag5fYhA7fv7OHzd1KUH0PRP46xc3TgwjwgROIW572AfYg/X9kaNq/LJnu6oJcFRXlIrGoTRw==", + "license": "BSD", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", + "license": "MIT", + "dependencies": { + "is-finite": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", @@ -6214,7 +6957,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -6232,7 +6974,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -6416,6 +7157,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/sooner": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/sooner/-/sooner-1.1.4.tgz", + "integrity": "sha512-9hltBBOSVcxoUz8/U41Jnd+Wk0rbB4WKOZUlezPWNFZaIUwTMzoHas0L2mCHD9UDzDjraiR8aDCuPtytqRW7rQ==", + "license": "MIT", + "dependencies": { + "@ava/babel-preset-stage-4": "^1.0.0", + "@ava/babel-preset-transform-test-files": "^3.0.0" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6559,6 +7329,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -6706,6 +7488,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6719,6 +7510,15 @@ "node": ">=8.0" } }, + "node_modules/trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -7171,6 +7971,15 @@ } } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index b226734..2d21948 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "clsx": "^2.1.1", "framer-motion": "^12.23.25", "next": "16.1.3", + "next-themes": "^0.4.6", "react": "19.2.0", "react-dom": "19.2.0", "react-hook-form": "^7.72.0", diff --git a/src/app/(protected)/_components/layout/Header.tsx b/src/app/(protected)/_components/layout/Header.tsx index 213141f..1c86a3e 100644 --- a/src/app/(protected)/_components/layout/Header.tsx +++ b/src/app/(protected)/_components/layout/Header.tsx @@ -12,6 +12,7 @@ import { useWordStats } from "@/hooks/queries/words"; import MyTooltip from "./MyTooltip"; import { useClickOutside } from "@/hooks/useClickOutside"; import { useLogout } from "@/hooks/auth/useLogout"; +import { ThemeToggle } from "@/components/button/ThemeButton"; export default function Header() { const { id, nickname } = useUserStore(); @@ -26,7 +27,7 @@ export default function Header() { useClickOutside(myWrapperRef, () => setIsMyOpen(false), isMyOpen); return ( - <header className="sticky top-0 z-20 p-6 bg-white/80 backdrop-blur-md border-b border-gray-200"> + <header className="sticky top-0 z-20 p-6 bg-background backdrop-blur-md border-b border-gray-200"> <div className="mx-auto flex max-w-6xl items-center justify-between"> <Link href="/dashboard" @@ -40,6 +41,8 @@ export default function Header() { <div className="relative flex items-center gap-3 text-gray-600"> <div ref={myWrapperRef} className="relative"> + <ThemeToggle /> + <Button onClick={() => setIsMyOpen((prev) => !prev)} variant="text_underline" diff --git a/src/app/(public)/_components/PublicHeader.tsx b/src/app/(public)/_components/PublicHeader.tsx index a0497bd..5427a55 100644 --- a/src/app/(public)/_components/PublicHeader.tsx +++ b/src/app/(public)/_components/PublicHeader.tsx @@ -1,4 +1,5 @@ import Button from "@/components/button/Button"; +import { ThemeToggle } from "@/components/button/ThemeButton"; import Link from "next/link"; export default function PublicHeader() { @@ -16,6 +17,7 @@ export default function PublicHeader() { </Link> <nav className="flex items-center gap-4"> + <ThemeToggle /> <Button type="button" variant="text_underline" href="/dashboard"> 시작하기 </Button> diff --git a/src/app/(public)/_components/title/Title.tsx b/src/app/(public)/_components/title/Title.tsx index daa128d..2b6da0d 100644 --- a/src/app/(public)/_components/title/Title.tsx +++ b/src/app/(public)/_components/title/Title.tsx @@ -6,12 +6,8 @@ type TitleProps = { export default function Title({ title, desc }: TitleProps) { return ( <div className="mb-8"> - <h1 className="text-3xl font-bold tracking-tight text-foreground"> - {title} - </h1> - <p className="mt-5 text-sm font-medium leading-relaxed text-gray-600"> - {desc} - </p> + <h1 className="text-3xl font-bold tracking-tight">{title}</h1> + <p className="mt-5 text-sm font-medium leading-relaxed">{desc}</p> </div> ); } diff --git a/src/app/(public)/join/page.tsx b/src/app/(public)/join/page.tsx index 439fc81..68ef368 100644 --- a/src/app/(public)/join/page.tsx +++ b/src/app/(public)/join/page.tsx @@ -9,6 +9,7 @@ import { useState } from "react"; import { toast } from "sonner"; import { SubmitHandler, useForm } from "react-hook-form"; import { signUp } from "@/api/auth"; +import { useTheme } from "next-themes"; type JoinInputs = { email: string; @@ -18,6 +19,7 @@ type JoinInputs = { }; export default function JoinPage() { + const { resolvedTheme } = useTheme(); const router = useRouter(); const [passwordShow, setPasswordShow] = useState({ password: false, @@ -54,8 +56,8 @@ export default function JoinPage() { }; return ( - <div className="flex min-h-screen items-center justify-center bg-background px-6"> - <div className="w-full max-w-lg rounded-2xl bg-white p-10 shadow-sm"> + <div className="flex min-h-screen items-center justify-center px-6"> + <div className="w-full max-w-lg rounded-2xl p-10 shadow-sm"> <Title title="회원가입" desc={ @@ -98,6 +100,7 @@ export default function JoinPage() { errors={errors.password?.message} > <FaRegEyeSlash + className={`${resolvedTheme === "dark" ? "text-white" : "text-black"}`} onClick={() => { setPasswordShow((prev) => ({ ...prev, @@ -119,6 +122,7 @@ export default function JoinPage() { errors={errors.passwordConfirm?.message} > <FaRegEyeSlash + className={`${resolvedTheme === "dark" ? "text-white" : "text-black"}`} onClick={() => { setPasswordShow((prev) => ({ ...prev, @@ -133,7 +137,7 @@ export default function JoinPage() { </Button> </form> - <div className="mt-6 text-center text-sm text-gray-600"> + <div className="mt-6 text-center text-sm text-foreground"> 이미 계정이 있으신가요? <Button variant="text_underline" href="/login" className="ml-1"> 로그인 diff --git a/src/app/(public)/landing-page/_components/SectionWrapper.tsx b/src/app/(public)/landing-page/_components/SectionWrapper.tsx index f270dda..6ba2784 100644 --- a/src/app/(public)/landing-page/_components/SectionWrapper.tsx +++ b/src/app/(public)/landing-page/_components/SectionWrapper.tsx @@ -9,9 +9,7 @@ export default function SectionWrapper({ children, }: SectionWrapperProps) { return ( - <section - className={`mx-auto w-full text-center bg-background text-foreground py-30`} - > + <section className="mx-auto w-full text-center py-30"> <div className="m-auto max-w-6xl flex flex-col justify-center px-4"> <h2 className="break-keep text-2xl font-bold tracking-tight leading-normal md:text-4xl mb-2 md:mb-5"> {title} diff --git a/src/app/(public)/landing-page/_components/feature/Feature.tsx b/src/app/(public)/landing-page/_components/feature/Feature.tsx index e8b23ed..778240d 100644 --- a/src/app/(public)/landing-page/_components/feature/Feature.tsx +++ b/src/app/(public)/landing-page/_components/feature/Feature.tsx @@ -54,7 +54,7 @@ export function FeaturePage() { {features.map((feature) => ( <div key={feature.title} - className="rounded-2xl border border-border p-5 md:px-2 md:py-7 md:text-center text-left" + className="rounded-2xl border border-border border-gray-300 p-5 md:px-2 md:py-7 md:text-center text-left" > <div className="flex items-center md:block gap-5"> <div diff --git a/src/app/(public)/login/page.tsx b/src/app/(public)/login/page.tsx index d8e7ffb..e604975 100644 --- a/src/app/(public)/login/page.tsx +++ b/src/app/(public)/login/page.tsx @@ -9,6 +9,7 @@ import { useState } from "react"; import { FaRegEyeSlash } from "react-icons/fa"; import { SubmitHandler, useForm } from "react-hook-form"; import { signIn } from "@/api/auth"; +import { useTheme } from "next-themes"; type LoginInputs = { email: string; @@ -16,6 +17,7 @@ type LoginInputs = { }; export default function LoginPage() { + const { resolvedTheme } = useTheme(); const router = useRouter(); const [passwordShow, setPasswordShow] = useState(false); @@ -42,8 +44,8 @@ export default function LoginPage() { }; return ( - <div className="flex min-h-screen items-center justify-center bg-background px-6"> - <div className="w-full max-w-lg rounded-2xl bg-white p-10 shadow-sm"> + <div className="flex min-h-screen items-center justify-center px-6"> + <div className="w-full max-w-lg rounded-2xl p-10 shadow-sm"> <Title title="환영합니다" desc={ @@ -81,6 +83,7 @@ export default function LoginPage() { errors={errors.password?.message} > <FaRegEyeSlash + className={`${resolvedTheme === "dark" ? "text-white" : "text-black"}`} onClick={() => { setPasswordShow((prev) => !prev); }} diff --git a/src/app/globals.css b/src/app/globals.css index df85993..3b32925 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,28 +1,32 @@ @import "tailwindcss"; + @theme { --font-pretendard: var(--font-pretendard); --font-sekuya: var(--font-sekuya); - - --color-background: rgb(var(--background)); - --color-foreground: rgb(var(--foreground)); - --color-border: rgb(var(--border)); - --color-point: rgb(var(--point)); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-point: rgba(100, 116, 189, 1); } :root { - --background: 248 249 250; - --foreground: 34 34 34; - --border: 229 231 235; - --point: 100 116 189; + --background: #f8f9fa; + --foreground: #222222; } .dark { - --background: 18 18 18; - --foreground: 229 231 235; - --border: 55 65 81; - --point: 124 58 237; + --background: #121212; + --foreground: #e5e7eb; } +@layer base { + body { + background-color: var(--background); + color: var(--foreground); + transition: + background-color 0.3s, + color 0.3s; + } +} *, *::before, *::after { @@ -41,11 +45,8 @@ html { } body { - @apply bg-background text-foreground; - font-family: var(--font-pretendard), sans-serif; line-height: 1.5; - color: var(--color-text-black); } button { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c597522..c99b321 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,7 @@ import QueryProvider from "@/providers/QueryProvider"; import localFont from "next/font/local"; import AuthProvider from "@/providers/AuthProvider"; import { Toaster } from "sonner"; +import ThemeProvider from "@/providers/ThemeProvider"; const pretendard = localFont({ src: "../../public/fonts/pretendard/PretendardVariable.woff2", @@ -39,21 +40,21 @@ export const viewport = { export default function RootLayout({ children, -}: Readonly<{ +}: { children: React.ReactNode; -}>) { +}) { return ( - <html lang="ko"> - <body - className={`${sekuya.variable} ${pretendard.variable} antialiased`} - > - <QueryProvider> - <AuthProvider> - <Toaster richColors position="top-center" /> - {children} - <Footer /> - </AuthProvider> - </QueryProvider> + <html lang="ko" suppressHydrationWarning> + <body className={`${sekuya.variable} ${pretendard.variable} antialiased`}> + <ThemeProvider> + <QueryProvider> + <AuthProvider> + <Toaster richColors position="top-center" /> + {children} + <Footer /> + </AuthProvider> + </QueryProvider> + </ThemeProvider> </body> </html> ); diff --git a/src/components/button/ThemeButton.tsx b/src/components/button/ThemeButton.tsx new file mode 100644 index 0000000..640d421 --- /dev/null +++ b/src/components/button/ThemeButton.tsx @@ -0,0 +1,21 @@ +"use client"; +import { useTheme } from "next-themes"; +import { useEffect, useState } from "react"; + +export function ThemeToggle() { + const { theme, setTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + + // eslint-disable-next-line react-hooks/set-state-in-effect + useEffect(() => setMounted(true), []); + if (!mounted) return null; + + return ( + <button + className={`${theme === "dark" ? "bg-white text-point" : "bg-point text-white"} rounded-sm px-2 py-1 `} + onClick={() => setTheme(theme === "dark" ? "light" : "dark")} + > + {theme === "dark" ? "light" : "dark"} + </button> + ); +} diff --git a/src/components/input/Input.tsx b/src/components/input/Input.tsx index 780f4ca..22cb282 100644 --- a/src/components/input/Input.tsx +++ b/src/components/input/Input.tsx @@ -47,7 +47,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>( onClick={onClick} autoFocus={autoFocus} className={clsx( - "w-full rounded-full px-5 py-3.5 text-sm transition focus:outline-none", + "w-full rounded-full px-5 py-3.5 text-sm transition focus:outline-none", errors ? "border border-red-500 focus:ring-2 focus:ring-red-500" : "border border-gray-100 focus:ring-2 shadow-md focus:ring-gray-900", diff --git a/src/providers/ThemeProvider.tsx b/src/providers/ThemeProvider.tsx new file mode 100644 index 0000000..7538fe5 --- /dev/null +++ b/src/providers/ThemeProvider.tsx @@ -0,0 +1,16 @@ +// providers/ThemeProvider.tsx +"use client"; + +import { ThemeProvider as NextThemesProvider } from "next-themes"; + +export default function ThemeProvider({ + children, +}: { + children: React.ReactNode; +}) { + return ( + <NextThemesProvider attribute="class" defaultTheme="system" enableSystem> + {children} + </NextThemesProvider> + ); +} diff --git a/src/store/useUserStore.ts b/src/store/useUserStore.ts index 01895d8..07c1011 100644 --- a/src/store/useUserStore.ts +++ b/src/store/useUserStore.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; -interface UserState { +type UserState = { id: string | null; nickname: string | null; isInitialized: boolean; @@ -9,7 +9,7 @@ interface UserState { user: Partial<Omit<UserState, "setUser" | "clearUser" | "setInitialized">>, ) => void; clearUser: () => void; -} +}; export const useUserStore = create<UserState>((set) => ({ id: null, From 6819126b47e2ed1fa9923178ce5d105cf61e9ce5 Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:05:52 +0900 Subject: [PATCH 07/18] =?UTF-8?q?fix:=20=EC=84=A4=EC=B9=98=20=ED=9B=84=20?= =?UTF-8?q?=EC=9E=AC=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pnpm-lock.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c99c73c..5c4139a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: next: specifier: 16.1.3 version: 16.1.3(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: specifier: 19.2.0 version: 19.2.0 @@ -1938,6 +1941,12 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + next@16.1.3: resolution: {integrity: sha512-gthG3TRD+E3/mA0uDQb9lqBmx1zVosq5kIwxNN6+MRNd085GzD+9VXMPUs+GGZCbZ+GDZdODUq4Pm7CTXK6ipw==} engines: {node: '>=20.9.0'} @@ -4565,6 +4574,11 @@ snapshots: natural-compare@1.4.0: {} + next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + next@16.1.3(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@next/env': 16.1.3 From df89fe98914a214e342ce87ce160cf5f6e7d54a8 Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:40:28 +0900 Subject: [PATCH 08/18] =?UTF-8?q?feature:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=9B=84=20=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9C=A0=EC=A0=80=20=EC=8B=B1=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/profile.ts | 9 +++++++-- src/app/(protected)/dashboard/page.tsx | 17 +++++++++++++++++ src/app/(public)/join/page.tsx | 7 +++---- .../landing-page/_components/banner/Banner.tsx | 2 +- .../_components/feature/Feature.tsx | 14 ++++++++------ src/app/(public)/login/page.tsx | 4 +--- src/app/globals.css | 2 +- src/components/button/ThemeButton.tsx | 4 +++- src/components/layout/Footer.tsx | 2 +- src/providers/ThemeProvider.tsx | 1 - 10 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/api/profile.ts b/src/api/profile.ts index fde2a96..35d7b89 100644 --- a/src/api/profile.ts +++ b/src/api/profile.ts @@ -5,11 +5,16 @@ export type Profile = { nickname: string; }; -export async function getMyProfile(userId: string) { +export async function getMyProfile() { + const { + data: { user }, + } = await supabase.auth.getUser(); + if (!user) throw new Error("유저 없음"); + const { data, error } = await supabase .from("profiles") .select("id, nickname") - .eq("id", userId) + .eq("id", user.id) .maybeSingle(); if (error) throw error; diff --git a/src/app/(protected)/dashboard/page.tsx b/src/app/(protected)/dashboard/page.tsx index 6259e87..3d020b6 100644 --- a/src/app/(protected)/dashboard/page.tsx +++ b/src/app/(protected)/dashboard/page.tsx @@ -1,9 +1,26 @@ +"use client"; +import { useEffect } from "react"; import { AllWordsBanner } from "./_components/AllWordsBanner"; import { MemoBanner } from "./_components/memo/MemoBanner"; import { RecentWords } from "./_components/RecentWords"; import { RecordStudy } from "./_components/record/RecordStudy"; +import { getMyProfile } from "@/api/profile"; +import { useUserStore } from "@/store/useUserStore"; export default function DashboardPage() { + const setUser = useUserStore((state) => state.setUser); + useEffect(() => { + const init = async () => { + const profile = await getMyProfile(); + if (profile) { + setUser({ + id: profile.id, + nickname: profile.nickname, + }); + } + }; + init(); + }, []); return ( <div className=" diff --git a/src/app/(public)/join/page.tsx b/src/app/(public)/join/page.tsx index 68ef368..0afaf11 100644 --- a/src/app/(public)/join/page.tsx +++ b/src/app/(public)/join/page.tsx @@ -9,7 +9,6 @@ import { useState } from "react"; import { toast } from "sonner"; import { SubmitHandler, useForm } from "react-hook-form"; import { signUp } from "@/api/auth"; -import { useTheme } from "next-themes"; type JoinInputs = { email: string; @@ -19,7 +18,6 @@ type JoinInputs = { }; export default function JoinPage() { - const { resolvedTheme } = useTheme(); const router = useRouter(); const [passwordShow, setPasswordShow] = useState({ password: false, @@ -42,6 +40,7 @@ export default function JoinPage() { }); toast.success("로그인이 되었습니다."); + if (result.session) { router.push("/dashboard"); } else { @@ -100,7 +99,7 @@ export default function JoinPage() { errors={errors.password?.message} > <FaRegEyeSlash - className={`${resolvedTheme === "dark" ? "text-white" : "text-black"}`} + className="text-black dark:text-white" onClick={() => { setPasswordShow((prev) => ({ ...prev, @@ -122,7 +121,7 @@ export default function JoinPage() { errors={errors.passwordConfirm?.message} > <FaRegEyeSlash - className={`${resolvedTheme === "dark" ? "text-white" : "text-black"}`} + className="text-black dark:text-white" onClick={() => { setPasswordShow((prev) => ({ ...prev, diff --git a/src/app/(public)/landing-page/_components/banner/Banner.tsx b/src/app/(public)/landing-page/_components/banner/Banner.tsx index 25e1438..ce65b2f 100644 --- a/src/app/(public)/landing-page/_components/banner/Banner.tsx +++ b/src/app/(public)/landing-page/_components/banner/Banner.tsx @@ -1,6 +1,6 @@ export default function BannerPage() { return ( - <section className="text-background bg-point py-20"> + <section className="text-background bg-point py-20 dark:text-white"> <div className="mx-auto max-w-3xl px-6 text-center"> <h2 className="break-keep text-xl md:text-2xl font-bold tracking-tight"> 오늘 배운 표현부터 diff --git a/src/app/(public)/landing-page/_components/feature/Feature.tsx b/src/app/(public)/landing-page/_components/feature/Feature.tsx index 778240d..974a7d5 100644 --- a/src/app/(public)/landing-page/_components/feature/Feature.tsx +++ b/src/app/(public)/landing-page/_components/feature/Feature.tsx @@ -15,7 +15,7 @@ const features = [ { icon: LuPencil, title: "메모 기능", - description: "메모를 추가해 헷갈리는 포인트나 사용법을 기록하세요.", + description: "메모를 추가하여 기록하세요.", }, { icon: LuChartNoAxesCombined, @@ -25,17 +25,17 @@ const features = [ { icon: FaRegBookmark, title: "북마크", - description: "중요한 표현는 북마크해서 따로 모아보고 집중 학습하세요.", + description: "중요한 표현을 모아보고 집중 학습하세요.", }, { icon: IoSearchOutline, title: "검색 기능", - description: "등록한 모든 표현를 빠르게 검색하고 찾아볼 수 있어요.", + description: "등록한 표현을 빠르게 검색할 수 있어요.", }, { icon: FiTag, title: "카테고리 분류", - description: "일상생활, 비즈니스 등 사용 장소별로 표현를 분류하세요.", + description: "표현의 카테고리를 분류하세요.", }, ]; @@ -46,15 +46,17 @@ export function FeaturePage() { desc={ <> <span className="font-sekuya text-point">StackPlus</span>가 제공하는 + <br className="block md:hidden" /> 강력한 기능들을 소개합니다. </> } > - <div className="grid grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6 mt-10"> + <div className="grid grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6 mt-10 "> {features.map((feature) => ( <div key={feature.title} - className="rounded-2xl border border-border border-gray-300 p-5 md:px-2 md:py-7 md:text-center text-left" + className="rounded-2xl border + border-gray-300 dark:border-gray-100/10 p-5 md:px-2 md:py-10 md:text-center text-left" > <div className="flex items-center md:block gap-5"> <div diff --git a/src/app/(public)/login/page.tsx b/src/app/(public)/login/page.tsx index e604975..df54687 100644 --- a/src/app/(public)/login/page.tsx +++ b/src/app/(public)/login/page.tsx @@ -9,7 +9,6 @@ import { useState } from "react"; import { FaRegEyeSlash } from "react-icons/fa"; import { SubmitHandler, useForm } from "react-hook-form"; import { signIn } from "@/api/auth"; -import { useTheme } from "next-themes"; type LoginInputs = { email: string; @@ -17,7 +16,6 @@ type LoginInputs = { }; export default function LoginPage() { - const { resolvedTheme } = useTheme(); const router = useRouter(); const [passwordShow, setPasswordShow] = useState(false); @@ -83,7 +81,7 @@ export default function LoginPage() { errors={errors.password?.message} > <FaRegEyeSlash - className={`${resolvedTheme === "dark" ? "text-white" : "text-black"}`} + className="text-black dark:text-white" onClick={() => { setPasswordShow((prev) => !prev); }} diff --git a/src/app/globals.css b/src/app/globals.css index 3b32925..77b3327 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,5 +1,5 @@ @import "tailwindcss"; - +@custom-variant dark (&:where(.dark, .dark *)); @theme { --font-pretendard: var(--font-pretendard); --font-sekuya: var(--font-sekuya); diff --git a/src/components/button/ThemeButton.tsx b/src/components/button/ThemeButton.tsx index 640d421..03fdbaf 100644 --- a/src/components/button/ThemeButton.tsx +++ b/src/components/button/ThemeButton.tsx @@ -12,7 +12,9 @@ export function ThemeToggle() { return ( <button - className={`${theme === "dark" ? "bg-white text-point" : "bg-point text-white"} rounded-sm px-2 py-1 `} + className={` + font-sekuya + ${theme === "dark" ? "bg-white text-point" : "bg-point text-white"} rounded-sm px-2 py-1 `} onClick={() => setTheme(theme === "dark" ? "light" : "dark")} > {theme === "dark" ? "light" : "dark"} diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index e6b1ec1..d7b75ee 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -1,6 +1,6 @@ export default function Footer() { return ( - <footer className="border-t border-gray-200 py-6"> + <footer className="py-6"> <p className="text-center text-xs text-gray-600"> © 2025 <span className="font-medium text-gray-700">Stack Plus</span>{" "} Build your growth diff --git a/src/providers/ThemeProvider.tsx b/src/providers/ThemeProvider.tsx index 7538fe5..c4ff59c 100644 --- a/src/providers/ThemeProvider.tsx +++ b/src/providers/ThemeProvider.tsx @@ -1,4 +1,3 @@ -// providers/ThemeProvider.tsx "use client"; import { ThemeProvider as NextThemesProvider } from "next-themes"; From 73f63bb1edd09b2d2fe4859c252b3e9e487d778d Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:25:24 +0900 Subject: [PATCH 09/18] =?UTF-8?q?feature:=20header=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20layout=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(protected)/_components/layout/Header.tsx | 69 +++---------------- .../_components/layout/Header/Logo.tsx | 15 ++++ .../layout/{ => Header}/MenuLink.tsx | 1 - .../_components/layout/Header/MyMenu.tsx | 36 ++++++++++ .../layout/{ => Header}/MyTooltip.tsx | 11 +-- .../_components/layout/Header/NavBtn.tsx | 33 +++++++++ src/components/button/ThemeButton.tsx | 13 +++- 7 files changed, 113 insertions(+), 65 deletions(-) create mode 100644 src/app/(protected)/_components/layout/Header/Logo.tsx rename src/app/(protected)/_components/layout/{ => Header}/MenuLink.tsx (95%) create mode 100644 src/app/(protected)/_components/layout/Header/MyMenu.tsx rename src/app/(protected)/_components/layout/{ => Header}/MyTooltip.tsx (59%) create mode 100644 src/app/(protected)/_components/layout/Header/NavBtn.tsx diff --git a/src/app/(protected)/_components/layout/Header.tsx b/src/app/(protected)/_components/layout/Header.tsx index 1c86a3e..8f9d77d 100644 --- a/src/app/(protected)/_components/layout/Header.tsx +++ b/src/app/(protected)/_components/layout/Header.tsx @@ -1,82 +1,35 @@ "use client"; import Button from "@/components/button/Button"; -import Link from "next/link"; -import { useState, useRef } from "react"; -import { GiHamburgerMenu } from "react-icons/gi"; +import { useState } from "react"; import { RiLogoutBoxRLine } from "react-icons/ri"; -import { IoClose } from "react-icons/io5"; -import MenuLink from "./MenuLink"; -import { useUserStore } from "@/store/useUserStore"; -import { useWordStats } from "@/hooks/queries/words"; -import MyTooltip from "./MyTooltip"; -import { useClickOutside } from "@/hooks/useClickOutside"; +import MenuLink from "./Header/MenuLink"; import { useLogout } from "@/hooks/auth/useLogout"; import { ThemeToggle } from "@/components/button/ThemeButton"; +import MyMenu from "./Header/MyMenu"; +import NavBtn from "./Header/NavBtn"; +import Logo from "./Header/Logo"; export default function Header() { - const { id, nickname } = useUserStore(); const { logout } = useLogout(); const [isMenuOpen, setIsMenuOpen] = useState(false); - const [isMyOpen, setIsMyOpen] = useState(false); - const { total, memo, bookmark } = useWordStats(); - - const myWrapperRef = useRef<HTMLDivElement | null>(null); - - useClickOutside(myWrapperRef, () => setIsMyOpen(false), isMyOpen); return ( - <header className="sticky top-0 z-20 p-6 bg-background backdrop-blur-md border-b border-gray-200"> + <header className="sticky top-0 z-20 p-6 bg-background backdrop-blur-md border-b border-gray-200 dark:border-gray-100/10"> <div className="mx-auto flex max-w-6xl items-center justify-between"> - <Link - href="/dashboard" - className="text-xl font-sekuya text-black transition-colors hover:text-point group" - > - STACK - <span className="text-point transition-colors group-hover:text-black"> - PLUS - </span> - </Link> - - <div className="relative flex items-center gap-3 text-gray-600"> - <div ref={myWrapperRef} className="relative"> - <ThemeToggle /> + <Logo /> - <Button - onClick={() => setIsMyOpen((prev) => !prev)} - variant="text_underline" - > - {id && nickname} - </Button> + <div className="relative flex items-center gap-1 text-gray-600"> + <ThemeToggle /> - {isMyOpen && ( - <MyTooltip - nickname={nickname} - total={total} - memo={memo} - bookmark={bookmark} - /> - )} - </div> + <MyMenu /> <Button type="button" variant="text" className="p-1" onClick={logout}> <RiLogoutBoxRLine className="text-lg" /> </Button> - <Button - type="button" - variant="text" - className="p-1" - onClick={() => setIsMenuOpen(!isMenuOpen)} - > - {isMenuOpen ? ( - <IoClose className="text-xl" /> - ) : ( - <GiHamburgerMenu className="text-xl" /> - )} - </Button> - + <NavBtn isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} /> <MenuLink isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} /> </div> </div> diff --git a/src/app/(protected)/_components/layout/Header/Logo.tsx b/src/app/(protected)/_components/layout/Header/Logo.tsx new file mode 100644 index 0000000..443910d --- /dev/null +++ b/src/app/(protected)/_components/layout/Header/Logo.tsx @@ -0,0 +1,15 @@ +import Link from "next/link"; + +export default function Logo() { + return ( + <Link + href="/dashboard" + className="text-xl font-sekuya text-black dark:text-white transition-colors hover:text-point group" + > + STACK + <span className="text-point transition-colors group-hover:text-black dark:group-hover:text-white"> + PLUS + </span> + </Link> + ); +} diff --git a/src/app/(protected)/_components/layout/MenuLink.tsx b/src/app/(protected)/_components/layout/Header/MenuLink.tsx similarity index 95% rename from src/app/(protected)/_components/layout/MenuLink.tsx rename to src/app/(protected)/_components/layout/Header/MenuLink.tsx index 10fc8c7..1036d8f 100644 --- a/src/app/(protected)/_components/layout/MenuLink.tsx +++ b/src/app/(protected)/_components/layout/Header/MenuLink.tsx @@ -1,4 +1,3 @@ -// import { useClickOutside } from "@/hooks/useClickOutside"; import Link from "next/link"; import { usePathname } from "next/navigation"; diff --git a/src/app/(protected)/_components/layout/Header/MyMenu.tsx b/src/app/(protected)/_components/layout/Header/MyMenu.tsx new file mode 100644 index 0000000..2d1f1e2 --- /dev/null +++ b/src/app/(protected)/_components/layout/Header/MyMenu.tsx @@ -0,0 +1,36 @@ +import Button from "@/components/button/Button"; +import { useWordStats } from "@/hooks/queries/words"; +import { useClickOutside } from "@/hooks/useClickOutside"; +import { useUserStore } from "@/store/useUserStore"; +import { useRef, useState } from "react"; +import MyTooltip from "./MyTooltip"; + +export default function MyMenu() { + const { id, nickname } = useUserStore(); + const { total, memo, bookmark } = useWordStats(); + const [isMyOpen, setIsMyOpen] = useState(false); + + const myWrapperRef = useRef<HTMLDivElement | null>(null); + useClickOutside(myWrapperRef, () => setIsMyOpen(false), isMyOpen); + + return ( + <div ref={myWrapperRef} className="relative"> + <Button + className="w-10 sm:w-auto" + onClick={() => setIsMyOpen((prev) => !prev)} + variant="text_underline" + > + <span className="block truncate">{id && nickname}</span> + </Button> + + {isMyOpen && ( + <MyTooltip + nickname={nickname} + total={total} + memo={memo} + bookmark={bookmark} + /> + )} + </div> + ); +} diff --git a/src/app/(protected)/_components/layout/MyTooltip.tsx b/src/app/(protected)/_components/layout/Header/MyTooltip.tsx similarity index 59% rename from src/app/(protected)/_components/layout/MyTooltip.tsx rename to src/app/(protected)/_components/layout/Header/MyTooltip.tsx index f68ba70..eca20bf 100644 --- a/src/app/(protected)/_components/layout/MyTooltip.tsx +++ b/src/app/(protected)/_components/layout/Header/MyTooltip.tsx @@ -17,16 +17,19 @@ export default function MyTooltip({ bg-white px-5 py-3 border border-gray-200 shadow-sm" > <p className="font-bold"> - 닉네임: <span className="bg-point px-1">{nickname}</span> + 닉네임: <span className="bg-point text-white px-1">{nickname}</span> </p> <p className="font-bold mt-2"> - 현재 저장 표현: <span className="bg-point px-1">{total}</span> + 현재 저장 표현:{" "} + <span className="bg-point text-white px-1">{total}</span> </p> <p className="font-bold mt-2"> - 메모가 있는 표현: <span className="bg-point px-1">{memo}</span> + 메모가 있는 표현:{" "} + <span className="bg-point text-white px-1">{memo}</span> </p> <p className="font-bold mt-2"> - 북마크가 있는 표현: <span className="bg-point px-1">{bookmark}</span> + 북마크가 있는 표현:{" "} + <span className="bg-point text-white px-1">{bookmark}</span> </p> </div> ); diff --git a/src/app/(protected)/_components/layout/Header/NavBtn.tsx b/src/app/(protected)/_components/layout/Header/NavBtn.tsx new file mode 100644 index 0000000..1f32291 --- /dev/null +++ b/src/app/(protected)/_components/layout/Header/NavBtn.tsx @@ -0,0 +1,33 @@ +"use client"; +import Button from "@/components/button/Button"; +import { useClickOutside } from "@/hooks/useClickOutside"; +import { useRef } from "react"; +import { GiHamburgerMenu } from "react-icons/gi"; +import { IoClose } from "react-icons/io5"; + +export default function NavBtn({ + isMenuOpen, + setIsMenuOpen, +}: { + isMenuOpen: boolean; + setIsMenuOpen: (boolean: boolean) => void; +}) { + const menuRef = useRef<HTMLDivElement | null>(null); + useClickOutside(menuRef, () => setIsMenuOpen(false), isMenuOpen); + return ( + <div ref={menuRef}> + <Button + type="button" + variant="text" + className="p-1" + onClick={() => setIsMenuOpen(!isMenuOpen)} + > + {isMenuOpen ? ( + <IoClose className="text-xl" /> + ) : ( + <GiHamburgerMenu className="text-xl" /> + )} + </Button> + </div> + ); +} diff --git a/src/components/button/ThemeButton.tsx b/src/components/button/ThemeButton.tsx index 03fdbaf..aa081ac 100644 --- a/src/components/button/ThemeButton.tsx +++ b/src/components/button/ThemeButton.tsx @@ -1,4 +1,5 @@ "use client"; +import { useMobileSize } from "@/hooks/useMobileSize"; import { useTheme } from "next-themes"; import { useEffect, useState } from "react"; @@ -6,6 +7,8 @@ export function ThemeToggle() { const { theme, setTheme } = useTheme(); const [mounted, setMounted] = useState(false); + const isMobile = useMobileSize(); + // eslint-disable-next-line react-hooks/set-state-in-effect useEffect(() => setMounted(true), []); if (!mounted) return null; @@ -13,11 +16,17 @@ export function ThemeToggle() { return ( <button className={` - font-sekuya + font-sekuya text-sm md:text-md ${theme === "dark" ? "bg-white text-point" : "bg-point text-white"} rounded-sm px-2 py-1 `} onClick={() => setTheme(theme === "dark" ? "light" : "dark")} > - {theme === "dark" ? "light" : "dark"} + {theme === "dark" + ? isMobile + ? "☀️" + : "light" + : isMobile + ? "🌙" + : "dark"} </button> ); } From 5264f6a10b0e58088a839360347028a162b7e393 Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:39:25 +0900 Subject: [PATCH 10/18] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=9B=84=20sync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/layout/Header/NavBtn.tsx | 1 - src/app/(protected)/dashboard/page.tsx | 18 +++--------------- src/app/(public)/join/page.tsx | 2 +- src/app/(public)/login/page.tsx | 15 ++++++++++++--- src/providers/AuthProvider.tsx | 2 +- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/app/(protected)/_components/layout/Header/NavBtn.tsx b/src/app/(protected)/_components/layout/Header/NavBtn.tsx index 1f32291..4c4bef9 100644 --- a/src/app/(protected)/_components/layout/Header/NavBtn.tsx +++ b/src/app/(protected)/_components/layout/Header/NavBtn.tsx @@ -1,4 +1,3 @@ -"use client"; import Button from "@/components/button/Button"; import { useClickOutside } from "@/hooks/useClickOutside"; import { useRef } from "react"; diff --git a/src/app/(protected)/dashboard/page.tsx b/src/app/(protected)/dashboard/page.tsx index 3d020b6..2c793bd 100644 --- a/src/app/(protected)/dashboard/page.tsx +++ b/src/app/(protected)/dashboard/page.tsx @@ -1,26 +1,14 @@ "use client"; -import { useEffect } from "react"; import { AllWordsBanner } from "./_components/AllWordsBanner"; import { MemoBanner } from "./_components/memo/MemoBanner"; import { RecentWords } from "./_components/RecentWords"; import { RecordStudy } from "./_components/record/RecordStudy"; -import { getMyProfile } from "@/api/profile"; import { useUserStore } from "@/store/useUserStore"; export default function DashboardPage() { - const setUser = useUserStore((state) => state.setUser); - useEffect(() => { - const init = async () => { - const profile = await getMyProfile(); - if (profile) { - setUser({ - id: profile.id, - nickname: profile.nickname, - }); - } - }; - init(); - }, []); + const { isInitialized } = useUserStore(); + if (!isInitialized) return null; + return ( <div className=" diff --git a/src/app/(public)/join/page.tsx b/src/app/(public)/join/page.tsx index 0afaf11..dbca93c 100644 --- a/src/app/(public)/join/page.tsx +++ b/src/app/(public)/join/page.tsx @@ -56,7 +56,7 @@ export default function JoinPage() { return ( <div className="flex min-h-screen items-center justify-center px-6"> - <div className="w-full max-w-lg rounded-2xl p-10 shadow-sm"> + <div className="w-full max-w-lg rounded-2xl p-3 sm:p-10 shadow-sm"> <Title title="회원가입" desc={ diff --git a/src/app/(public)/login/page.tsx b/src/app/(public)/login/page.tsx index df54687..fa1c2b9 100644 --- a/src/app/(public)/login/page.tsx +++ b/src/app/(public)/login/page.tsx @@ -9,6 +9,7 @@ import { useState } from "react"; import { FaRegEyeSlash } from "react-icons/fa"; import { SubmitHandler, useForm } from "react-hook-form"; import { signIn } from "@/api/auth"; +import { useUserStore } from "@/store/useUserStore"; type LoginInputs = { email: string; @@ -25,25 +26,33 @@ export default function LoginPage() { formState: { errors }, } = useForm<LoginInputs>(); + const { setUser, setInitialized } = useUserStore(); const onSubmit: SubmitHandler<LoginInputs> = async (formData) => { try { - await signIn({ + const data = await signIn({ email: formData.email, password: formData.password, }); + + setUser({ + id: data.user.id, + nickname: data.user.user_metadata?.nickname ?? null, + }); + setInitialized(); toast.success("로그인에 성공했습니다."); router.push("/dashboard"); } catch (error) { if (error instanceof Error) { toast.error(error.message); + } else { + toast.error("로그인 중 오류가 발생했습니다."); } - toast.error("로그인 중 오류가 발생했습니다."); } }; return ( <div className="flex min-h-screen items-center justify-center px-6"> - <div className="w-full max-w-lg rounded-2xl p-10 shadow-sm"> + <div className="w-full max-w-lg rounded-2xl p-3 sm:p-10 shadow-sm"> <Title title="환영합니다" desc={ diff --git a/src/providers/AuthProvider.tsx b/src/providers/AuthProvider.tsx index a7a6834..2623909 100644 --- a/src/providers/AuthProvider.tsx +++ b/src/providers/AuthProvider.tsx @@ -22,7 +22,7 @@ export default function AuthProvider({ } try { - const profile = await getMyProfile(session.user.id); + const profile = await getMyProfile(); if (!profile) { setUser({ id: session.user.id, From 61c7b8bcf85051874e7841265d6c22c92987a9d2 Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:17:39 +0900 Subject: [PATCH 11/18] =?UTF-8?q?style:=20=EB=8B=A4=ED=81=AC=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=EC=97=90=20=EB=A7=9E=EA=B2=8C=20home=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../add-word-button/AddWordButton.tsx | 7 ++- src/app/(protected)/_components/list/List.tsx | 4 +- .../_components/list/ListContent.tsx | 4 +- .../list/list-actions/ListActionsView.tsx | 22 ++++--- .../_components/modal/word/ModalBody.tsx | 7 ++- .../dashboard/_components/RecentWords.tsx | 6 +- .../dashboard/_components/common/Title.tsx | 4 +- .../_components/common/banner/Banner.tsx | 1 + .../_components/common/banner/BannerTitle.tsx | 31 ++++++++- .../_components/record/RecordChart.tsx | 3 +- .../RecordLineChart/RecordChartToggle.tsx | 63 ++++++++----------- .../RecordLineChart/RecordChartView.tsx | 5 +- src/app/(protected)/dashboard/page.tsx | 2 +- src/components/button/Button.tsx | 1 + 14 files changed, 98 insertions(+), 62 deletions(-) diff --git a/src/app/(protected)/_components/add-word-button/AddWordButton.tsx b/src/app/(protected)/_components/add-word-button/AddWordButton.tsx index 0d55adb..f81d511 100644 --- a/src/app/(protected)/_components/add-word-button/AddWordButton.tsx +++ b/src/app/(protected)/_components/add-word-button/AddWordButton.tsx @@ -3,7 +3,7 @@ import Button from "@/components/button/Button"; import { useUploadWordMutation } from "@/hooks/queries/words/useUploadWordMutation"; import { WordCreateInput } from "@/types/word"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { CiCirclePlus } from "react-icons/ci"; import Modal from "../modal/Modal"; import WordModal from "../modal/word/WordModal"; @@ -45,7 +45,10 @@ export default function AddWordButton({ <Button type="button" variant="text_underline" - className="ml-auto text-sm text-gray-700 hover:text-black" + className="ml-auto text-sm text-gray-700 hover:text-black + + dark:text-white dark:hover:text-white + " onClick={() => setOpenModal(true)} > {children} <CiCirclePlus className="text-base ml-1" /> diff --git a/src/app/(protected)/_components/list/List.tsx b/src/app/(protected)/_components/list/List.tsx index c220a7b..201ad20 100644 --- a/src/app/(protected)/_components/list/List.tsx +++ b/src/app/(protected)/_components/list/List.tsx @@ -47,7 +47,9 @@ export function List({ className=" border border-gray-200 rounded-lg group relative - gap-3 px-5 py-7 pb-13" + gap-3 px-5 py-7 pb-13 + + " > {isRecordPage ? ( <div>{content}</div> diff --git a/src/app/(protected)/_components/list/ListContent.tsx b/src/app/(protected)/_components/list/ListContent.tsx index 9031e09..4640e2f 100644 --- a/src/app/(protected)/_components/list/ListContent.tsx +++ b/src/app/(protected)/_components/list/ListContent.tsx @@ -32,7 +32,7 @@ export default function ListContent({ return ( <> <div className="gap-1 md:gap-6 w-full"> - <p className="text-xl font-bold mb-2 text-black"> + <p className="text-xl font-bold mb-2 text-black dark:text-white"> {expression} <span> : {meaning}</span> </p> @@ -53,7 +53,7 @@ export default function ListContent({ </p> )} - <p className="absolute left-5 bottom-7 text-sm text-gray-600 truncate break-keep"> + <p className="absolute left-5 bottom-7 text-sm text-gray-600 dark:text-gray-100 truncate break-keep"> {usage} </p> diff --git a/src/app/(protected)/_components/list/list-actions/ListActionsView.tsx b/src/app/(protected)/_components/list/list-actions/ListActionsView.tsx index 115f71a..1e24db0 100644 --- a/src/app/(protected)/_components/list/list-actions/ListActionsView.tsx +++ b/src/app/(protected)/_components/list/list-actions/ListActionsView.tsx @@ -57,7 +57,7 @@ export default function ListActionsView({ className={`${buttonStyles} pointer-events-none`} disabled > - <TfiWrite className=" text-gray-600" /> + <TfiWrite className=" text-gray-600 dark:text-white" /> </Button> )} <Button @@ -72,9 +72,9 @@ export default function ListActionsView({ } > {word.bookmarked ? ( - <FaBookmark className="text-gray-700" /> + <FaBookmark className="text-gray-700 dark:text-white" /> ) : ( - <FaRegBookmark className="text-gray-600" /> + <FaRegBookmark className="text-gray-600 dark:text-white" /> )} </Button> <div ref={dropdownWrapperRef}> @@ -84,20 +84,28 @@ export default function ListActionsView({ className={`${buttonStyles}`} onClick={toggleDropdown} > - <BsThreeDots className="text-gray-600" /> + <BsThreeDots className="text-gray-600 dark:text-white" /> </Button> {isDropdownOpen && ( <ul className="absolute z-10 right-0 -bottom-23 w-max rounded-xl - bg-white px-5 py-3 border border-gray-200 shadow-sm" + bg-white px-5 py-3 border border-gray-200 shadow-sm " > <li> - <Button onClick={openEditModal} variant="text"> + <Button + onClick={openEditModal} + variant="text" + className="dark:text-black" + > 수정하기 </Button> </li> <li> - <Button onClick={openDeleteModal} variant="text"> + <Button + onClick={openDeleteModal} + variant="text" + className="dark:text-black" + > 삭제하기 </Button> </li> diff --git a/src/app/(protected)/_components/modal/word/ModalBody.tsx b/src/app/(protected)/_components/modal/word/ModalBody.tsx index fba31e8..2ba16ff 100644 --- a/src/app/(protected)/_components/modal/word/ModalBody.tsx +++ b/src/app/(protected)/_components/modal/word/ModalBody.tsx @@ -11,14 +11,15 @@ type ModalBodyProps = { export default function ModalBody({ register, errors }: ModalBodyProps) { const InputStyles = - "w-full rounded-md border border-gray-300 px-3 py-2 text-sm text-black placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-900 focus:border-gray-900 transition"; + "w-full border border-gray-300 px-3 py-2 text-sm text-black placeholder place-gray-100 focus:outline-none focus:ring-1 transition"; const SelectStyles = - "w-full appearance-none rounded-md border border-gray-300 px-3 py-2 pr-10 text-sm text-black bg-white focus:outline-none focus:ring-2 focus:ring-gray-900 focus:border-gray-900 transition"; + "w-full appearance-none rounded-md border border-gray-300 px-3 py-2 pr-10 text-sm text-black bg-white focus:outline-none focus:ring-1 transition"; return ( <> <ModalField label="표현*"> <Input + className={InputStyles} autoFocus {...register("expression", { required: "표현을 입력해주세요.", @@ -29,6 +30,7 @@ export default function ModalBody({ register, errors }: ModalBodyProps) { </ModalField> <ModalField label="뜻*"> <Input + className={InputStyles} {...register("meaning", { required: "뜻을 입력해주세요.", })} @@ -38,6 +40,7 @@ export default function ModalBody({ register, errors }: ModalBodyProps) { </ModalField> <ModalField label="예문"> <Input + className={InputStyles} {...register("sentence")} placeholder="I bought a book yesterday." /> diff --git a/src/app/(protected)/dashboard/_components/RecentWords.tsx b/src/app/(protected)/dashboard/_components/RecentWords.tsx index c660b33..09cdfd1 100644 --- a/src/app/(protected)/dashboard/_components/RecentWords.tsx +++ b/src/app/(protected)/dashboard/_components/RecentWords.tsx @@ -17,8 +17,10 @@ export function RecentWords() { return ( <div className="h-full"> - <div className="mb-5 flex items-center justify-between"> - <Title className="text-base">최근 등록한 표현 +
    + + 최근 등록한 표현 + 추가
    diff --git a/src/app/(protected)/dashboard/_components/common/Title.tsx b/src/app/(protected)/dashboard/_components/common/Title.tsx index 937d720..85f638b 100644 --- a/src/app/(protected)/dashboard/_components/common/Title.tsx +++ b/src/app/(protected)/dashboard/_components/common/Title.tsx @@ -5,6 +5,8 @@ type TitleProps = { export function Title({ children, className = "" }: TitleProps) { return ( -

    {children}

    +

    + {children} +

    ); } diff --git a/src/app/(protected)/dashboard/_components/common/banner/Banner.tsx b/src/app/(protected)/dashboard/_components/common/banner/Banner.tsx index 8983923..f81154c 100644 --- a/src/app/(protected)/dashboard/_components/common/banner/Banner.tsx +++ b/src/app/(protected)/dashboard/_components/common/banner/Banner.tsx @@ -10,6 +10,7 @@ export function Banner({ children, className = "" }: BannerProps) { border border-gray-200 transition-colors hover:bg-background + focus:outline-none focus:ring-2 focus:ring-gray-900/10 flex flex-col justify-between group h-full diff --git a/src/app/(protected)/dashboard/_components/common/banner/BannerTitle.tsx b/src/app/(protected)/dashboard/_components/common/banner/BannerTitle.tsx index bf8e450..46669d4 100644 --- a/src/app/(protected)/dashboard/_components/common/banner/BannerTitle.tsx +++ b/src/app/(protected)/dashboard/_components/common/banner/BannerTitle.tsx @@ -1,13 +1,38 @@ +import clsx from "clsx"; + type BannerTitleProps = { title: React.ReactNode; description: React.ReactNode; + variant?: "default" | "white"; }; -export function BannerTitle({ title, description }: BannerTitleProps) { +export function BannerTitle({ + title, + description, + variant = "default", +}: BannerTitleProps) { + const isWhite = variant === "white"; + return (
    -

    {title}

    -

    {description}

    +

    + {title} +

    +

    + {description} +

    ); } diff --git a/src/app/(protected)/dashboard/_components/record/RecordChart.tsx b/src/app/(protected)/dashboard/_components/record/RecordChart.tsx index 6959d47..61bee39 100644 --- a/src/app/(protected)/dashboard/_components/record/RecordChart.tsx +++ b/src/app/(protected)/dashboard/_components/record/RecordChart.tsx @@ -24,8 +24,9 @@ export function RecordChart() { : yearlyData; return ( - + void; + period: Period; + onChange: (period: Period) => void; }; +const PERIOD_OPTIONS: { value: Period; label: string }[] = [ + { value: "daily", label: "일간" }, + { value: "monthly", label: "월간" }, + { value: "yearly", label: "연간" }, +]; + export function RecordChartToggle({ period, onChange }: Props) { return ( -
    - - - - - +
    + {PERIOD_OPTIONS.map(({ value, label }) => ( + + ))}
    ); } diff --git a/src/app/(protected)/dashboard/_components/record/RecordLineChart/RecordChartView.tsx b/src/app/(protected)/dashboard/_components/record/RecordLineChart/RecordChartView.tsx index b43b1b3..4f470f0 100644 --- a/src/app/(protected)/dashboard/_components/record/RecordLineChart/RecordChartView.tsx +++ b/src/app/(protected)/dashboard/_components/record/RecordLineChart/RecordChartView.tsx @@ -34,13 +34,14 @@ export function RecordChartView({ data }: ChartProps) { fontSize: "12px", borderRadius: "8px", borderColor: "#E5E7EB", + color: "#000", }} /> diff --git a/src/app/(protected)/dashboard/page.tsx b/src/app/(protected)/dashboard/page.tsx index 2c793bd..3c6c602 100644 --- a/src/app/(protected)/dashboard/page.tsx +++ b/src/app/(protected)/dashboard/page.tsx @@ -7,7 +7,7 @@ import { useUserStore } from "@/store/useUserStore"; export default function DashboardPage() { const { isInitialized } = useUserStore(); - if (!isInitialized) return null; + if (!isInitialized) return
    로딩 중..
    ; return (
    = { px-4 py-3.5 hover:bg-point dark:hover:bg-point + dark:hover:text-white `, text: ` From 3e156f9b8698bc5029a4dfc4aaa281f8d61744fb Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Wed, 25 Mar 2026 21:34:24 +0900 Subject: [PATCH 12/18] =?UTF-8?q?refactor:=20=EC=B0=A8=ED=8A=B8=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC=20?= =?UTF-8?q?=EB=B0=8F=20=ED=83=80=EC=9E=85=20=EC=A4=91=EB=B3=B5=20=EC=9D=B4?= =?UTF-8?q?=EC=8A=88=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/next.svg | 2 +- src/api/types/words.ts | 10 ++++ src/api/words.ts | 15 +---- src/app/(protected)/_components/list/List.tsx | 1 - .../_components/list/ListContent.tsx | 11 ++-- .../_components/modal/word/ModalBody.tsx | 2 +- .../dashboard/_components/RecentWords.tsx | 2 - .../_components/record/RecordChart.tsx | 24 +++----- .../RecordLineChart/RecordChartToggle.tsx | 9 +-- .../RecordLineChart/RecordChartView.tsx | 4 +- .../RecordLineChart/useRecordChartData.ts | 58 +++++-------------- src/app/(protected)/dashboard/page.tsx | 6 +- src/app/globals.css | 2 +- src/components/button/Button.tsx | 8 +-- src/components/input/Input.tsx | 2 +- src/hooks/queries/words/querykey.ts | 2 +- src/hooks/queries/words/useWordsQuery.ts | 35 ++--------- src/hooks/useMobileSize.ts | 2 +- src/types/chartPeriod.ts | 13 +++++ src/utils/defaultChart.ts | 21 +++++++ 20 files changed, 95 insertions(+), 134 deletions(-) create mode 100644 src/types/chartPeriod.ts create mode 100644 src/utils/defaultChart.ts diff --git a/public/next.svg b/public/next.svg index 5174b28..411fd66 100644 --- a/public/next.svg +++ b/public/next.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/api/types/words.ts b/src/api/types/words.ts index 51f15eb..b846af6 100644 --- a/src/api/types/words.ts +++ b/src/api/types/words.ts @@ -1,3 +1,5 @@ +import { FilterValue } from "@/constants/filter"; + export interface WordsRequest { id: number; user_id: string; @@ -15,3 +17,11 @@ export type WordsResponse = { words: WordsRequest[]; totalCount: number; }; + +export type WordsQueryRequest = { + filter?: FilterValue; + keyword?: string; + wordId?: string | null; + page?: number; + pageSize?: number; +}; diff --git a/src/api/words.ts b/src/api/words.ts index 99bea6d..cd4c74b 100644 --- a/src/api/words.ts +++ b/src/api/words.ts @@ -1,5 +1,5 @@ import { FilterValue } from "@/constants/filter"; -import { WordsRequest } from "./types/words"; +import { WordsQueryRequest, WordsRequest } from "./types/words"; import { WordCreateInput, WordUpdateInput } from "@/types/word"; import { DuplicateWordError } from "./types/errors"; import { supabase } from "@/lib/supabase"; @@ -18,17 +18,9 @@ export async function getWords({ filter, keyword, wordId, - range, page = 1, pageSize = 20, -}: { - filter?: FilterValue; - keyword?: string; - wordId?: string | null; - range?: { from: number; to: number }; - page?: number; - pageSize?: number; -}): Promise<{ words: WordsRequest[]; totalCount: number }> { +}: WordsQueryRequest): Promise<{ words: WordsRequest[]; totalCount: number }> { const user = await getUserOrThrow(); let query = supabase @@ -61,9 +53,8 @@ export async function getWords({ } const from = (page - 1) * pageSize; - const to = from + pageSize - 1; - query = query.range(from, to); + query = query.range(from, from + pageSize - 1); const { data, error, count } = await query; diff --git a/src/app/(protected)/_components/list/List.tsx b/src/app/(protected)/_components/list/List.tsx index 201ad20..4420ef9 100644 --- a/src/app/(protected)/_components/list/List.tsx +++ b/src/app/(protected)/_components/list/List.tsx @@ -48,7 +48,6 @@ export function List({ border border-gray-200 rounded-lg group relative gap-3 px-5 py-7 pb-13 - " > {isRecordPage ? ( diff --git a/src/app/(protected)/_components/list/ListContent.tsx b/src/app/(protected)/_components/list/ListContent.tsx index 4640e2f..cab8794 100644 --- a/src/app/(protected)/_components/list/ListContent.tsx +++ b/src/app/(protected)/_components/list/ListContent.tsx @@ -19,7 +19,6 @@ export default function ListContent({ const lowerExpression = expression.toLowerCase(); const idx = lowerSentence.indexOf(lowerExpression); - const textareaRef = useRef(null); useEffect(() => { @@ -32,19 +31,19 @@ export default function ListContent({ return ( <>
    -

    +

    {expression} : {meaning}

    {sentence && ( -

    +

    {idx === -1 ? ( sentence ) : ( <> {sentence.slice(0, idx)} - + {sentence.slice(idx, idx + expression.length)} {sentence.slice(idx + expression.length)} @@ -60,11 +59,11 @@ export default function ListContent({ {memo && (