Skip to content
This repository was archived by the owner on Jul 15, 2025. It is now read-only.

Commit 31d6d9c

Browse files
committed
chore
: created shared sidebar component
1 parent 50f6c91 commit 31d6d9c

File tree

5 files changed

+229
-21
lines changed

5 files changed

+229
-21
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from "react";
2+
import { ExternalLink, Home, LogOut, Moon } from "lucide-react";
3+
import { useLogout } from "@/hooks/useLogout";
4+
import { useClientStore } from "@/stores/clientStore";
5+
6+
export const MenuContent: React.FC = () => {
7+
const [isDarkMode, setIsDarkMode] = React.useState(false);
8+
const { isLoggedIn } = useClientStore();
9+
const logout = useLogout();
10+
11+
return (
12+
<div className="p-4 space-y-4">
13+
<a
14+
href="https://champion.trade/"
15+
target="_blank"
16+
rel="noopener noreferrer"
17+
className="flex items-center justify-between cursor-pointer py-2"
18+
>
19+
<div className="flex items-center gap-3">
20+
<Home className="w-5 h-5" />
21+
<span>Go to Home</span>
22+
</div>
23+
<ExternalLink className="w-5 h-5" />
24+
</a>
25+
<div className="flex items-center justify-between cursor-pointer py-2">
26+
<div className="flex items-center gap-3">
27+
<Moon className="w-5 h-5" />
28+
<span>Theme</span>
29+
</div>
30+
<label className="relative inline-flex items-center cursor-pointer">
31+
<input
32+
type="checkbox"
33+
className="sr-only peer"
34+
checked={isDarkMode}
35+
onChange={() => setIsDarkMode(!isDarkMode)}
36+
/>
37+
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
38+
</label>
39+
</div>
40+
{isLoggedIn && (
41+
<button
42+
className="flex items-center gap-3 cursor-pointer py-2 w-full hover:bg-gray-100"
43+
onClick={logout}
44+
>
45+
<LogOut className="w-5 h-5" />
46+
<span>Log out</span>
47+
</button>
48+
)}
49+
</div>
50+
);
51+
};
52+
53+
export default MenuContent;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { FC, useState, useEffect } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import { OPEN_POSITIONS, CLOSED_POSITIONS, Position } from "../PositionsSidebar/positionsSidebarStub";
4+
import { useFilteredPositions } from "../PositionsSidebar/hooks/useFilteredPositions";
5+
import { FilterDropdown } from "../PositionsSidebar/components/FilterDropdown";
6+
7+
export const PositionsContent: FC = () => {
8+
const [isOpenTab, setIsOpenTab] = useState(true);
9+
const navigate = useNavigate();
10+
const [allPositions, setAllPositions] = useState<Position[]>(OPEN_POSITIONS);
11+
12+
const { filteredPositions, selectedFilter, handleFilterSelect } = useFilteredPositions({
13+
isOpenTab,
14+
allPositions,
15+
closedPositions: CLOSED_POSITIONS,
16+
});
17+
18+
useEffect(() => {
19+
fetch("/api/positions")
20+
.then(response => response.json())
21+
.then(data => {
22+
setAllPositions(data);
23+
})
24+
.catch(error => console.error("Error fetching positions:", error));
25+
}, []);
26+
27+
return (
28+
<div className="flex flex-col h-full">
29+
<div className="p-6 flex-1 overflow-auto">
30+
<div className="flex gap-2 p-1 bg-gray-100 rounded-lg">
31+
<button
32+
className={`flex-1 h-8 flex items-center justify-center rounded-lg transition-all ${
33+
isOpenTab
34+
? "bg-white text-black shadow-sm"
35+
: "text-gray-500 hover:bg-gray-50"
36+
}`}
37+
onClick={() => setIsOpenTab(true)}
38+
>
39+
Open
40+
</button>
41+
<button
42+
className={`flex-1 h-8 flex items-center justify-center rounded-lg transition-all ${
43+
!isOpenTab
44+
? "bg-white text-black shadow-sm"
45+
: "text-gray-500 hover:bg-gray-50"
46+
}`}
47+
onClick={() => setIsOpenTab(false)}
48+
>
49+
Closed
50+
</button>
51+
</div>
52+
<div className="mt-4">
53+
<FilterDropdown
54+
isOpenTab={isOpenTab}
55+
selectedFilter={selectedFilter}
56+
onFilterSelect={handleFilterSelect}
57+
/>
58+
</div>
59+
<div className="mt-4 space-y-4">
60+
{filteredPositions.map((position) => (
61+
<div
62+
key={position.id}
63+
className="p-3 rounded-lg shadow-sm cursor-pointer"
64+
onClick={() => navigate(`/contract/${position.id}`)}
65+
>
66+
<div className="flex justify-between text-sm font-medium">
67+
<div className="flex flex-col items-start">
68+
<div className="flex items-center gap-2">
69+
<img src="/market icon.svg" alt="Market Icon" className="w-5 h-8 mb-1" />
70+
</div>
71+
<span className="mb-[5] font-light text-black-400">{position.type}</span>
72+
<span className="text-s font-light text-gray-500 mb-4">{position.market}</span>
73+
</div>
74+
<div>
75+
<div className="flex flex-col items-end">
76+
{isOpenTab ? (
77+
<span className="text-gray-500 w-35 text-xs flex items-center bg-gray-50 px-2 py-1 rounded-md border border-transparent hover:border-gray-300 mb-3">
78+
<span className="mr-2"></span> {position.ticks}
79+
</span>
80+
) : (
81+
<span className="text-red-600 bg-red-50 px-2 py-1 rounded-md text-xs font-medium mb-3">
82+
Closed
83+
</span>
84+
)}
85+
<span className="text-s font-light text-gray-400 mb-[2]">{position.stake}</span>
86+
<span className={`text-sm ${position.profit.startsWith('+') ? 'text-[#008832]' : 'text-red-500'}`}>
87+
{position.profit}
88+
</span>
89+
</div>
90+
</div>
91+
</div>
92+
{isOpenTab && (
93+
<button className="w-full h-6 flex items-center justify-center py-2 border border-black text-xs font-bold rounded-[8]">
94+
Close {position.stake}
95+
</button>
96+
)}
97+
</div>
98+
))}
99+
</div>
100+
</div>
101+
<div className="p-4 font-bold border-t flex justify-between mt-auto">
102+
<span className="text-black-300">Total profit/loss: </span>
103+
<span className="text-red-500">-1.50 USD</span>
104+
</div>
105+
</div>
106+
);
107+
};
108+
109+
export default PositionsContent;

