Skip to content

Conversation

@Han-wo
Copy link
Collaborator

@Han-wo Han-wo commented Dec 10, 2024

#️⃣ 이슈

📝 작업 내용

간단한 웹사이트 이용방법 튜토리얼을 제작했습니다

📸 스크린샷

default.webm

✅ 체크 리스트

  • 적절한 Title 작성
  • 적절한 Label 지정
  • Assignee 및 Reviewer 지정
  • 로컬 작동 확인
  • Merge 되는 브랜치 확인

👩‍💻 공유 포인트 및 논의 사항

Summary by CodeRabbit

  • 새로운 기능
    • 사용자 튜토리얼을 위한 모달 및 단계별 안내 추가.
    • 튜토리얼 내용 및 단계 구성 요소 추가.
  • 버그 수정
    • 튜토리얼 완료 상태를 로컬 스토리지에 저장하여 중복 표시 방지.
  • 문서화
    • 튜토리얼 관련 상수 및 타입 인터페이스 추가.

@Han-wo Han-wo added 우선순위: MEDIUM New feature or request ✨ FEAT This doesn't seem right labels Dec 10, 2024
@Han-wo Han-wo self-assigned this Dec 10, 2024
@Han-wo Han-wo linked an issue Dec 10, 2024 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Dec 10, 2024

Walkthrough

이 변경 사항은 주식 거래 튜토리얼 페이지를 위한 여러 컴포넌트를 추가합니다. TutorialContainer, ChartTutorialContainer, TutorialModal, TutorialContent와 같은 새로운 React 컴포넌트가 도입되며, 각 컴포넌트는 튜토리얼의 단계와 내용을 관리하고 표시하는 기능을 포함합니다. 또한, 튜토리얼 단계와 내용을 정의하는 상수 및 인터페이스가 추가되어 튜토리얼 관련 데이터의 구조화와 타입 지정이 이루어졌습니다.

Changes

파일 경로 변경 요약
src/app/layout.tsx TutorialContainer 컴포넌트를 추가하여 RootLayout에 포함시킴.
src/app/search/[id]/_components/tutorial/constant/index.ts TUTORIAL_STEPS 상수를 추가하여 튜토리얼 단계와 내용을 정의함.
src/app/search/[id]/_components/tutorial/tutorial-container.tsx ChartTutorialContainer 컴포넌트를 추가하여 튜토리얼 모달의 가시성을 관리함.
src/app/search/[id]/_components/tutorial/tutorial-modal.tsx TutorialModal 컴포넌트를 추가하여 튜토리얼 모달의 UI 및 단계 관리를 구현함.
src/app/search/[id]/_components/tutorial/types/index.ts TutorialContentItemTutorialStep 인터페이스를 추가하여 튜토리얼 데이터 구조를 정의함.
src/app/search/[id]/page.tsx TutorialContainer 컴포넌트를 추가하여 StockPage에 포함시킴.
src/components/common/tutorial/_components/tutorial-container.tsx TutorialContainer 컴포넌트를 추가하여 튜토리얼 모달의 가시성을 관리함.
src/components/common/tutorial/_components/tutorial-contents.tsx TutorialContent 컴포넌트를 추가하여 튜토리얼 단계 내용을 표시함.
src/components/common/tutorial/_components/tutorial-modal.tsx TutorialModal 컴포넌트를 추가하여 튜토리얼 모달의 UI 및 단계 관리를 구현함.
src/components/common/tutorial/constant/index.ts TUTORIAL_STEPS 상수를 추가하여 튜토리얼 단계와 내용을 정의함.
src/components/common/tutorial/types/index.ts TutorialContentItemTutorialStep 인터페이스를 추가하여 튜토리얼 데이터 구조를 정의함.

Assessment against linked issues

