Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 25 additions & 11 deletions src/_MainPage/components/HeroSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,45 @@ import { Link } from "react-router-dom";

export default function HeroSection() {
return (
<div className="flex flex-col justify-center items-center py-20 sm:py-32 text-center">
<h1 className="font-bold text-white text-4xl sm:text-6xl tracking-tight">
<div className="flex flex-col justify-center items-center px-4 py-10 sm:py-20 md:py-32 text-center">
<h1 className="px-4 font-bold text-white text-3xl sm:text-4xl md:text-6xl tracking-tight">
{HERO_CONTENT.title.split("\n").map((line, i) => (
<React.Fragment key={i}>
{line}
<br />
</React.Fragment>
))}
</h1>
<p className="mt-6 max-w-2xl text-gray-300 text-lg">
{HERO_CONTENT.description.split("\n").map((line, i) => (
<React.Fragment key={i}>
{line}
<br />
</React.Fragment>
))}
<p className="mt-4 sm:mt-6 px-4 max-w-2xl text-gray-300 text-base sm:text-lg">
{/* 모바일 환경에서만 표시되는 텍스트 */}
<span className="sm:hidden block">
{"과거 시장 데이터를 기반으로\n당신의 투자 전략을 시뮬레이션하여\n수익률과 안정성을 미리 예측할 수 있습니다."
.split("\n")
.map((line, i) => (
<React.Fragment key={i}>
{line}
<br />
</React.Fragment>
))}
</span>
{/* 태블릿 이상 환경에서 표시되는 텍스트 */}
<span className="hidden sm:block">
{HERO_CONTENT.description.split("\n").map((line, i) => (
<React.Fragment key={i}>
{line}
<br />
</React.Fragment>
))}
</span>
</p>
<Button
asChild
size="lg"
className="bg-blue-500 hover:bg-blue-600 mt-10 px-9 py-6 rounded-full text-[1rem] text-white"
className="bg-blue-500 hover:bg-blue-600 mt-6 sm:mt-10 px-6 sm:px-9 py-4 sm:py-6 rounded-full text-white md:text-[1rem] text-sm sm:text-base"
>
<Link to="/backtest">
{HERO_CONTENT.cta}
<ArrowRight className="ml-1 w-5 h-5" />
<ArrowRight className="ml-1 w-4 sm:w-5 h-4 sm:h-5" />
</Link>
</Button>
</div>
Expand Down
66 changes: 39 additions & 27 deletions src/_MainPage/components/MarketIndexCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ const MarketIndexCard = ({ marketType, marketIndex, isLoading, error }: MarketIn
// 로딩 상태
if (isLoading) {
return (
<Card className="bg-[#0A194E] border-gray-700 text-white">
<CardHeader>
<CardTitle className="font-medium text-gray-300 text-lg">{marketType}</CardTitle>
<Card className="gap-1 sm:gap-6 bg-[#0A194E] py-3 sm:py-6 border-gray-700 text-white">
<CardHeader className="gap-1 sm:gap-1.5 px-4 sm:px-6">
<CardTitle className="font-medium text-gray-300 text-base sm:text-lg">
{marketType}
</CardTitle>
</CardHeader>
<CardContent>
<CardContent className="px-4 sm:px-6">
<div className="flex justify-center items-center h-48">
<Spinner className="size-12" />
<Spinner className="size-8 sm:size-12" />
</div>
</CardContent>
</Card>
Expand All @@ -50,13 +52,15 @@ const MarketIndexCard = ({ marketType, marketIndex, isLoading, error }: MarketIn
axiosError.response?.data?.detail || "데이터를 불러오는 중 오류가 발생했습니다.";

return (
<Card className="bg-[#0A194E] border-gray-700 text-white">
<CardHeader>
<CardTitle className="font-medium text-gray-300 text-lg">{marketType}</CardTitle>
<Card className="gap-1 sm:gap-6 bg-[#0A194E] py-3 sm:py-6 border-gray-700 text-white">
<CardHeader className="gap-1 sm:gap-1.5 px-4 sm:px-6">
<CardTitle className="font-medium text-gray-300 text-base sm:text-lg">
{marketType}
</CardTitle>
</CardHeader>
<CardContent>
<CardContent className="px-4 sm:px-6">
<div className="flex justify-center items-center h-48">
<p className="text-red-500 text-center">{errorMessage}</p>
<p className="text-red-500 text-sm sm:text-base text-center">{errorMessage}</p>
</div>
</CardContent>
</Card>
Expand All @@ -66,13 +70,15 @@ const MarketIndexCard = ({ marketType, marketIndex, isLoading, error }: MarketIn
// 데이터가 없는 경우
if (!marketIndex) {
return (
<Card className="bg-[#0A194E] border-gray-700 text-white">
<CardHeader>
<CardTitle className="font-medium text-gray-300 text-lg">{marketType}</CardTitle>
<Card className="gap-1 sm:gap-6 bg-[#0A194E] py-3 sm:py-6 border-gray-700 text-white">
<CardHeader className="gap-1 sm:gap-1.5 px-4 sm:px-6">
<CardTitle className="font-medium text-gray-300 text-base sm:text-lg">
{marketType}
</CardTitle>
</CardHeader>
<CardContent>
<CardContent className="px-4 sm:px-6">
<div className="flex justify-center items-center h-48">
<p className="text-gray-400">데이터가 없습니다.</p>
<p className="text-gray-400 text-sm sm:text-base">데이터가 없습니다.</p>
</div>
</CardContent>
</Card>
Expand All @@ -84,13 +90,15 @@ const MarketIndexCard = ({ marketType, marketIndex, isLoading, error }: MarketIn

if (!latestData) {
return (
<Card className="bg-[#0A194E] border-gray-700 text-white">
<CardHeader>
<CardTitle className="font-medium text-gray-300 text-lg">{marketType}</CardTitle>
<Card className="gap-1 sm:gap-6 bg-[#0A194E] py-3 sm:py-6 border-gray-700 text-white">
<CardHeader className="gap-1 sm:gap-1.5 px-4 sm:px-6">
<CardTitle className="font-medium text-gray-300 text-base sm:text-lg">
{marketType}
</CardTitle>
</CardHeader>
<CardContent>
<CardContent className="px-4 sm:px-6">
<div className="flex justify-center items-center h-48">
<p className="text-gray-400">데이터가 없습니다.</p>
<p className="text-gray-400 text-sm sm:text-base">데이터가 없습니다.</p>
</div>
</CardContent>
</Card>
Expand All @@ -108,18 +116,22 @@ const MarketIndexCard = ({ marketType, marketIndex, isLoading, error }: MarketIn
}));

return (
<Card className="bg-[#0A194E] border-gray-700 text-white">
<CardHeader>
<CardTitle className="font-medium text-gray-300 text-lg">{marketType}</CardTitle>
<Card className="gap-1 sm:gap-6 bg-[#0A194E] py-2 sm:py-6 border-gray-700 text-white">
<CardHeader className="gap-1 sm:gap-1.5 px-4 sm:px-6 pt-2">
<CardTitle className="font-medium text-gray-300 text-base sm:text-lg">
{marketType}
</CardTitle>
</CardHeader>

<CardContent>
<p className="font-bold text-4xl">{latestData.closePrice.toLocaleString()}</p>
<p className={`mt-2 text-lg font-semibold ${getChangeColor(latestData.changeRate)}`}>
<CardContent className="px-4 sm:px-6">
<p className="font-bold text-2xl sm:text-4xl">{latestData.closePrice.toLocaleString()}</p>
<p
className={`mt-1.5 sm:mt-2 text-sm sm:text-lg font-semibold ${getChangeColor(latestData.changeRate)}`}
>
{arrow} {Math.abs(latestData.changeAmount).toFixed(2)} ({sign}
{latestData.changeRate.toFixed(2)}%)
</p>
<div className="mt-4 h-24">
<div className="mt-3 sm:mt-4 h-24">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={chartData}>
<YAxis domain={["dataMin - 10", "dataMax + 10"]} hide />
Expand Down
6 changes: 4 additions & 2 deletions src/components/Navbar/Logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { Link } from "react-router-dom";

const Logo = () => {
return (
<div className="flex items-center justify-center bg-transparent font-lalezar text-[6.25rem]">
<Link to="/">STPT</Link>
<div className="flex justify-center items-center bg-transparent pt-2 font-lalezar text-[4rem] md:text-[6.25rem]">
<Link to="/" className="pt-2">
STPT
</Link>
</div>
);
};
Expand Down
81 changes: 73 additions & 8 deletions src/components/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,82 @@
import Logo from "@/components/Navbar/Logo";
import NavItem from "@/components/Navbar/NavItem";
import SearchBar from "@/components/Navbar/SearchBar";
import SideBarButton from "@/components/Navbar/SideBarButton";
import { Link } from "react-router-dom";
import { useState, useEffect, useRef } from "react";

const Navbar = () => {
const [isSideBarOpen, setIsSideBarOpen] = useState(false);
const modalRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLDivElement>(null);

// 외부 클릭 시 모달 닫기
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as Node;
if (
modalRef.current &&
!modalRef.current.contains(target) &&
buttonRef.current &&
!buttonRef.current.contains(target)
) {
setIsSideBarOpen(false);
}
};

if (isSideBarOpen) {
document.addEventListener("mousedown", handleClickOutside);
}

return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isSideBarOpen]);

return (
<nav className="relative flex gap-16 bg-transparent mr-[3.25rem] ml-[2.6875rem] w-auto h-40">
<Logo />
<ul className="relative flex gap-[3.75rem] p-0 w-full">
<NavItem to="portfolio" label="Portfolios" />
<NavItem to="markets" label="Markets" />
</ul>
<SearchBar />
</nav>
<>
<nav className="z-50 relative flex justify-between items-center gap-16 bg-transparent mx-[2rem] sm:mx-[3rem] w-auto md:h-40">
<Logo />
<div className="relative" ref={buttonRef}>
<SideBarButton isOpen={isSideBarOpen} onClick={() => setIsSideBarOpen(!isSideBarOpen)} />

{/* 드롭다운 - 사이드바 버튼 아래에 표시 */}
{isSideBarOpen && (
<div
ref={modalRef}
className="md:hidden top-full right-0 z-50 absolute bg-[#0A194E] slide-in-from-top-2 shadow-lg mt-2 border border-gray-700 rounded-lg min-w-[150px] animate-in duration-200 fade-in"
>
<div className="p-4">
<p className="opacity-45 mb-2 text-white text-sm">Menu</p>
<div className="flex flex-col gap-">
<Link
to="/portfolio"
className="hover:opacity-100 py-1 font-bold text-white text-base transition-opacity"
onClick={() => setIsSideBarOpen(false)}
>
Portfolio
</Link>
<Link
to="/markets"
className="hover:opacity-100 py-1 font-bold text-white text-base transition-opacity"
onClick={() => setIsSideBarOpen(false)}
>
Markets
</Link>
</div>
</div>
</div>
)}
</div>
<div className="hidden md:flex md:flex-row md:justify-between md:items-center md:gap-16 md:w-full">
<ul className="relative flex gap-[3.75rem] p-0 w-full">
<NavItem to="portfolio" label="Portfolios" />
<NavItem to="markets" label="Markets" />
</ul>
<SearchBar />
</div>
</nav>
</>
);
};

Expand Down
34 changes: 34 additions & 0 deletions src/components/Navbar/SideBarButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Button } from "@/components/ui/button";

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

export default function SideBarButton({ isOpen, onClick }: SideBarButtonProps) {
return (
<Button
variant="ghost"
className="md:hidden relative flex flex-col justify-center items-center gap-1.5 bg-transparent p-2 border border-white/20 rounded-xl w-12 h-12 transition-colors hover:transparent"
onClick={onClick}
>
<span
className={`bg-white rounded-full w-5 h-0.5 transition-all duration-300 ease-in-out ${
isOpen ? "rotate-45 translate-y-2" : ""
}`}
></span>

<span
className={`bg-white rounded-full w-5 h-0.5 transition-all duration-300 ease-in-out ${
isOpen ? "opacity-0" : "opacity-100"
}`}
></span>

<span
className={`bg-white rounded-full w-5 h-0.5 transition-all duration-300 ease-in-out ${
isOpen ? "-rotate-45 -translate-y-2" : ""
}`}
></span>
</Button>
);
}