Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/app/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { useModal } from "@/hooks/useModal.ts";
import { useAtom } from "jotai";
import { loginState } from "@/store/user";
import { SubscriptionModalContent } from "@/components/common/modal/SubscriptionModalContent";

import FloatingWidget from "@/components/app/home/floating/floatingWidget";

type CoinInfoType = {
[coin in "BTC" | "ETH" | "XRP"]: {
Expand Down Expand Up @@ -112,14 +112,14 @@ export default function HomePage() {
}
};

/**
/**
* @description 구독버튼 클릭시 flowbit서비스에 대해 주기적으로 구독할 수 있는 함수입니다.
*/
const openSubscriptionModal = () => {
const openSubscriptionModal = () => {
open({
title: "구독 설정",
content: <SubscriptionModalContent />,
isVisibleBtn: false
isVisibleBtn: false,
});
};

Expand Down Expand Up @@ -226,7 +226,8 @@ export default function HomePage() {
<Button
css={css`
width: 11.4rem !important;
height: 4.5rem;
height: 4.5rem;import FloatingWidget from '../../components/app/home/floating/FloatingWidget';

Comment on lines +229 to +230
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove erroneous import statement in CSS template literal.

There's an import statement incorrectly placed inside a CSS template literal for the button's height. This will cause styling issues and potentially runtime errors.

-                  height: 4.5rem;import FloatingWidget from '../../components/app/home/floating/FloatingWidget';
-
+                  height: 4.5rem;


${DESIGN_SYSTEM_COLOR.GRAY_50}
font-size: 1.6rem;
Expand Down Expand Up @@ -505,6 +506,7 @@ export default function HomePage() {
</div>
</div>
</div>
<FloatingWidget />
</section>

{/* 하단 이미지 */}
Expand Down
5 changes: 5 additions & 0 deletions src/assets/IconAIChartAnalyzeActive.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/IconAIChartAnalyzeInactive.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/IconAIChartRecommendActive.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/IconAIChartRecommendInactive.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/assets/IconFloatingClose.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions src/assets/IconFloatingOpen.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/IconFunctionHelpActive.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/IconFunctionHelpInactive.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/components/app/home/floating/AIChartAnalyze.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function AIChartAnalyze() {
return <div>AIChartAnalyze</div>;
}
Comment on lines +1 to +3
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Component implementation is a placeholder

This component is currently just a placeholder with static text. Since this is part of the main AI feature set (차트해석/Chart Analysis), it should contain the actual implementation for chart analysis functionality.

Consider implementing the actual AI chart analysis content with proper UI components:

-export default function AIChartAnalyze() {
-  return <div>AIChartAnalyze</div>;
+export default function AIChartAnalyze() {
+  return (
+    <div className="ai-chart-analyze">
+      <h3>AI 차트해석</h3>
+      <div className="chart-analysis-content">
+        {/* Implement the actual chart analysis UI here */}
+        <p>선택된 코인의 차트를 AI가 분석 중입니다...</p>
+        {/* Add loading state, analysis results, etc. */}
+      </div>
+    </div>
+  );
}
📝 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 default function AIChartAnalyze() {
return <div>AIChartAnalyze</div>;
}
export default function AIChartAnalyze() {
return (
<div className="ai-chart-analyze">
<h3>AI 차트해석</h3>
<div className="chart-analysis-content">
{/* Implement the actual chart analysis UI here */}
<p>선택된 코인의 차트를 AI가 분석 중입니다...</p>
{/* Add loading state, analysis results, etc. */}
</div>
</div>
);
}

3 changes: 3 additions & 0 deletions src/components/app/home/floating/AIChartRecommendation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function AIChartRecommendation() {
return <div>AIChartRecommendation</div>;
}
Comment on lines +1 to +3
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Component implementation is a placeholder

This component is currently just a placeholder with static text. Since this is part of the main AI feature set (AI 추천판단/Chart Recommendation), it should contain the actual implementation for AI-based recommendation functionality.

Consider implementing the actual AI recommendation content:

-export default function AIChartRecommendation() {
-  return <div>AIChartRecommendation</div>;
+export default function AIChartRecommendation() {
+  return (
+    <div className="ai-chart-recommendation">
+      <h3>AI 추천판단</h3>
+      <div className="recommendation-content">
+        {/* Implement the actual recommendation UI here */}
+        <p>현재 코인 시장에 대한 AI 추천을 분석 중입니다...</p>
+        {/* Add recommendation cards, analysis results, etc. */}
+      </div>
+    </div>
+  );
}
📝 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 default function AIChartRecommendation() {
return <div>AIChartRecommendation</div>;
}
export default function AIChartRecommendation() {
return (
<div className="ai-chart-recommendation">
<h3>AI 추천판단</h3>
<div className="recommendation-content">
{/* Implement the actual recommendation UI here */}
<p>현재 코인 시장에 대한 AI 추천을 분석 중입니다...</p>
{/* Add recommendation cards, analysis results, etc. */}
</div>
</div>
);
}

18 changes: 18 additions & 0 deletions src/components/app/home/floating/FloatingButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import IconFloatingOpen from "@/assets/IconFloatingOpen.svg?react";
import IconFloatingClose from "@/assets/IconFloatingClose.svg?react";

interface FloatingButtonProps {
isOpen: boolean;
onClick: () => void;
}

export default function FloatingButton({
isOpen,
onClick,
}: FloatingButtonProps) {
return (
<button onClick={onClick}>
{isOpen ? <IconFloatingClose /> : <IconFloatingOpen />}
</button>
);
}
Comment thread
klmhyeonwoo marked this conversation as resolved.
71 changes: 71 additions & 0 deletions src/components/app/home/floating/FloatingPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import IconAIChartAnalyzeInactive from "@/assets/IconAIChartAnalyzeInactive.svg?react";
import IconAIChartAnalyzeActive from "@/assets/IconAIChartAnalyzeActive.svg?react";
import IconAIChartRecommendInactive from "@/assets/IconAIChartRecommendInactive.svg?react";
import IconAIChartRecommendActive from "@/assets/IconAIChartRecommendActive.svg?react";
import IconFunctionHelpInactive from "@/assets/IconFunctionHelpInactive.svg?react";
import IconFunctionHelpActive from "@/assets/IconFunctionHelpActive.svg?react";
import FloatingPanelLayout from "./FloatingPanelLayout";
import { useTab } from "@/hooks/useTab";
import AIChartAnalyze from "./AIChartAnalyze";
import AIChartRecommendation from "./AIChartRecommendation";
import FunctionHelp from "./FunctionHelp";

export default function FloatingPanel() {
const TabKey = {
CHART_ANALYZE: "CHART_ANALYZE",
CHART_RECOMMENDATION: "CHART_RECOMMENDATION",
FUNCTION_HELP: "FUNCTION_HELP",
} as const;

Copy link
Copy Markdown
Collaborator

@klmhyeonwoo klmhyeonwoo Apr 21, 2025

Choose a reason for hiding this comment

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

const tabItems = [
  {
    key: TabKey.CHART_ANALYZE,
    label: "AI 차트해석",
    activeIcon: <IconAIChartAnalyzeActive />,
    inactiveIcon: <IconAIChartAnalyzeInactive />,
    content: <AIChartAnalyze />,
  },
  {
    key: TabKey.CHART_RECOMMENDATION,
    label: "AI 추천판단",
    activeIcon: <IconAIChartRecommendActive />,
    inactiveIcon: <IconAIChartRecommendInactive />,
    content: <AIChartRecommendation />,
  },
  ...
];

이렇게 위와 같이 중복적으로 사용되는 값들을 변수로 구조화하게 된다면 추후 수정 사항이 발생했을 때 변수 구조 부분만 살짝 바꾸면서 관련 컴포넌트들을 Map 형태로 쉽게 나타낼 수 있을 것 같다는 생각이 드는데 어떻게 생각하시나영!

<FloatingPanelLayout>
  <Tabs>
    <Tabs.List>
        {tabItems.map(({ key, label, activeIcon, inactiveIcon }) => (
            <Tabs.Trigger
            key={key}
            value={key}
            activeIcon={activeIcon}
            inactiveIcon={inactiveIcon}>
                {label}
            </Tabs.Trigger>
        ))}
    </Tabs.List>

    <Tabs.ContentWrapper>
        {tabItems.map(({ key, content }) => (
            <Tabs.Content key={key} value={key}>
                {content}
            </Tabs.Content>
        ))}
    </Tabs.ContentWrapper>
  </Tabs>
</FloatingPanelLayout>

Copy link
Copy Markdown
Member Author

@Cllaude99 Cllaude99 Apr 22, 2025

Choose a reason for hiding this comment

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

넵! 말씀하신 코드 리뷰를 바탕으로 제가 작성한 코드를 다시 보니, 새로운 탭에 수정이 생기거나 삭제 또는 추가가 발생할 경우에 어려움이 있을 것 같다고 생각했어요!
따라서 코드리뷰를 반영하여 tabItems를 만들는 방식으로 코드를 리팩토링 하였습니다. 이렇게 수정해보니 결과적으로 전체 흐름을 이전보다 깔끔하게 볼 수 있어서 좋은 것 같고 수정이 발생할 때에도 변경이 쉬워질 것 같아서 좋은 것 같아요!

const { Tabs } = useTab<keyof typeof TabKey>(TabKey.CHART_ANALYZE);

const tabItems = [
{
key: TabKey.CHART_ANALYZE,
label: "AI 차트해석",
activeIcon: <IconAIChartAnalyzeActive />,
inactiveIcon: <IconAIChartAnalyzeInactive />,
content: <AIChartAnalyze />,
},
{
key: TabKey.CHART_RECOMMENDATION,
label: "AI 추천판단",
activeIcon: <IconAIChartRecommendActive />,
inactiveIcon: <IconAIChartRecommendInactive />,
content: <AIChartRecommendation />,
},
{
key: TabKey.FUNCTION_HELP,
label: "기능 도움말",
activeIcon: <IconFunctionHelpActive />,
inactiveIcon: <IconFunctionHelpInactive />,
content: <FunctionHelp />,
},
];

return (
<FloatingPanelLayout>
<Tabs>
<Tabs.List>
{tabItems.map(({ key, label, activeIcon, inactiveIcon }) => (
<Tabs.Trigger
key={key}
value={key}
activeIcon={activeIcon}
inactiveIcon={inactiveIcon}
>
{label}
</Tabs.Trigger>
))}
</Tabs.List>
<Tabs.ContentWrapper>
{tabItems.map(({ key, content }) => (
<Tabs.Content key={key} value={key}>
{content}
</Tabs.Content>
))}
</Tabs.ContentWrapper>
</Tabs>
</FloatingPanelLayout>
);
}
41 changes: 41 additions & 0 deletions src/components/app/home/floating/FloatingPanelLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { PropsWithChildren } from "react";
import { css } from "@emotion/react";

export default function FloatingPanelLayout({ children }: PropsWithChildren) {
return (
<div css={containerStyle}>
<div css={panelStyle}>
{children}
<div css={arrowStyle} />
</div>
</div>
);
}

const containerStyle = css`
position: absolute;
bottom: 9rem;
right: 1rem;
`;

const panelStyle = css`
width: 390px;
height: calc(100dvh - 450px);
background-color: white;
padding: 1.5rem;
border-radius: 24px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
border: 1px solid #e5e7eb;
position: relative;
`;

const arrowStyle = css`
position: absolute;
right: 2rem;
bottom: -0.5rem;
width: 2rem;
height: 2rem;
background-color: white;
transform: rotate(45deg);
border-bottom-right-radius: 2px;
`;
40 changes: 40 additions & 0 deletions src/components/app/home/floating/FloatingWidgetLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { PropsWithChildren, forwardRef } from "react";
import { css } from "@emotion/react";

interface FloatingWidgetLayoutProps extends PropsWithChildren {
containerRef: React.RefObject<HTMLDivElement>;
}

const FloatingWidgetLayout = forwardRef<
HTMLDivElement,
FloatingWidgetLayoutProps
>(({ children, containerRef }) => {
return (
<div css={wrapperStyle}>
<div ref={containerRef} css={containerStyle}>
{children}
</div>
</div>
);
});

const wrapperStyle = css`
position: fixed;
bottom: 5rem;
right: 8rem;
button {
background: transparent;
border: none;
padding: 0;
margin: 0;
outline: none;
}
`;

const containerStyle = css`
position: relative;
`;

FloatingWidgetLayout.displayName = "FloatingWidgetLayout";

export default FloatingWidgetLayout;
3 changes: 3 additions & 0 deletions src/components/app/home/floating/FunctionHelp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function FunctionHelp() {
return <div>FunctionHelp</div>;
}
Comment on lines +1 to +3
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Component implementation is a placeholder

This component is currently just a placeholder returning static text. As part of the floating panel's tabbed interface, it should be properly implemented with actual function help content.

Consider implementing the actual help content with proper styling and information about the application's features:

-export default function FunctionHelp() {
-  return <div>FunctionHelp</div>;
+export default function FunctionHelp() {
+  return (
+    <div className="function-help-container">
+      <h3>기능 도움말</h3>
+      <ul>
+        <li>
+          <strong>차트 분석:</strong> 코인 데이터를 분석하여 추세를 파악할 수 있습니다.
+        </li>
+        <li>
+          <strong>AI 추천:</strong> AI 기반 코인 투자 추천을 확인할 수 있습니다.
+        </li>
+        {/* Add more help items as needed */}
+      </ul>
+    </div>
+  );
}
📝 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 default function FunctionHelp() {
return <div>FunctionHelp</div>;
}
export default function FunctionHelp() {
return (
<div className="function-help-container">
<h3>기능 도움말</h3>
<ul>
<li>
<strong>차트 분석:</strong> 코인 데이터를 분석하여 추세를 파악할 있습니다.
</li>
<li>
<strong>AI 추천:</strong> AI 기반 코인 투자 추천을 확인할 있습니다.
</li>
{/* Add more help items as needed */}
</ul>
</div>
);
}

28 changes: 28 additions & 0 deletions src/components/app/home/floating/floatingWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useRef } from "react";
import useToggle from "@/hooks/useToggle";
import useClickOutside from "@/hooks/useClickOutside";
import FloatingWidgetLayout from "./FloatingWidgetLayout";
import FloatingPanel from "./FloatingPanel";
import FloatingButton from "./FloatingButton";

export default function FloatingWidget() {
const floatingRef = useRef<HTMLDivElement>(null);
const {
value: isOpen,
toggleValue: toggleOpen,
setValue: setIsOpen,
} = useToggle(false);

useClickOutside(floatingRef, () => {
if (isOpen) {
setIsOpen(false);
}
});

return (
<FloatingWidgetLayout containerRef={floatingRef}>
{isOpen && <FloatingPanel />}
<FloatingButton isOpen={isOpen} onClick={toggleOpen} />
</FloatingWidgetLayout>
);
}
23 changes: 23 additions & 0 deletions src/hooks/useClickOutside.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { RefObject, useEffect } from "react";

export default function useClickOutside(
ref: RefObject<HTMLElement>,
handler: () => void,
) {
useEffect(() => {
const listener = (event: MouseEvent | TouchEvent) => {
if (!ref.current || ref.current.contains(event.target as Node)) {
return;
}
handler();
};

document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);

return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
}, [ref, handler]);
}
Loading