Objective Addressed Explanation
주식 거래 튜토리얼 페이지 제작 (#73)

Possibly related PRs

  • feat: 공용 토스트 제작 #50: TutorialContainer 컴포넌트를 도입하는 주요 PR로, 이 PR은 TutorialContainer 내에서 사용될 수 있는 공유 Toast 컴포넌트를 구현함.
  • Refactor/64: 로딩/에러 처리  #68: 오류 처리 및 로딩 상태를 개선하는 PR로, TutorialContainer가 튜토리얼 표시 중 로딩 상태나 오류를 관리할 필요가 있을 수 있음.

Suggested labels

💄 DESIGN

Suggested reviewers

  • cindycho0423

Poem

🐰
튜토리얼 페이지가 열렸네,
새로운 지식이 가득하네.
모달 속에서 단계가 쏙,
주식 거래의 비밀을 찾아보자!
함께 배우고, 함께 성장해,
튜토리얼과 함께 뛰어보자! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Experiment)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Outside diff range and nitpick comments (10)
src/components/common/tutorial/types/index.ts (1)

1-9: 타입 정의에 문서화 추가 필요

인터페이스의 목적과 각 속성의 역할을 명확히 하기 위해 JSDoc 문서화가 필요합니다.

다음과 같이 문서화를 추가하는 것을 제안합니다:

+/**
+ * 튜토리얼의 개별 콘텐츠 항목을 정의하는 인터페이스
+ */
 export interface TutorialContentItem {
+  /** 콘텐츠의 부제목 */
   subtitle: string;
+  /** 콘텐츠의 상세 설명 */
   description: string;
 }

+/**
+ * 튜토리얼의 단계별 구조를 정의하는 인터페이스
+ */
 export interface TutorialStep {
+  /** 튜토리얼 단계의 제목 */
   title: string;
+  /** 해당 단계의 콘텐츠 항목 배열 */
   content: TutorialContentItem[];
 }
src/components/common/tutorial/_components/tutorial-container.tsx (1)

7-20: 타입 안전성 개선 필요

컴포넌트와 props에 대한 명시적인 타입 정의가 필요합니다.

다음과 같이 수정하는 것을 제안합니다:

