diff --git a/package-lock.json b/package-lock.json index ce46fb92..2065998f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ }, "devDependencies": { "@chromatic-com/storybook": "^4.1.2", + "@emotion/is-prop-valid": "^1.4.0", "@storybook/addon-a11y": "^10.0.2", "@storybook/addon-docs": "^10.0.2", "@storybook/addon-onboarding": "^10.0.2", @@ -2146,6 +2147,23 @@ "tslib": "^2.4.0" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", diff --git a/package.json b/package.json index 1217c601..c947c29b 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ }, "devDependencies": { "@chromatic-com/storybook": "^4.1.2", + "@emotion/is-prop-valid": "^1.4.0", "@storybook/addon-a11y": "^10.0.2", "@storybook/addon-docs": "^10.0.2", "@storybook/addon-onboarding": "^10.0.2", diff --git a/src/app/(auth)/reset-password/layout.tsx b/src/app/(auth)/reset-password/layout.tsx new file mode 100644 index 00000000..b21271bf --- /dev/null +++ b/src/app/(auth)/reset-password/layout.tsx @@ -0,0 +1,28 @@ +import { Metadata } from "next"; +import { ReactNode } from "react"; + +export const metadata: Metadata = { + title: "비밀번호 재설정", + openGraph: { + title: "Coworkers", + description: "비밀번호 재설정 페이지", + type: "website", + url: "https://coworkes.com/signin", + locale: "ko_KR", + siteName: "Coworkers", + images: [ + { + url: "https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Coworkers/user/2449/open_graph.jpg", + width: 1200, + height: 630, + alt: "Coworkers", + }, + ], + }, +}; + +const Layout = ({ children }: { children: ReactNode }) => { + return
{children}
; +}; + +export default Layout; diff --git a/src/app/(auth)/reset-password/reset-password-page.tsx b/src/app/(auth)/reset-password/reset-password-page.tsx index 14d5fdbc..5aab41b4 100644 --- a/src/app/(auth)/reset-password/reset-password-page.tsx +++ b/src/app/(auth)/reset-password/reset-password-page.tsx @@ -57,6 +57,8 @@ const ResetPasswordPage = () => { type={showPassword ? "text" : "password"} errorMessage={errors.password?.message} rightIconClassName="pr-2" + autoComplete="new-password" + aria-invalid={!!errors.password} rightIcon={ @@ -54,11 +58,11 @@ const GnbHeader = () => { }, { label: "계정 설정", - onClick: () => router.replace("/mypage"), + onClick: () => router.push("/mypage"), }, { label: "팀 참여", - onClick: () => router.replace("/taketeam"), + onClick: () => router.push("/taketeam"), }, { label: "로그아웃", diff --git a/src/components/gnb/header/mobile-sidebar.tsx b/src/components/gnb/header/mobile-sidebar.tsx index a6931577..15e889ea 100644 --- a/src/components/gnb/header/mobile-sidebar.tsx +++ b/src/components/gnb/header/mobile-sidebar.tsx @@ -2,7 +2,8 @@ import React, { useEffect, useState } from "react"; import { createPortal } from "react-dom"; -import { Button, Icon } from "@/components/index"; +import Button from "@/components/button/button"; +import Icon from "@/components/icon/Icon"; import SidebarMenu from "../sidebar/_components/sidebar-menu/sidebar-menu"; import { usePathname } from "next/navigation"; import { motion } from "framer-motion"; @@ -18,7 +19,7 @@ const MobileSidebar = ({ onClose }: MobileSidebarProps) => { const pathname = usePathname(); const segments = pathname.split("/"); const currentTeamId = segments[segments.length - 1]; - const isBoardPage = pathname === "/boards"; + const isBoardPage = pathname.startsWith("/boards"); const isMyHistoryPage = pathname === "/myhistory"; const { data: userInfo } = useGetUserInfoQuery(); const [isMounted, setIsMounted] = useState(false); @@ -46,7 +47,7 @@ const MobileSidebar = ({ onClose }: MobileSidebarProps) => { transition={{ duration: 0.1, ease: "easeIn" }} >
-
@@ -58,7 +59,6 @@ const MobileSidebar = ({ onClose }: MobileSidebarProps) => { >
= { title: "components/SidebarDropdown", component: SidebarDropdown, tags: ["autodocs"], - args: { - isOpen: false, - onToggle: () => alert("드롭다운 닫기"), - }, - argTypes: { - isOpen: { - control: "boolean", - description: "드롭다운 열림 상태", - }, - onToggle: { - action: "onClose", - description: "드롭다운 닫기 함수", - }, - }, decorators: [ (Story) => ( -
- -
+ +
+ +
+
), ], + args: { + isSidebarOpen: true, + currentTeamId: "1", + setIsOpen: () => console.log("사이드바 열림"), + onToggle: () => console.log("드롭다운 열림"), + }, + parameters: { + nextjs: { + appDirectory: true, + }, + }, }; +export default meta; type Story = StoryObj; export const Default: Story = { render: (args) => { const [isOpen, setIsOpen] = useState(args.isOpen); + const handleToggle = () => { + setIsOpen(!isOpen); + args.onToggle?.(); + }; + return ( setIsOpen(!isOpen)} + setIsOpen={setIsOpen} + onToggle={handleToggle} /> ); }, @@ -51,8 +96,27 @@ export const Default: Story = { export const InitialOpen: Story = { ...Default, args: { + ...Default.args, isOpen: true, }, }; -export default meta; +export const SidebarClosed: Story = { + render: (args) => { + return ; + }, + decorators: [ + (Story) => ( + +
+ +
+
+ ), + ], + args: { + isSidebarOpen: false, + isOpen: false, + currentTeamId: "1", + }, +}; diff --git a/src/components/gnb/sidebar/_components/sidebar-footer/sidebar-footer.stories.tsx b/src/components/gnb/sidebar/_components/sidebar-footer/sidebar-footer.stories.tsx new file mode 100644 index 00000000..666d7175 --- /dev/null +++ b/src/components/gnb/sidebar/_components/sidebar-footer/sidebar-footer.stories.tsx @@ -0,0 +1,123 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import SidebarFooter from "./sidebar-footer"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +const MOCK_USER_DATA = { + id: 1, + email: "test@example.com", + nickname: "가상의 유저", + image: "", + memberships: [ + { + group: { + id: 1, + name: "코드잇 팀", + }, + }, + { + group: { + id: 2, + name: "코워커스 팀", + }, + }, + ], +}; + +const loggedInClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + staleTime: Infinity, + }, + }, +}); + +const QUERY_KEY = ["userInfo"]; + +loggedInClient.setQueryData(QUERY_KEY, MOCK_USER_DATA); + +const loggedOutClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}); + +const meta = { + title: "components/SidebarFooter", + component: SidebarFooter, + tags: ["autodocs"], + decorators: [(Story) => ], + args: { + currentTeamId: "1", + }, + parameters: { + nextjs: { + appDirectory: true, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const FooterOpenLoggedIn: Story = { + args: { + isSidebarOpen: true, + }, + decorators: [ + (Story) => ( + +
+ +
+
+ ), + ], +}; + +export const FooterOpenLoggedOut: Story = { + args: { + isSidebarOpen: true, + }, + decorators: [ + (Story) => ( + +
+ +
+
+ ), + ], +}; + +export const FooterClosedLoggedIn: Story = { + args: { + isSidebarOpen: false, + }, + decorators: [ + (Story) => ( + +
+ +
+
+ ), + ], +}; + +export const FooterClosedLoggedOut: Story = { + args: { + isSidebarOpen: false, + }, + decorators: [ + (Story) => ( + +
+ +
+
+ ), + ], +}; diff --git a/src/components/gnb/sidebar/_components/sidebar-footer/sidebar-footer.tsx b/src/components/gnb/sidebar/_components/sidebar-footer/sidebar-footer.tsx index 30e5047a..23e6d5d8 100644 --- a/src/components/gnb/sidebar/_components/sidebar-footer/sidebar-footer.tsx +++ b/src/components/gnb/sidebar/_components/sidebar-footer/sidebar-footer.tsx @@ -42,7 +42,7 @@ const SidebarFooter = ({ /> } items={[ - { label: "계정 설정", onClick: () => router.replace("/mypage") }, + { label: "계정 설정", onClick: () => router.push("/mypage") }, { label: "로그아웃", onClick: handleLogout, @@ -66,7 +66,7 @@ const SidebarFooter = ({ {userInfo.nickname} - {userInfo?.memberships?.[currentTeamIndex || 0]?.group.name || + {userInfo?.memberships?.[currentTeamIndex || 0]?.group?.name || null} diff --git a/src/components/gnb/sidebar/_components/sidebar-header/sidebar-header.stories.tsx b/src/components/gnb/sidebar/_components/sidebar-header/sidebar-header.stories.tsx new file mode 100644 index 00000000..731dc175 --- /dev/null +++ b/src/components/gnb/sidebar/_components/sidebar-header/sidebar-header.stories.tsx @@ -0,0 +1,63 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import SidebarHeader from "./sidebar-header"; +import { useState } from "react"; + +const meta = { + title: "components/SidebarHeader", + component: SidebarHeader, + tags: ["autodocs"], + + args: { + setIsSidebarOpen: () => console.log("상태 변경"), + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Open: Story = { + args: { + isSidebarOpen: true, + }, +}; + +export const Closed: Story = { + args: { + isSidebarOpen: false, + }, + render(args) { + return ( +
+ +
+ ); + }, +}; + +export const Interactive: Story = { + args: { + isSidebarOpen: true, + }, + + render: (args) => { + const [isOpen, setIsOpen] = useState(args.isSidebarOpen); + + return ( +
+ +
+ 현재 상태: {isOpen ? "열림" : "닫힘"} +
+
+ ); + }, +}; diff --git a/src/components/gnb/sidebar/_components/sidebar-header/sidebar-header.tsx b/src/components/gnb/sidebar/_components/sidebar-header/sidebar-header.tsx index 51c45755..1ac3392b 100644 --- a/src/components/gnb/sidebar/_components/sidebar-header/sidebar-header.tsx +++ b/src/components/gnb/sidebar/_components/sidebar-header/sidebar-header.tsx @@ -33,7 +33,11 @@ const SidebarHeader = ({

COWORKERS

)} -