src/components/Sidebar/Sidebar.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React, { useEffect, useRef } from "react";
2+
3+
interface SidebarProps {
4+
isOpen: boolean;
5+
onClose: () => void;
6+
title: string;
7+
children: React.ReactNode;
8+
}
9+
10+
export const Sidebar: React.FC<SidebarProps> = ({ isOpen, onClose, title, children }) => {
11+
const sidebarRef = useRef<HTMLDivElement>(null);
12+
13+
useEffect(() => {
14+
const handleClickOutside = (event: MouseEvent) => {
15+
if (sidebarRef.current && !sidebarRef.current.contains(event.target as Node)) {
16+
onClose();
17+
}
18+
};
19+
20+
document.addEventListener("mousedown", handleClickOutside);
21+
return () => {
22+
document.removeEventListener("mousedown", handleClickOutside);
23+
};
24+
}, [onClose]);
25+
26+
return (
27+
<div
28+
className={`fixed left-[65px] h-full w-[22%] bg-white shadow-lg transform transition-transform duration-300 ease-in-out ${
29+
isOpen ? "translate-x-0" : "-translate-x-[calc(100%+65px)]"
30+
} z-[51] flex flex-col overflow-hidden`}
31+
ref={sidebarRef}
32+
>
33+
<div className="p-4 border-b flex justify-between items-center">
34+
<h2 className="text-lg font-bold">{title}</h2>
35+
<button onClick={onClose} className="text-gray-600 hover:text-gray-900"></button>
36+
</div>
37+
<div className="flex-1 overflow-auto">
38+
{children}
39+
</div>
40+
</div>
41+
);
42+
};
43+
44+
export default Sidebar;

src/components/Sidebar/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { default as Sidebar } from './Sidebar';
2+
export { default as MenuContent } from './MenuContent';
3+
export { default as PositionsContent } from './PositionsContent';

src/layouts/MainLayout/MainLayout.tsx

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import { useMainLayoutStore } from "@/stores/mainLayoutStore"
77
import { Footer } from "./Footer"
88
import { Header } from "./Header"
99
import { SideNav } from "@/components/SideNav"
10-
import PositionsSidebar from "@/components/PositionsSidebar/PositionsSidebar"
11-
import MenuSidebar from "@/components/SideNav/MenuSidebar"
10+
import { Sidebar, MenuContent, PositionsContent } from "@/components/Sidebar"
1211

1312
interface MainLayoutProps {
1413
children: React.ReactNode
@@ -55,25 +54,25 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
5554
<div className="flex flex-1 overflow-hidden">
5655
{isLandscape ? (
5756
<div className="flex flex-1">
58-
<div
59-
className={`${
60-
isSidebarOpen ? "w-[22%]" : "w-0"
61-
} transition-all duration-300 flex-shrink-0`}
62-
>
63-
<PositionsSidebar
64-
isOpen={isSidebarOpen}
65-
onClose={() => setSidebarOpen(false)}
66-
/>
67-
</div>
68-
<div
69-
className={`${
70-
isMenuOpen ? "w-[22%]" : "w-0"
71-
} transition-all duration-300 flex-shrink-0`}
72-
>
73-
<MenuSidebar
74-
isOpen={isMenuOpen}
75-
onClose={() => setMenuOpen(false)}
76-
/>
57+
<div className="relative z-[50]">
58+
{isSidebarOpen && (
59+
<Sidebar
60+
isOpen={isSidebarOpen}
61+
onClose={() => setSidebarOpen(false)}
62+
title="Positions"
63+
>
64+
<PositionsContent />
65+
</Sidebar>
66+
)}
67+
{isMenuOpen && (
68+
<Sidebar
69+
isOpen={isMenuOpen}
70+
onClose={() => setMenuOpen(false)}
71+
title="Menu"
72+
>
73+
<MenuContent />
74+
</Sidebar>
75+
)}
7776
</div>
7877
<main className="flex-1 flex flex-row transition-all duration-300">
7978
{children}

0 commit comments

Comments
 (0)