-export default function TutorialContainer() {
+interface TutorialModalProps {
+  isOpen: boolean;
+  onClose: () => void;
+}
+
+export default function TutorialContainer(): React.ReactElement {
   const [isOpen, setIsOpen] = useState<boolean>(false);

   useEffect(() => {
     // ... 이전 코드 ...
   }, []);

-  return <TutorialModal isOpen={isOpen} onClose={() => setIsOpen(false)} />;
+  const handleClose = useCallback(() => {
+    setIsOpen(false);
+  }, []);
+
+  return <TutorialModal isOpen={isOpen} onClose={handleClose} />;
 }
src/app/search/[id]/_components/tutorial/tutorial-container.tsx (1)

20-22: 튜토리얼 완료 처리 로직 추가 필요

모달이 닫힐 때 튜토리얼 완료 상태를 저장하는 로직을 추가하는 것이 좋습니다.

다음과 같이 개선해보세요:

+const handleClose = () => {
+  try {
+    localStorage.setItem("chart-tutorial-completed", "true");
+    setIsOpen(false);
+  } catch (error) {
+    console.error('튜토리얼 완료 상태 저장 중 오류 발생:', error);
+    setIsOpen(false);
+  }
+};

 return (
-  <ChartTutorialModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
+  <ChartTutorialModal isOpen={isOpen} onClose={handleClose} />
 );
src/components/common/tutorial/_components/tutorial-contents.tsx (1)

1-2: eslint-disable 범위 축소 필요

전체 파일에 대한 eslint 비활성화보다는 특정 규칙에 대해서만 비활성화하는 것이 좋습니다.

-/* eslint-disable */
+/* eslint-disable react/no-array-index-as-key */
src/app/search/[id]/_components/tutorial/constant/index.ts (1)

3-43: 타입 안정성 및 국제화 지원 개선 필요

현재 구조에 다음과 같은 개선이 필요합니다:

  1. 문자열 리터럴 타입 정의
  2. 다국어 지원을 위한 구조 개선
  3. 콘텐츠 분리

다음과 같은 구조적 개선을 제안합니다:

  1. 타입 정의 개선:
type TutorialStepTitle = '차트 보기' | '거래하기' | '주문 관리';
type TutorialSubtitle = '기간 설정' | '이동평균선(MA)' | '매수/매도' | '실시간 호가' | '체결 내역' | '미체결 주문';

interface TutorialStep {
  title: TutorialStepTitle;
  content: {
    subtitle: TutorialSubtitle;
    description: string;
  }[];
}
  1. 다국어 지원을 위한 키 기반 구조:
const TUTORIAL_STEPS_KEYS = {
  CHART_VIEW: 'chart_view',
  TRADING: 'trading',
  ORDER_MANAGEMENT: 'order_management',
} as const;

const TUTORIAL_STEPS = [
  {
    titleKey: TUTORIAL_STEPS_KEYS.CHART_VIEW,
    content: [
      {
        subtitleKey: 'period_setting',
        descriptionKey: 'period_setting_desc',
      },
      // ...
    ],
  },
  // ...
];
  1. 별도의 파일로 콘텐츠 분리:
// tutorial-content.ko.ts
export const TUTORIAL_CONTENT = {
  [TUTORIAL_STEPS_KEYS.CHART_VIEW]: {
    title: '차트 보기',
    period_setting: {
      subtitle: '기간 설정',
      description: '일/주/월 버튼으로 원하는 기간의 차트를 확인하세요.',
    },
    // ...
  },
  // ...
};
src/components/common/tutorial/constant/index.ts (1)

3-58: 다국어 지원을 위한 i18n 구현 검토 필요

현재 튜토리얼 텍스트가 한국어로 하드코딩되어 있습니다. 향후 다국어 지원을 위해 i18n 구현을 고려해보시는 것이 좋을 것 같습니다.

예시 구현:

const TUTORIAL_STEPS_KO = {
  home: {
    title: "홈",
    content: [
      {
        subtitle: "투자 현황",
        description: "전체 계좌의 수익률과 포트폴리오 현황을 한눈에 확인하세요."
      },
      // ...
    ]
  },
  // ...
};

const TUTORIAL_STEPS: Record<string, typeof TUTORIAL_STEPS_KO> = {
  ko: TUTORIAL_STEPS_KO,
  // en: TUTORIAL_STEPS_EN,
  // ...
};
src/app/layout.tsx (1)

7-7: import 문 정렬 개선 필요

관련된 import 문들을 함께 그룹화하여 가독성을 높이는 것이 좋습니다. TutorialContainer를 다른 컴포넌트 import 문들과 함께 그룹화하는 것을 추천드립니다.

import Toast from "@/components/common/toast/index";
-import TutorialContainer from "@/components/common/tutorial/_components/tutorial-container";
import MainContent from "@/components/main-content";
import NavBar from "@/components/nav-bar";
+import TutorialContainer from "@/components/common/tutorial/_components/tutorial-container";
src/app/search/[id]/_components/tutorial/tutorial-modal.tsx (2)

29-29: 매직 스트링 상수화 필요

"stock-tutorial-completed"와 같은 매직 스트링은 상수로 분리하여 관리하는 것이 좋습니다.

const TUTORIAL_STORAGE_KEY = {
  STOCK_COMPLETED: "stock-tutorial-completed"
} as const;

56-82: 버튼 컴포넌트 추출 권장

반복되는 버튼 컴포넌트를 별도의 컴포넌트로 추출하여 재사용성을 높이는 것이 좋습니다.

interface TutorialButtonProps {
  onClick: () => void;
  children: React.ReactNode;
  variant: 'primary' | 'secondary';
}

function TutorialButton({ onClick, children, variant }: TutorialButtonProps) {
  const baseStyles = "px-8 py-4 text-16-600 rounded-md";
  const variantStyles = {
    primary: "bg-green-500 text-white hover:bg-green-600",
    secondary: "border border-gray-300 text-gray-700 hover:bg-gray-50"
  };

  return (
    <button
      type="button"
      onClick={onClick}
      className={`${baseStyles} ${variantStyles[variant]}`}
    >
      {children}
    </button>
  );
}
src/components/common/tutorial/_components/tutorial-modal.tsx (1)

37-41: 클라이언트 사이드 네비게이션 최적화 필요

매 스텝 변경마다 router.push를 호출하면 불필요한 페이지 리로드가 발생할 수 있습니다.

shallow 옵션을 사용하여 최적화하는 것을 고려해보세요:

  const handleNext = useCallback(() => {
    setCurrentStep((prev) => Math.min(prev + 1, TUTORIAL_STEPS.length - 1));
-   router.push(getPathForStep(currentStep + 1));
+   router.push(getPathForStep(currentStep + 1), undefined, { shallow: true });
  }, [currentStep, router]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 76d3ce1 and 687cbc0.

📒 Files selected for processing (11)
  • src/app/layout.tsx (2 hunks)
  • src/app/search/[id]/_components/tutorial/constant/index.ts (1 hunks)
  • src/app/search/[id]/_components/tutorial/tutorial-container.tsx (1 hunks)
  • src/app/search/[id]/_components/tutorial/tutorial-modal.tsx (1 hunks)
  • src/app/search/[id]/_components/tutorial/types/index.ts (1 hunks)
  • src/app/search/[id]/page.tsx (2 hunks)
  • src/components/common/tutorial/_components/tutorial-container.tsx (1 hunks)
  • src/components/common/tutorial/_components/tutorial-contents.tsx (1 hunks)
  • src/components/common/tutorial/_components/tutorial-modal.tsx (1 hunks)
  • src/components/common/tutorial/constant/index.ts (1 hunks)
  • src/components/common/tutorial/types/index.ts (1 hunks)
🔇 Additional comments (1)
src/app/search/[id]/page.tsx (1)

6-6: 적절한 튜토리얼 컨테이너 통합

튜토리얼 컨테이너가 페이지 최상단에 적절히 배치되어 있으며, 기존 컴포넌트들과의 통합이 깔끔하게 이루어졌습니다.

Also applies to: 63-63

Comment on lines +1 to +9
export interface TutorialContentItem {
subtitle: string;
description: string;
}

export interface TutorialStep {
title: string;
content: TutorialContentItem[];
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

중복된 타입 정의 제거 필요

동일한 인터페이스가 src/components/common/tutorial/types/index.ts에도 정의되어 있습니다. 코드 중복을 피하고 유지보수성을 높이기 위해 다음과 같은 개선이 필요합니다:

  1. common 디렉토리의 타입 정의를 재사용
  2. 필요한 타입만 import하여 사용

다음과 같이 수정하는 것을 제안합니다:

-export interface TutorialContentItem {
-  subtitle: string;
-  description: string;
-}
-
-export interface TutorialStep {
-  title: string;
-  content: TutorialContentItem[];
-}
+import type { TutorialContentItem, TutorialStep } from '@/components/common/tutorial/types';
+
+export type { TutorialContentItem, TutorialStep };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface TutorialContentItem {
subtitle: string;
description: string;
}
export interface TutorialStep {
title: string;
content: TutorialContentItem[];
}
import type { TutorialContentItem, TutorialStep } from '@/components/common/tutorial/types';
export type { TutorialContentItem, TutorialStep };

Comment on lines +10 to +17
useEffect(() => {
const hasCompletedTutorial = localStorage.getItem("nav-tutorial-completed");
if (!hasCompletedTutorial) {
setIsOpen(true);
// 튜토리얼을 본 것으로 표시
localStorage.setItem("nav-tutorial-completed", "true");
}
}, []);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

localStorage 접근 및 useEffect 개선 필요

localStorage 접근 시 발생할 수 있는 예외 처리와 useEffect의 안전한 구현이 필요합니다.

다음과 같이 수정하는 것을 제안합니다:

+const TUTORIAL_COMPLETED_KEY = 'nav-tutorial-completed';
+
 export default function TutorialContainer() {
   const [isOpen, setIsOpen] = useState(false);
 
   useEffect(() => {
-    const hasCompletedTutorial = localStorage.getItem("nav-tutorial-completed");
-    if (!hasCompletedTutorial) {
-      setIsOpen(true);
-      // 튜토리얼을 본 것으로 표시
-      localStorage.setItem("nav-tutorial-completed", "true");
+    try {
+      const hasCompletedTutorial = localStorage.getItem(TUTORIAL_COMPLETED_KEY);
+      if (!hasCompletedTutorial) {
+        setIsOpen(true);
+        localStorage.setItem(TUTORIAL_COMPLETED_KEY, 'true');
+      }
+    } catch (error) {
+      console.error('튜토리얼 상태 저장 중 오류 발생:', error);
     }
+    
+    return () => {
+      // cleanup 로직이 필요한 경우 여기에 추가
+    };
   }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const hasCompletedTutorial = localStorage.getItem("nav-tutorial-completed");
if (!hasCompletedTutorial) {
setIsOpen(true);
// 튜토리얼을 본 것으로 표시
localStorage.setItem("nav-tutorial-completed", "true");
}
}, []);
const TUTORIAL_COMPLETED_KEY = 'nav-tutorial-completed';
useEffect(() => {
try {
const hasCompletedTutorial = localStorage.getItem(TUTORIAL_COMPLETED_KEY);
if (!hasCompletedTutorial) {
setIsOpen(true);
localStorage.setItem(TUTORIAL_COMPLETED_KEY, 'true');
}
} catch (error) {
console.error('튜토리얼 상태 저장 중 오류 발생:', error);
}
return () => {
// cleanup 로직이 필요한 경우 여기에 추가
};
}, []);

Comment on lines +10 to +18
useEffect(() => {
const hasCompletedTutorial = localStorage.getItem(
"chart-tutorial-completed",
);
if (!hasCompletedTutorial) {
setIsOpen(true);
localStorage.setItem("chart-tutorial-completed", "true");
}
}, []);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

localStorage 접근 및 상태 관리 개선 필요

다음과 같은 개선사항을 고려해주세요:

  1. localStorage 접근 시 예외 처리가 필요합니다
  2. 튜토리얼을 실제로 완료했을 때 localStorage에 저장하는 것이 더 적절할 수 있습니다

다음과 같이 개선해보세요:

 useEffect(() => {
-  const hasCompletedTutorial = localStorage.getItem(
-    "chart-tutorial-completed",
-  );
-  if (!hasCompletedTutorial) {
-    setIsOpen(true);
-    localStorage.setItem("chart-tutorial-completed", "true");
-  }
+  try {
+    const hasCompletedTutorial = localStorage.getItem(
+      "chart-tutorial-completed",
+    );
+    if (!hasCompletedTutorial) {
+      setIsOpen(true);
+    }
+  } catch (error) {
+    console.error('로컬 스토리지 접근 중 오류 발생:', error);
+  }
 }, []);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +11 to +24
<div className="mb-20">
<h3 className="text-20-500 mb-10 font-semibold text-gray-800">
{step.title}
</h3>

<div className="space-y-20">
{step.content.map((item, idx) => (
<div key={`${step.title}-content-${idx}`}>
<h4 className="text-16-600 text-gray-700 mb-10">{item.subtitle}</h4>
<p className="text-14-500 text-gray-600">{item.description}</p>
</div>
))}
</div>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성 및 시맨틱 마크업 개선 필요

현재 구조에 다음과 같은 개선이 필요합니다:

  1. 시맨틱한 HTML 구조 사용
  2. 접근성 속성 추가
  3. 더 안정적인 키 생성 방식 적용

다음과 같이 개선해보세요:

-    <div className="mb-20">
+    <section className="mb-20" aria-labelledby="tutorial-title">
-      <h3 className="text-20-500 mb-10 font-semibold text-gray-800">
+      <h3 id="tutorial-title" className="text-20-500 mb-10 font-semibold text-gray-800">
         {step.title}
       </h3>

-      <div className="space-y-20">
+      <ul className="space-y-20 list-none" role="list">
         {step.content.map((item, idx) => (
-          <div key={`${step.title}-content-${idx}`}>
+          <li key={`${step.title}-${item.subtitle}-${idx}`}>
             <h4 className="text-16-600 text-gray-700 mb-10">{item.subtitle}</h4>
             <p className="text-14-500 text-gray-600">{item.description}</p>
-          </div>
+          </li>
         ))}
-      </div>
-    </div>
+      </ul>
+    </section>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="mb-20">
<h3 className="text-20-500 mb-10 font-semibold text-gray-800">
{step.title}
</h3>
<div className="space-y-20">
{step.content.map((item, idx) => (
<div key={`${step.title}-content-${idx}`}>
<h4 className="text-16-600 text-gray-700 mb-10">{item.subtitle}</h4>
<p className="text-14-500 text-gray-600">{item.description}</p>
</div>
))}
</div>
</div>
<section className="mb-20" aria-labelledby="tutorial-title">
<h3 id="tutorial-title" className="text-20-500 mb-10 font-semibold text-gray-800">
{step.title}
</h3>
<ul className="space-y-20 list-none" role="list">
{step.content.map((item, idx) => (
<li key={`${step.title}-${item.subtitle}-${idx}`}>
<h4 className="text-16-600 text-gray-700 mb-10">{item.subtitle}</h4>
<p className="text-14-500 text-gray-600">{item.description}</p>
</li>
))}
</ul>
</section>

Comment on lines +39 to +46
<button
type="button"
onClick={onClose}
className="rounded-full p-1 hover:bg-gray-100"
>
<X className="size-25 text-gray-500" />
</button>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성 개선 필요

버튼 요소들에 대한 접근성 속성이 누락되어 있습니다. 스크린 리더 사용자를 위한 aria 속성 추가가 필요합니다.

  <button
    type="button"
    onClick={onClose}
+   aria-label="튜토리얼 닫기"
    className="rounded-full p-1 hover:bg-gray-100"
  >
    <X className="size-25 text-gray-500" />
  </button>

  <button
    type="button"
    onClick={handlePrev}
+   aria-label="이전 단계로 이동"
    className="flex items-center gap-4 rounded-md border border-gray-300 px-8 py-4 text-16-600 text-gray-700 hover:bg-gray-50"
  >

Also applies to: 56-64, 66-74, 75-82

Comment on lines +27 to +32
const handleComplete = useCallback(() => {
if (typeof window !== "undefined") {
localStorage.setItem("stock-tutorial-completed", "true");
}
onClose();
}, [onClose]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

localStorage 접근 시 예외 처리 필요

localStorage 접근 시 발생할 수 있는 예외 상황(private 모드 등)에 대한 처리가 필요합니다.

  const handleComplete = useCallback(() => {
    if (typeof window !== "undefined") {
+     try {
        localStorage.setItem("stock-tutorial-completed", "true");
+     } catch (error) {
+       console.error("Failed to save tutorial status:", error);
+     }
    }
    onClose();
  }, [onClose]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleComplete = useCallback(() => {
if (typeof window !== "undefined") {
localStorage.setItem("stock-tutorial-completed", "true");
}
onClose();
}, [onClose]);
const handleComplete = useCallback(() => {
if (typeof window !== "undefined") {
try {
localStorage.setItem("stock-tutorial-completed", "true");
} catch (error) {
console.error("Failed to save tutorial status:", error);
}
}
onClose();
}, [onClose]);

Comment on lines +49 to +54
const handleComplete = useCallback(() => {
if (typeof window !== "undefined") {
localStorage.setItem("nav-tutorial-completed", "true");
}
onClose();
}, [onClose]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

localStorage 접근 에러 처리 개선 필요

localStorage 접근 시 발생할 수 있는 예외 상황(예: 사용자가 쿠키를 차단한 경우)에 대한 처리가 필요합니다.

다음과 같이 개선해보세요:

  const handleComplete = useCallback(() => {
    if (typeof window !== "undefined") {
+     try {
        localStorage.setItem("nav-tutorial-completed", "true");
+     } catch (error) {
+       console.error("Failed to save tutorial state:", error);
+     }
    }
    onClose();
  }, [onClose]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleComplete = useCallback(() => {
if (typeof window !== "undefined") {
localStorage.setItem("nav-tutorial-completed", "true");
}
onClose();
}, [onClose]);
const handleComplete = useCallback(() => {
if (typeof window !== "undefined") {
try {
localStorage.setItem("nav-tutorial-completed", "true");
} catch (error) {
console.error("Failed to save tutorial state:", error);
}
}
onClose();
}, [onClose]);

Comment on lines +21 to +35
// 스텝에 따른 경로 반환
const getPathForStep = (step: number) => {
switch (step) {
case 0:
return "/";
case 1:
return "/search";
case 2:
return "/my-account";
case 3:
return "/portfolio";
default:
return "/";
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

라우팅 로직 개선 필요

현재 구현된 getPathForStep 함수는 하드코딩된 경로를 사용하고 있어 유지보수가 어려울 수 있습니다. 상수로 분리하거나 설정 파일로 관리하는 것이 좋습니다.

다음과 같이 개선해보세요:

+ const STEP_PATHS = {
+   0: "/",
+   1: "/search",
+   2: "/my-account",
+   3: "/portfolio"
+ } as const;

  const getPathForStep = (step: number) => {
-   switch (step) {
-     case 0:
-       return "/";
-     case 1:
-       return "/search";
-     case 2:
-       return "/my-account";
-     case 3:
-       return "/portfolio";
-     default:
-       return "/";
-   }
+   return STEP_PATHS[step as keyof typeof STEP_PATHS] || "/";
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 스텝에 따른 경로 반환
const getPathForStep = (step: number) => {
switch (step) {
case 0:
return "/";
case 1:
return "/search";
case 2:
return "/my-account";
case 3:
return "/portfolio";
default:
return "/";
}
};
const STEP_PATHS = {
0: "/",
1: "/search",
2: "/my-account",
3: "/portfolio"
} as const;
// 스텝에 따른 경로 반환
const getPathForStep = (step: number) => {
return STEP_PATHS[step as keyof typeof STEP_PATHS] || "/";
};

Comment on lines +56 to +108
return (
<BaseModal isOpen={isOpen} onClose={onClose}>
<div className="shadow-xl rounded-lg bg-white px-26 py-20">
<div className="mb-15 flex items-center justify-between">
<h2 className="text-24-600 text-gray-900">GrowFoilo 둘러보기</h2>
<button
type="button"
onClick={onClose}
className="rounded-full p-3 hover:bg-gray-100"
>
<X className="size-25 text-gray-500" />
</button>
</div>

<TutorialContent step={TUTORIAL_STEPS[currentStep]} />

<div className="flex items-center justify-between">
<div className="text-16-600 text-gray-500">
{currentStep + 1} / {TUTORIAL_STEPS.length}
</div>
<div className="flex gap-10">
{currentStep > 0 && (
<button
type="button"
onClick={handlePrev}
className="flex items-center gap-4 rounded-md border border-gray-300 px-8 py-4 text-16-600 text-gray-700 hover:bg-gray-50"
>
<ChevronLeft className="size-16" />
이전
</button>
)}
{currentStep < TUTORIAL_STEPS.length - 1 ? (
<button
type="button"
onClick={handleNext}
className="flex items-center gap-4 rounded-md bg-green-500 px-8 py-4 text-16-600 text-white hover:bg-green-600"
>
다음
<ChevronRight className="size-16" />
</button>
) : (
<button
type="button"
onClick={handleComplete}
className="rounded-md bg-green-500 px-8 py-4 text-16-600 text-white hover:bg-green-600"
>
시작하기
</button>
)}
</div>
</div>
</div>
</BaseModal>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성 개선 필요

모달 컴포넌트의 접근성이 부족합니다. 키보드 네비게이션과 스크린 리더 지원을 추가해야 합니다.

다음 개선사항을 적용해주세요:

  1. ARIA 레이블 추가:
- <div className="shadow-xl rounded-lg bg-white px-26 py-20">
+ <div 
+   className="shadow-xl rounded-lg bg-white px-26 py-20"
+   role="dialog"
+   aria-labelledby="tutorial-title"
+   aria-describedby="tutorial-desc"
+ >
  1. 키보드 네비게이션:
  <button
    type="button"
    onClick={handleNext}
+   onKeyDown={(e) => e.key === 'Enter' && handleNext()}
    className="flex items-center gap-4 rounded-md bg-green-500 px-8 py-4 text-16-600 text-white hover:bg-green-600"
  >

Committable suggestion skipped: line range outside the PR's diff.

@github-actions
Copy link

@Han-wo Han-wo merged commit e55fb9f into develop Dec 10, 2024
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ FEAT This doesn't seem right 우선순위: MEDIUM New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 튜토리얼 페이지 제작

2 participants